add handler recorder

This commit is contained in:
ginuerzh
2024-09-14 23:21:25 +08:00
parent e7581f0a9e
commit 69455ace9d
39 changed files with 1256 additions and 271 deletions
+5
View File
@@ -3,6 +3,7 @@ package bypass
import ( import (
"bufio" "bufio"
"context" "context"
"errors"
"io" "io"
"net" "net"
"strings" "strings"
@@ -15,6 +16,10 @@ import (
"github.com/go-gost/x/internal/matcher" "github.com/go-gost/x/internal/matcher"
) )
var (
ErrBypass = errors.New("bypass")
)
type options struct { type options struct {
whitelist bool whitelist bool
matchers []string matchers []string
+25 -18
View File
@@ -10,6 +10,7 @@ import (
"github.com/go-gost/core/chain" "github.com/go-gost/core/chain"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
) )
@@ -48,7 +49,11 @@ func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Co
} }
r.record(ctx, recorder.RecorderServiceRouterDialAddress, []byte(host)) r.record(ctx, recorder.RecorderServiceRouterDialAddress, []byte(host))
conn, err = r.dial(ctx, network, address) log := r.options.Logger.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
conn, err = r.dial(ctx, network, address, log)
if err != nil { if err != nil {
r.record(ctx, recorder.RecorderServiceRouterDialAddressError, []byte(host)) r.record(ctx, recorder.RecorderServiceRouterDialAddressError, []byte(host))
return return
@@ -69,22 +74,19 @@ func (r *Router) record(ctx context.Context, name string, data []byte) error {
for _, rec := range r.options.Recorders { for _, rec := range r.options.Recorders {
if rec.Record == name { if rec.Record == name {
err := rec.Recorder.Record(ctx, data) return rec.Recorder.Record(ctx, data)
if err != nil {
r.options.Logger.Errorf("record %s: %v", name, err)
}
return err
} }
} }
return nil return nil
} }
func (r *Router) dial(ctx context.Context, network, address string) (conn net.Conn, err error) { func (r *Router) dial(ctx context.Context, network, address string, log logger.Logger) (conn net.Conn, err error) {
count := r.options.Retries + 1 count := r.options.Retries + 1
if count <= 0 { if count <= 0 {
count = 1 count = 1
} }
r.options.Logger.Debugf("dial %s/%s", address, network)
log.Debugf("dial %s/%s", address, network)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
ctx := ctx ctx := ctx
@@ -95,9 +97,9 @@ func (r *Router) dial(ctx context.Context, network, address string) (conn net.Co
} }
var ipAddr string var ipAddr string
ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, r.options.Logger) ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, log)
if err != nil { if err != nil {
r.options.Logger.Error(err) log.Error(err)
break break
} }
@@ -106,13 +108,13 @@ func (r *Router) dial(ctx context.Context, network, address string) (conn net.Co
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address)) route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
} }
if r.options.Logger.IsLevelEnabled(logger.DebugLevel) { if log.IsLevelEnabled(logger.DebugLevel) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
for _, node := range routePath(route) { for _, node := range routePath(route) {
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr) fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
} }
fmt.Fprintf(&buf, "%s", ipAddr) fmt.Fprintf(&buf, "%s", ipAddr)
r.options.Logger.Debugf("route(retry=%d) %s", i, buf.String()) log.Debugf("route(retry=%d) %s", i, buf.String())
} }
if route == nil { if route == nil {
@@ -122,12 +124,12 @@ func (r *Router) dial(ctx context.Context, network, address string) (conn net.Co
chain.InterfaceDialOption(r.options.IfceName), chain.InterfaceDialOption(r.options.IfceName),
chain.NetnsDialOption(r.options.Netns), chain.NetnsDialOption(r.options.Netns),
chain.SockOptsDialOption(r.options.SockOpts), chain.SockOptsDialOption(r.options.SockOpts),
chain.LoggerDialOption(r.options.Logger), chain.LoggerDialOption(log),
) )
if err == nil { if err == nil {
break break
} }
r.options.Logger.Errorf("route(retry=%d) %s", i, err) log.Errorf("route(retry=%d) %s", i, err)
} }
return return
@@ -138,7 +140,12 @@ func (r *Router) Bind(ctx context.Context, network, address string, opts ...chai
if count <= 0 { if count <= 0 {
count = 1 count = 1
} }
r.options.Logger.Debugf("bind on %s/%s", address, network)
log := r.options.Logger.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
log.Debugf("bind on %s/%s", address, network)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
ctx := ctx ctx := ctx
@@ -157,13 +164,13 @@ func (r *Router) Bind(ctx context.Context, network, address string, opts ...chai
} }
} }
if r.options.Logger.IsLevelEnabled(logger.DebugLevel) { if log.IsLevelEnabled(logger.DebugLevel) {
buf := bytes.Buffer{} buf := bytes.Buffer{}
for _, node := range routePath(route) { for _, node := range routePath(route) {
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr) fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
} }
fmt.Fprintf(&buf, "%s", address) fmt.Fprintf(&buf, "%s", address)
r.options.Logger.Debugf("route(retry=%d) %s", i, buf.String()) log.Debugf("route(retry=%d) %s", i, buf.String())
} }
if route == nil { if route == nil {
@@ -173,7 +180,7 @@ func (r *Router) Bind(ctx context.Context, network, address string, opts ...chai
if err == nil { if err == nil {
break break
} }
r.options.Logger.Errorf("route(retry=%d) %s", i, err) log.Errorf("route(retry=%d) %s", i, err)
} }
return return
+3 -2
View File
@@ -284,8 +284,9 @@ type TCPRecorder struct {
} }
type HTTPRecorder struct { type HTTPRecorder struct {
URL string `json:"url" yaml:"url"` URL string `yaml:"url" json:"url"`
Timeout time.Duration `json:"timeout"` Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
} }
type RedisRecorder struct { type RedisRecorder struct {
+9 -1
View File
@@ -2,6 +2,7 @@ package recorder
import ( import (
"crypto/tls" "crypto/tls"
"net/http"
"strings" "strings"
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
@@ -51,7 +52,14 @@ func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
} }
if cfg.HTTP != nil && cfg.HTTP.URL != "" { if cfg.HTTP != nil && cfg.HTTP.URL != "" {
return xrecorder.HTTPRecorder(cfg.HTTP.URL, xrecorder.TimeoutHTTPRecorderOption(cfg.HTTP.Timeout)) h := http.Header{}
for k, v := range cfg.HTTP.Header {
h.Add(k, v)
}
return xrecorder.HTTPRecorder(cfg.HTTP.URL,
xrecorder.TimeoutHTTPRecorderOption(cfg.HTTP.Timeout),
xrecorder.HeaderHTTPRecorderOption(h),
)
} }
if cfg.Redis != nil && if cfg.Redis != nil &&
+1
View File
@@ -278,6 +278,7 @@ func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)), handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)), handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)), handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
handler.RecordersOption(recorders...),
handler.LoggerOption(handlerLogger), handler.LoggerOption(handlerLogger),
handler.ServiceOption(cfg.Name), handler.ServiceOption(cfg.Name),
handler.NetnsOption(netnsIn), handler.NetnsOption(netnsIn),
+1 -1
View File
@@ -9,7 +9,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/gin-contrib/cors v1.6.0 github.com/gin-contrib/cors v1.6.0
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/go-gost/core v0.1.2 github.com/go-gost/core v0.1.3
github.com/go-gost/gosocks4 v0.0.1 github.com/go-gost/gosocks4 v0.0.1
github.com/go-gost/gosocks5 v0.4.2 github.com/go-gost/gosocks5 v0.4.2
github.com/go-gost/plugin v0.1.1 github.com/go-gost/plugin v0.1.1
+2 -2
View File
@@ -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-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 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-gost/core v0.1.2 h1:uWGLXEcfqkLYwvpGutXN2eIGXLOUGQX7L1QTe3NUDeA= github.com/go-gost/core v0.1.3 h1:Azq50of/OEv89/4THFbwNLidXK1fIwsMRVcgQUlvJug=
github.com/go-gost/core v0.1.2/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A= github.com/go-gost/core v0.1.3/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 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc= github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc= github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc=
+71 -21
View File
@@ -15,8 +15,12 @@ import (
"github.com/go-gost/core/hosts" "github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xhop "github.com/go-gost/x/hop" xhop "github.com/go-gost/x/hop"
resolver_util "github.com/go-gost/x/internal/util/resolver" resolver_util "github.com/go-gost/x/internal/util/resolver"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/go-gost/x/resolver/exchanger" "github.com/go-gost/x/resolver/exchanger"
"github.com/miekg/dns" "github.com/miekg/dns"
@@ -37,6 +41,7 @@ type dnsHandler struct {
hostMapper hosts.HostMapper hostMapper hosts.HostMapper
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -108,6 +113,13 @@ func (h *dnsHandler) Init(md md.Metadata) (err error) {
h.exchangers["default"] = ex h.exchangers["default"] = ex
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
@@ -116,24 +128,44 @@ func (h *dnsHandler) Forward(hop hop.Hop) {
h.hop = hop h.hop = hop
} }
func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: conn.LocalAddr().Network(),
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { 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.Warnf("recorder: %v", err)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
b := bufpool.Get(h.md.bufferSize) b := bufpool.Get(h.md.bufferSize)
@@ -145,7 +177,7 @@ func (h *dnsHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
return err return err
} }
reply, err := h.request(ctx, b[:n], log) reply, err := h.request(ctx, b[:n], ro, log)
if err != nil { if err != nil {
return err return err
} }
@@ -170,7 +202,7 @@ func (h *dnsHandler) checkRateLimit(addr net.Addr) bool {
return true return true
} }
func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger) ([]byte, error) { func (h *dnsHandler) request(ctx context.Context, msg []byte, ro *xrecorder.HandlerRecorderObject, log logger.Logger) ([]byte, error) {
mq := dns.Msg{} mq := dns.Msg{}
if err := mq.Unpack(msg); err != nil { if err := mq.Unpack(msg); err != nil {
log.Error(err) log.Error(err)
@@ -181,6 +213,14 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger)
return nil, errors.New("msg: empty question") return nil, errors.New("msg: empty question")
} }
ro.DNS = &xrecorder.DNSRecorderObject{
ID: int(mq.Id),
Name: mq.Question[0].Name,
Class: dns.Class(mq.Question[0].Qclass).String(),
Type: dns.Type(mq.Question[0].Qtype).String(),
Question: mq.String(),
}
resolver_util.AddSubnetOpt(&mq, h.md.clientIP) resolver_util.AddSubnetOpt(&mq, h.md.clientIP)
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
@@ -188,13 +228,15 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger)
} }
var mr *dns.Msg var mr *dns.Msg
if log.IsLevelEnabled(logger.TraceLevel) { defer func() {
defer func() { if mr != nil {
if mr != nil { if log.IsLevelEnabled(logger.TraceLevel) {
log.Trace(mr.String()) log.Trace(mr.String())
} }
}()
} ro.DNS.Answer = mr.String()
}
}()
if h.options.Bypass != nil && mq.Question[0].Qclass == dns.ClassINET { if h.options.Bypass != nil && mq.Question[0].Qclass == dns.ClassINET {
if h.options.Bypass.Contains(context.Background(), "udp", strings.Trim(mq.Question[0].Name, ".")) { if h.options.Bypass.Contains(context.Background(), "udp", strings.Trim(mq.Question[0].Name, ".")) {
@@ -214,10 +256,12 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger)
// only cache for single question message. // only cache for single question message.
if len(mq.Question) == 1 { if len(mq.Question) == 1 {
var ttl time.Duration var ttl time.Duration
mr, ttl = h.cache.Load(resolver_util.NewCacheKey(&mq.Question[0])) mr, ttl = h.cache.Load(ctx, resolver_util.NewCacheKey(&mq.Question[0]))
if mr != nil { if mr != nil {
mr.Id = mq.Id mr.Id = mq.Id
if int32(ttl.Seconds()) > 0 { if int32(ttl.Seconds()) > 0 {
ro.DNS.Cached = true
log.Debugf("message %d (cached): %s", mq.Id, mq.Question[0].String()) log.Debugf("message %d (cached): %s", mq.Id, mq.Question[0].String())
b := bufpool.Get(h.md.bufferSize) b := bufpool.Get(h.md.bufferSize)
return mr.PackBuffer(b) return mr.PackBuffer(b)
@@ -225,6 +269,12 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger)
} }
} }
ex := h.selectExchanger(ctx, strings.Trim(mq.Question[0].Name, "."))
if ex == nil {
return nil, fmt.Errorf("exchange not found for %s", mq.Question[0].Name)
}
ro.Host = ex.String()
if mr != nil && h.md.async { if mr != nil && h.md.async {
b := bufpool.Get(h.md.bufferSize) b := bufpool.Get(h.md.bufferSize)
reply, err := mr.PackBuffer(b) reply, err := mr.PackBuffer(b)
@@ -234,15 +284,21 @@ func (h *dnsHandler) request(ctx context.Context, msg []byte, log logger.Logger)
h.cache.RefreshTTL(resolver_util.NewCacheKey(&mq.Question[0])) h.cache.RefreshTTL(resolver_util.NewCacheKey(&mq.Question[0]))
log.Debugf("exchange message %d (async): %s", mq.Id, mq.Question[0].String()) log.Debugf("exchange message %d (async): %s", mq.Id, mq.Question[0].String())
go h.exchange(ctx, &mq) go h.exchange(ctx, ex, &mq)
return reply, nil return reply, nil
} }
log.Debugf("exchange message %d: %s", mq.Id, mq.Question[0].String()) log.Debugf("exchange message %d: %s", mq.Id, mq.Question[0].String())
return h.exchange(ctx, &mq) mr, err := h.exchange(ctx, ex, &mq)
if err != nil {
return nil, err
}
b := bufpool.Get(h.md.bufferSize)
return mr.PackBuffer(b)
} }
func (h *dnsHandler) exchange(ctx context.Context, mq *dns.Msg) ([]byte, error) { func (h *dnsHandler) exchange(ctx context.Context, ex exchanger.Exchanger, mq *dns.Msg) (*dns.Msg, error) {
b := bufpool.Get(h.md.bufferSize) b := bufpool.Get(h.md.bufferSize)
defer bufpool.Put(b) defer bufpool.Put(b)
@@ -251,12 +307,6 @@ func (h *dnsHandler) exchange(ctx context.Context, mq *dns.Msg) ([]byte, error)
return nil, err return nil, err
} }
ex := h.selectExchanger(ctx, strings.Trim(mq.Question[0].Name, "."))
if ex == nil {
err = fmt.Errorf("exchange not found for %s", mq.Question[0].Name)
return nil, err
}
reply, err := ex.Exchange(ctx, query) reply, err := ex.Exchange(ctx, query)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -268,10 +318,10 @@ func (h *dnsHandler) exchange(ctx context.Context, mq *dns.Msg) ([]byte, error)
} }
if len(mq.Question) == 1 { if len(mq.Question) == 1 {
key := resolver_util.NewCacheKey(&mq.Question[0]) key := resolver_util.NewCacheKey(&mq.Question[0])
h.cache.Store(key, mr, h.md.ttl) h.cache.Store(ctx, key, mr, h.md.ttl)
} }
return reply, nil return mr, nil
} }
// lookup host mapper // lookup host mapper
+91 -13
View File
@@ -21,12 +21,16 @@ import (
"github.com/go-gost/core/hop" "github.com/go-gost/core/hop"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config" "github.com/go-gost/x/config"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/util/forward" "github.com/go-gost/x/internal/util/forward"
tls_util "github.com/go-gost/x/internal/util/tls" tls_util "github.com/go-gost/x/internal/util/tls"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -37,9 +41,10 @@ func init() {
} }
type forwardHandler struct { type forwardHandler struct {
hop hop.Hop hop hop.Hop
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -58,6 +63,13 @@ func (h *forwardHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
@@ -66,24 +78,42 @@ func (h *forwardHandler) Forward(hop hop.Hop) {
h.hop = hop h.hop = hop
} }
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
network := "tcp" network := "tcp"
@@ -106,7 +136,11 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
} }
if protocol == forward.ProtoHTTP { if protocol == forward.ProtoHTTP {
h.handleHTTP(ctx, xio.NewReadWriteCloser(rw, rw, conn), conn.RemoteAddr(), log) ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro.Time = time.Time{}
h.handleHTTP(ctx, xio.NewReadWriteCloser(rw, rw, conn), conn.RemoteAddr(), ro2, log)
return nil return nil
} }
@@ -144,6 +178,9 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
} }
} }
ro.Network = network
ro.Host = addr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"host": host, "host": host,
"node": target.Name, "node": target.Name,
@@ -177,7 +214,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return nil return nil
} }
func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, remoteAddr net.Addr, log logger.Logger) (err error) { func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, remoteAddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
br := bufio.NewReader(rw) br := bufio.NewReader(rw)
for { for {
@@ -202,6 +239,27 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
log.Trace(string(dump)) log.Trace(string(dump))
} }
start := time.Now()
ro.Time = start
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header.Clone(),
}
defer func() {
if err != nil {
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
ro.Duration = time.Since(start)
ro.Err = err.Error()
ro.Record(ctx, h.recorder.Recorder)
}
}()
host := req.Host host := req.Host
if _, _, err := net.SplitHostPort(host); err != nil { if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80") host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
@@ -209,7 +267,8 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
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) log.Debugf("bypass: %s %s", host, req.RequestURI)
resp.StatusCode = http.StatusForbidden resp.StatusCode = http.StatusForbidden
return resp.Write(rw) resp.Write(rw)
return xbypass.ErrBypass
} }
if addr := getRealClientAddr(req, remoteAddr); addr != remoteAddr { if addr := getRealClientAddr(req, remoteAddr); addr != remoteAddr {
@@ -236,6 +295,8 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
return resp.Write(rw) return resp.Write(rw)
} }
ro.Host = target.Addr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"host": req.Host, "host": req.Host,
"node": target.Name, "node": target.Name,
@@ -305,14 +366,15 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
cc = tls.Client(cc, cfg) cc = tls.Client(cc, cfg)
} }
if err := req.Write(cc); err != nil { if err = req.Write(cc); err != nil {
cc.Close() cc.Close()
log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err) log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err)
return resp.Write(rw) resp.Write(rw)
return err
} }
if req.Header.Get("Upgrade") == "websocket" { if req.Header.Get("Upgrade") == "websocket" {
err := xnet.Transport(cc, xio.NewReadWriter(br, rw)) err = xnet.Transport(cc, xio.NewReadWriter(br, rw))
if err == nil { if err == nil {
err = io.EOF err = io.EOF
} }
@@ -322,12 +384,28 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
go func() { go func() {
defer cc.Close() defer cc.Close()
res, err := http.ReadResponse(bufio.NewReader(cc), req) var err error
var res *http.Response
defer func() {
ro.Duration = time.Since(start)
if err != nil {
ro.Err = err.Error()
}
if res != nil && ro.HTTP != nil {
ro.HTTP.ResponseHeader = res.Header
ro.HTTP.StatusCode = res.StatusCode
}
ro.Record(ctx, h.recorder.Recorder)
}()
res, err = http.ReadResponse(bufio.NewReader(cc), req)
if err != nil { if err != nil {
log.Warnf("read response from node %s(%s): %v", target.Name, target.Addr, err) log.Warnf("read response from node %s(%s): %v", target.Name, target.Addr, err)
resp.Write(rw) resp.Write(rw)
return return
} }
defer res.Body.Close()
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false) dump, _ := httputil.DumpResponse(res, false)
@@ -338,7 +416,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
defer rw.Close() defer rw.Close()
} }
if err := h.rewriteBody(res, bodyRewrites...); err != nil { if err = h.rewriteBody(res, bodyRewrites...); err != nil {
rw.Close() rw.Close()
log.Errorf("rewrite body: %v", err) log.Errorf("rewrite body: %v", err)
return return
+90 -12
View File
@@ -22,6 +22,8 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
mdata "github.com/go-gost/core/metadata" mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/core/metadata/util" mdutil "github.com/go-gost/core/metadata/util"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
"github.com/go-gost/x/config" "github.com/go-gost/x/config"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
@@ -29,6 +31,8 @@ import (
"github.com/go-gost/x/internal/net/proxyproto" "github.com/go-gost/x/internal/net/proxyproto"
"github.com/go-gost/x/internal/util/forward" "github.com/go-gost/x/internal/util/forward"
tls_util "github.com/go-gost/x/internal/util/tls" tls_util "github.com/go-gost/x/internal/util/tls"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -38,9 +42,10 @@ func init() {
} }
type forwardHandler struct { type forwardHandler struct {
hop hop.Hop hop hop.Hop
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -59,6 +64,13 @@ func (h *forwardHandler) Init(md mdata.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
@@ -67,24 +79,42 @@ func (h *forwardHandler) Forward(hop hop.Hop) {
h.hop = hop h.hop = hop
} }
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
network := "tcp" network := "tcp"
@@ -108,7 +138,11 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
} }
} }
if protocol == forward.ProtoHTTP { if protocol == forward.ProtoHTTP {
h.handleHTTP(ctx, xio.NewReadWriteCloser(rw, rw, conn), conn.RemoteAddr(), localAddr, log) ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro.Time = time.Time{}
h.handleHTTP(ctx, xio.NewReadWriteCloser(rw, rw, conn), conn.RemoteAddr(), localAddr, ro2, log)
return nil return nil
} }
@@ -143,6 +177,9 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
} }
} }
ro.Network = network
ro.Host = target.Addr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"host": host, "host": host,
"node": target.Name, "node": target.Name,
@@ -178,7 +215,7 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return nil return nil
} }
func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, remoteAddr net.Addr, localAddr net.Addr, log logger.Logger) (err error) { func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser, remoteAddr net.Addr, localAddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
br := bufio.NewReader(rw) br := bufio.NewReader(rw)
for { for {
@@ -202,6 +239,27 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
log.Trace(string(dump)) log.Trace(string(dump))
} }
start := time.Now()
ro.Time = start
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header.Clone(),
}
defer func() {
if err != nil {
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
ro.Duration = time.Since(start)
ro.Err = err.Error()
ro.Record(ctx, h.recorder.Recorder)
}
}()
host := req.Host host := req.Host
if _, _, err := net.SplitHostPort(host); err != nil { if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80") host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
@@ -209,7 +267,8 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
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) log.Debugf("bypass: %s %s", host, req.RequestURI)
resp.StatusCode = http.StatusForbidden resp.StatusCode = http.StatusForbidden
return resp.Write(rw) resp.Write(rw)
return xbypass.ErrBypass
} }
if addr := getRealClientAddr(req, remoteAddr); addr != remoteAddr { if addr := getRealClientAddr(req, remoteAddr); addr != remoteAddr {
@@ -236,6 +295,8 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
return resp.Write(rw) return resp.Write(rw)
} }
ro.Host = target.Addr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"host": req.Host, "host": req.Host,
"node": target.Name, "node": target.Name,
@@ -306,10 +367,11 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
cc = proxyproto.WrapClientConn(h.md.proxyProtocol, remoteAddr, localAddr, cc) cc = proxyproto.WrapClientConn(h.md.proxyProtocol, remoteAddr, localAddr, cc)
if err := req.Write(cc); err != nil { if err = req.Write(cc); err != nil {
cc.Close() cc.Close()
log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err) log.Warnf("send request to node %s(%s): %v", target.Name, target.Addr, err)
return resp.Write(rw) resp.Write(rw)
return err
} }
if req.Header.Get("Upgrade") == "websocket" { if req.Header.Get("Upgrade") == "websocket" {
@@ -323,12 +385,28 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
go func() { go func() {
defer cc.Close() defer cc.Close()
res, err := http.ReadResponse(bufio.NewReader(cc), req) var err error
var res *http.Response
defer func() {
ro.Duration = time.Since(start)
if err != nil {
ro.Err = err.Error()
}
if res != nil && ro.HTTP != nil {
ro.HTTP.ResponseHeader = res.Header
ro.HTTP.StatusCode = res.StatusCode
}
ro.Record(ctx, h.recorder.Recorder)
}()
res, err = http.ReadResponse(bufio.NewReader(cc), req)
if err != nil { if err != nil {
log.Warnf("read response from node %s(%s): %v", target.Name, target.Addr, err) log.Warnf("read response from node %s(%s): %v", target.Name, target.Addr, err)
resp.Write(rw) resp.Write(rw)
return return
} }
defer res.Body.Close()
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false) dump, _ := httputil.DumpResponse(res, false)
@@ -339,7 +417,7 @@ func (h *forwardHandler) handleHTTP(ctx context.Context, rw io.ReadWriteCloser,
defer rw.Close() defer rw.Close()
} }
if err := h.rewriteBody(res, bodyRewrites...); err != nil { if err = h.rewriteBody(res, bodyRewrites...); err != nil {
rw.Close() rw.Close()
log.Errorf("rewrite body: %v", err) log.Errorf("rewrite body: %v", err)
return return
+104 -23
View File
@@ -24,13 +24,17 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer/stats" "github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
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"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -39,11 +43,12 @@ func init() {
} }
type httpHandler struct { type httpHandler struct {
md metadata md metadata
options handler.Options options handler.Options
stats *stats_util.HandlerStats stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter limiter traffic.TrafficLimiter
cancel context.CancelFunc cancel context.CancelFunc
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -74,15 +79,29 @@ func (h *httpHandler) Init(md md.Metadata) error {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second) h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
// ctx = sx.ContextWithHash(ctx, &sx.Hash{})
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
@@ -90,13 +109,21 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
req, err := http.ReadRequest(bufio.NewReader(conn)) req, err := http.ReadRequest(bufio.NewReader(conn))
@@ -106,7 +133,7 @@ func (h *httpHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
} }
defer req.Body.Close() defer req.Body.Close()
return h.handleRequest(ctx, conn, req, log) return h.handleRequest(ctx, conn, req, ro, log)
} }
func (h *httpHandler) Close() error { func (h *httpHandler) Close() error {
@@ -116,7 +143,7 @@ func (h *httpHandler) Close() error {
return nil return nil
} }
func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request, log logger.Logger) error { func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) { if !req.URL.IsAbs() && govalidator.IsDNSName(req.Host) {
req.URL.Scheme = "http" req.URL.Scheme = "http"
} }
@@ -125,6 +152,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
if network != "udp" { if network != "udp" {
network = "tcp" network = "tcp"
} }
ro.Network = network
// Try to get the actual host. // Try to get the actual host.
// Compatible with GOST 2.x. // Compatible with GOST 2.x.
@@ -133,15 +161,13 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
req.Host = h req.Host = h
} }
} }
req.Header.Del("Gost-Target")
if v := req.Header.Get("X-Gost-Target"); v != "" { if v := req.Header.Get("X-Gost-Target"); v != "" {
if h, err := h.decodeServerName(v); err == nil { if h, err := h.decodeServerName(v); err == nil {
req.Host = h req.Host = h
} }
} }
req.Header.Del("X-Gost-Target")
ro.Host = req.Host
addr := req.Host addr := req.Host
if _, port, _ := net.SplitHostPort(addr); port == "" { if _, port, _ := net.SplitHostPort(addr); port == "" {
addr = net.JoinHostPort(strings.Trim(addr, "[]"), "80") addr = net.JoinHostPort(strings.Trim(addr, "[]"), "80")
@@ -150,8 +176,10 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
fields := map[string]any{ fields := map[string]any{
"dst": addr, "dst": addr,
} }
if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" { if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" {
fields["user"] = u fields["user"] = u
ro.Client = u
} }
log = log.WithFields(fields) log = log.WithFields(fields)
@@ -170,14 +198,26 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
if resp.Header == nil { if resp.Header == nil {
resp.Header = http.Header{} resp.Header = http.Header{}
} }
if resp.Header.Get("Proxy-Agent") == "" { if resp.Header.Get("Proxy-Agent") == "" {
resp.Header.Set("Proxy-Agent", h.md.proxyAgent) resp.Header.Set("Proxy-Agent", h.md.proxyAgent)
} }
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header.Clone(),
}
defer func() {
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
}()
clientID, ok := h.authenticate(ctx, conn, req, resp, log) clientID, ok := h.authenticate(ctx, conn, req, resp, log)
if !ok { if !ok {
return nil return errors.New("authenication failed")
} }
ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID))
@@ -189,8 +229,8 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
log.Trace(string(dump)) log.Trace(string(dump))
} }
log.Debug("bypass: ", addr) log.Debug("bypass: ", addr)
resp.Write(conn)
return resp.Write(conn) return xbypass.ErrBypass
} }
if network == "udp" { if network == "udp" {
@@ -209,8 +249,6 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return resp.Write(conn) return resp.Write(conn)
} }
req.Header.Del("Proxy-Authorization")
switch h.md.hash { switch h.md.hash {
case "host": case "host":
ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr}) ctx = ctxvalue.ContextWithHash(ctx, &ctxvalue.Hash{Source: addr})
@@ -249,7 +287,11 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
} }
if req.Method != http.MethodConnect { if req.Method != http.MethodConnect {
return h.handleProxy(xio.NewReadWriteCloser(rw, rw, conn), cc, req, log) ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro.Time = time.Time{}
return h.handleProxy(ctx, xio.NewReadWriteCloser(rw, rw, conn), cc, req, ro2, log)
} }
resp.StatusCode = http.StatusOK resp.StatusCode = http.StatusOK
@@ -274,7 +316,7 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt
return nil return nil
} }
func (h *httpHandler) handleProxy(rw io.ReadWriteCloser, cc io.ReadWriter, req *http.Request, log logger.Logger) (err error) { func (h *httpHandler) handleProxy(ctx context.Context, rw io.ReadWriteCloser, cc io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
roundTrip := func(req *http.Request) error { roundTrip := func(req *http.Request) error {
if req == nil { if req == nil {
return nil return nil
@@ -287,6 +329,21 @@ func (h *httpHandler) handleProxy(rw io.ReadWriteCloser, cc io.ReadWriter, req *
StatusCode: http.StatusServiceUnavailable, StatusCode: http.StatusServiceUnavailable,
} }
start := time.Now()
ro.Time = start
if ro.HTTP != nil {
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header.Clone(),
StatusCode: resp.StatusCode,
}
}
// HTTP/1.0 // HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 { if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" { if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" {
@@ -296,20 +353,44 @@ func (h *httpHandler) handleProxy(rw io.ReadWriteCloser, cc io.ReadWriter, req *
} }
} }
req.Header.Del("Proxy-Authorization")
req.Header.Del("Proxy-Connection") req.Header.Del("Proxy-Connection")
req.Header.Del("Gost-Target")
req.Header.Del("X-Gost-Target")
if err = req.Write(cc); err != nil { if err = req.Write(cc); err != nil {
resp.Write(rw) resp.Write(rw)
ro.Duration = time.Since(start)
ro.Err = err.Error()
ro.Record(ctx, h.recorder.Recorder)
return err return err
} }
go func() { go func() {
res, err := http.ReadResponse(bufio.NewReader(cc), req) var err error
var res *http.Response
defer func() {
ro.Duration = time.Since(start)
if err != nil {
ro.Err = err.Error()
}
if res != nil && ro.HTTP != nil {
ro.HTTP.ResponseHeader = res.Header
ro.HTTP.StatusCode = res.StatusCode
}
ro.Record(ctx, h.recorder.Recorder)
}()
res, err = http.ReadResponse(bufio.NewReader(cc), req)
if err != nil { if err != nil {
h.options.Logger.Errorf("read response: %v", err) h.options.Logger.Errorf("read response: %v", err)
resp.Write(rw) resp.Write(rw)
return return
} }
defer res.Body.Close()
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false) dump, _ := httputil.DumpResponse(res, false)
+67 -17
View File
@@ -24,13 +24,17 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer/stats" "github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
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"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -39,11 +43,12 @@ func init() {
} }
type http2Handler struct { type http2Handler struct {
md metadata md metadata
options handler.Options options handler.Options
stats *stats_util.HandlerStats stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter limiter traffic.TrafficLimiter
cancel context.CancelFunc cancel context.CancelFunc
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -74,31 +79,55 @@ func (h *http2Handler) Init(md md.Metadata) error {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second) h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
func (h *http2Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *http2Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Network: "tcp",
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
v, ok := conn.(md.Metadatable) v, ok := conn.(md.Metadatable)
if !ok || v == nil { if !ok || v == nil {
err := errors.New("wrong connection type") err = errors.New("wrong connection type")
log.Error(err) log.Error(err)
return err return err
} }
@@ -107,6 +136,7 @@ func (h *http2Handler) Handle(ctx context.Context, conn net.Conn, opts ...handle
return h.roundTrip(ctx, return h.roundTrip(ctx,
md.Get("w").(http.ResponseWriter), md.Get("w").(http.ResponseWriter),
md.Get("r").(*http.Request), md.Get("r").(*http.Request),
ro,
log, log,
) )
} }
@@ -121,7 +151,7 @@ func (h *http2Handler) Close() error {
// NOTE: there is an issue (golang/go#43989) will cause the client hangs // NOTE: there is an issue (golang/go#43989) will cause the client hangs
// when server returns an non-200 status code, // when server returns an non-200 status code,
// May be fixed in go1.18. // May be fixed in go1.18.
func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req *http.Request, log logger.Logger) error { func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
// Try to get the actual host. // Try to get the actual host.
// Compatible with GOST 2.x. // Compatible with GOST 2.x.
if v := req.Header.Get("Gost-Target"); v != "" { if v := req.Header.Get("Gost-Target"); v != "" {
@@ -129,15 +159,13 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
req.Host = h req.Host = h
} }
} }
req.Header.Del("Gost-Target")
if v := req.Header.Get("X-Gost-Target"); v != "" { if v := req.Header.Get("X-Gost-Target"); v != "" {
if h, err := h.decodeServerName(v); err == nil { if h, err := h.decodeServerName(v); err == nil {
req.Host = h req.Host = h
} }
} }
req.Header.Del("X-Gost-Target")
ro.Host = req.Host
addr := req.Host addr := req.Host
if _, port, _ := net.SplitHostPort(addr); port == "" { if _, port, _ := net.SplitHostPort(addr); port == "" {
addr = net.JoinHostPort(strings.Trim(addr, "[]"), "80") addr = net.JoinHostPort(strings.Trim(addr, "[]"), "80")
@@ -148,6 +176,7 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
} }
if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" { if u, _, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")); u != "" {
fields["user"] = u fields["user"] = u
ro.Client = u
} }
log = log.WithFields(fields) log = log.WithFields(fields)
@@ -164,25 +193,41 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
resp := &http.Response{ resp := &http.Response{
ProtoMajor: 2, ProtoMajor: 2,
ProtoMinor: 0, ProtoMinor: 0,
Header: http.Header{}, Header: w.Header(),
Body: io.NopCloser(bytes.NewReader([]byte{})), Body: io.NopCloser(bytes.NewReader([]byte{})),
} }
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header.Clone(),
}
defer func() {
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
}()
clientID, ok := h.authenticate(ctx, w, req, resp, log) clientID, ok := h.authenticate(ctx, w, req, resp, log)
if !ok { if !ok {
return nil return errors.New("authenication failed")
} }
ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID))
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", addr) { if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", addr) {
w.WriteHeader(http.StatusForbidden) resp.StatusCode = http.StatusForbidden
w.WriteHeader(resp.StatusCode)
log.Debug("bypass: ", addr) log.Debug("bypass: ", addr)
return nil return xbypass.ErrBypass
} }
// delete the proxy related headers. // delete the proxy related headers.
req.Header.Del("Proxy-Authorization") req.Header.Del("Proxy-Authorization")
req.Header.Del("Proxy-Connection") req.Header.Del("Proxy-Connection")
req.Header.Del("Gost-Target")
req.Header.Del("X-Gost-Target")
switch h.md.hash { switch h.md.hash {
case "host": case "host":
@@ -192,12 +237,14 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
cc, err := h.options.Router.Dial(ctx, "tcp", addr) cc, err := h.options.Router.Dial(ctx, "tcp", addr)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
w.WriteHeader(http.StatusServiceUnavailable) resp.StatusCode = http.StatusServiceUnavailable
w.WriteHeader(resp.StatusCode)
return err return err
} }
defer cc.Close() defer cc.Close()
if req.Method == http.MethodConnect { if req.Method == http.MethodConnect {
resp.StatusCode = http.StatusOK
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok { if fw, ok := w.(http.Flusher); ok {
fw.Flush() fw.Flush()
@@ -209,6 +256,7 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
conn, _, err := hj.Hijack() conn, _, err := hj.Hijack()
if err != nil { if err != nil {
log.Error(err) log.Error(err)
resp.StatusCode = http.StatusInternalServerError
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return err return err
} }
@@ -253,6 +301,8 @@ func (h *http2Handler) roundTrip(ctx context.Context, w http.ResponseWriter, req
} }
// TODO: forward request // TODO: forward request
resp.StatusCode = http.StatusBadRequest
w.WriteHeader(resp.StatusCode)
return nil return nil
} }
+1
View File
@@ -60,6 +60,7 @@ func (h *http3Handler) Handle(ctx context.Context, conn net.Conn, opts ...handle
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
+71 -19
View File
@@ -18,9 +18,14 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
dissector "github.com/go-gost/tls-dissector" dissector "github.com/go-gost/tls-dissector"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -31,8 +36,9 @@ func init() {
} }
type redirectHandler struct { type redirectHandler struct {
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -51,6 +57,13 @@ func (h *redirectHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
@@ -58,20 +71,39 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "tcp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
var dstAddr net.Addr var dstAddr net.Addr
@@ -86,6 +118,8 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
} }
} }
ro.Host = 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()),
}) })
@@ -111,7 +145,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
// try to sniff HTTP traffic // try to sniff HTTP traffic
if isHTTP(string(hdr[:])) { if isHTTP(string(hdr[:])) {
return h.handleHTTP(ctx, rw, conn.RemoteAddr(), dstAddr, log) return h.handleHTTP(ctx, rw, conn.RemoteAddr(), dstAddr, ro, log)
} }
} }
@@ -119,7 +153,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
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) log.Debug("bypass: ", dstAddr)
return nil return xbypass.ErrBypass
} }
cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String()) cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String())
@@ -139,17 +173,27 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
return nil return nil
} }
func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr, dstAddr net.Addr, log logger.Logger) error { 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)) req, err := http.ReadRequest(bufio.NewReader(rw))
if err != nil { if err != nil {
return err return err
} }
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header,
}
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false) dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump)) log.Trace(string(dump))
} }
ro.Host = req.Host
host := req.Host host := req.Host
if _, _, err := net.SplitHostPort(host); err != nil { if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80") host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
@@ -160,12 +204,15 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd
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) log.Debugf("bypass: %s %s", host, req.RequestURI)
return nil return xbypass.ErrBypass
} }
cc, err := h.options.Router.Dial(ctx, "tcp", host) cc, err := h.options.Router.Dial(ctx, "tcp", host)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
if !h.md.sniffingFallback {
return err
}
} }
if cc == nil { if cc == nil {
@@ -190,23 +237,28 @@ func (h *redirectHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, radd
return err return err
} }
var rw2 io.ReadWriter = cc br := bufio.NewReader(cc)
if log.IsLevelEnabled(logger.TraceLevel) { resp, err := http.ReadResponse(br, req)
var buf bytes.Buffer if err != nil {
resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(cc, &buf)), req) log.Error(err)
if err != nil { return err
log.Error(err) }
return err defer resp.Body.Close()
}
defer resp.Body.Close()
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false) dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump)) log.Trace(string(dump))
rw2 = xio.NewReadWriter(io.MultiReader(&buf, cc), cc)
} }
netpkg.Transport(rw, rw2) if err := resp.Write(rw); err != nil {
log.Error(err)
return err
}
netpkg.Transport(rw, xio.NewReadWriter(br, cc))
return nil return nil
} }
+36 -5
View File
@@ -8,7 +8,12 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -17,8 +22,9 @@ func init() {
} }
type redirectHandler struct { type redirectHandler struct {
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -37,30 +43,55 @@ func (h *redirectHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
dstAddr := conn.LocalAddr() dstAddr := conn.LocalAddr()
ro.Network = dstAddr.Network()
ro.Host = 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()),
@@ -70,7 +101,7 @@ func (h *redirectHandler) Handle(ctx context.Context, conn net.Conn, opts ...han
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) log.Debug("bypass: ", dstAddr)
return nil return xbypass.ErrBypass
} }
cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String()) cc, err := h.options.Router.Dial(ctx, dstAddr.Network(), dstAddr.String())
+3 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/observer/stats" "github.com/go-gost/core/observer/stats"
"github.com/go-gost/relay" "github.com/go-gost/relay"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
serial "github.com/go-gost/x/internal/util/serial" serial "github.com/go-gost/x/internal/util/serial"
@@ -49,8 +50,8 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, address) { if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, network, address) {
log.Debug("bypass: ", address) log.Debug("bypass: ", address)
resp.Status = relay.StatusForbidden resp.Status = relay.StatusForbidden
_, err = resp.WriteTo(conn) resp.WriteTo(conn)
return return xbypass.ErrBypass
} }
switch h.md.hash { switch h.md.hash {
+37 -14
View File
@@ -11,10 +11,13 @@ import (
"github.com/go-gost/core/hop" "github.com/go-gost/core/hop"
"github.com/go-gost/core/limiter/traffic" "github.com/go-gost/core/limiter/traffic"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/relay" "github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -22,7 +25,6 @@ var (
ErrBadVersion = errors.New("relay: bad version") ErrBadVersion = errors.New("relay: bad version")
ErrUnknownCmd = errors.New("relay: unknown command") ErrUnknownCmd = errors.New("relay: unknown command")
ErrUnauthorized = errors.New("relay: unauthorized") ErrUnauthorized = errors.New("relay: unauthorized")
ErrRateLimit = errors.New("relay: rate limiting exceeded")
) )
func init() { func init() {
@@ -30,12 +32,13 @@ func init() {
} }
type relayHandler struct { type relayHandler struct {
hop hop.Hop hop hop.Hop
md metadata md metadata
options handler.Options options handler.Options
stats *stats_util.HandlerStats stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter limiter traffic.TrafficLimiter
cancel context.CancelFunc cancel context.CancelFunc
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -66,6 +69,13 @@ func (h *relayHandler) Init(md md.Metadata) (err error) {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second) h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
@@ -75,25 +85,40 @@ func (h *relayHandler) Forward(hop hop.Hop) {
} }
func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) { func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil { if err != nil {
conn.Close() ro.Err = err.Error()
} }
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return ErrRateLimit return rate_limiter.ErrRateLimit
} }
if h.md.readTimeout > 0 { if h.md.readTimeout > 0 {
@@ -139,6 +164,7 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle
} }
if user != "" { if user != "" {
ro.Client = user
log = log.WithFields(map[string]any{"user": user}) log = log.WithFields(map[string]any{"user": user})
} }
@@ -156,21 +182,18 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handle
if (req.Cmd & relay.FUDP) == relay.FUDP { if (req.Cmd & relay.FUDP) == relay.FUDP {
network = "udp" network = "udp"
} }
ro.Network = network
ro.Host = address
if h.hop != nil { if h.hop != nil {
defer conn.Close()
// forward mode // forward mode
return h.handleForward(ctx, conn, network, log) return h.handleForward(ctx, conn, network, log)
} }
switch req.Cmd & relay.CmdMask { switch req.Cmd & relay.CmdMask {
case 0, relay.CmdConnect: case 0, relay.CmdConnect:
defer conn.Close()
return h.handleConnect(ctx, conn, network, address, log) return h.handleConnect(ctx, conn, network, address, log)
case relay.CmdBind: case relay.CmdBind:
defer conn.Close()
return h.handleBind(ctx, conn, network, address, log) return h.handleBind(ctx, conn, network, address, log)
default: default:
resp.Status = relay.StatusBadRequest resp.Status = relay.StatusBadRequest
+6 -6
View File
@@ -13,6 +13,7 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
serial "github.com/go-gost/x/internal/util/serial" serial "github.com/go-gost/x/internal/util/serial"
xrecorder "github.com/go-gost/x/recorder" xrecorder "github.com/go-gost/x/recorder"
@@ -46,12 +47,10 @@ func (h *serialHandler) Init(md md.Metadata) (err error) {
return return
} }
if opts := h.options.Router.Options(); opts != nil { for _, ro := range h.options.Recorders {
for _, ro := range opts.Recorders { if ro.Record == xrecorder.RecorderServiceHandlerSerial {
if ro.Record == xrecorder.RecorderServiceHandlerSerial { h.recorder = ro
h.recorder = ro break
break
}
} }
} }
@@ -71,6 +70,7 @@ func (h *serialHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
conn = &recorderConn{ conn = &recorderConn{
+70 -22
View File
@@ -20,10 +20,14 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
dissector "github.com/go-gost/tls-dissector" dissector "github.com/go-gost/tls-dissector"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io" xio "github.com/go-gost/x/internal/io"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -32,8 +36,9 @@ func init() {
} }
type sniHandler struct { type sniHandler struct {
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -54,27 +59,51 @@ func (h *sniHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Network: "tcp",
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
var hdr [dissector.RecordHeaderLen]byte var hdr [dissector.RecordHeaderLen]byte
@@ -88,17 +117,26 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
tlsVersion := binary.BigEndian.Uint16(hdr[1:3]) tlsVersion := binary.BigEndian.Uint16(hdr[1:3])
if hdr[0] == dissector.Handshake && if hdr[0] == dissector.Handshake &&
(tlsVersion >= tls.VersionTLS10 && tlsVersion <= tls.VersionTLS13) { (tlsVersion >= tls.VersionTLS10 && tlsVersion <= tls.VersionTLS13) {
return h.handleHTTPS(ctx, rw, conn.RemoteAddr(), log) return h.handleHTTPS(ctx, rw, conn.RemoteAddr(), ro, log)
} }
return h.handleHTTP(ctx, rw, conn.RemoteAddr(), log) return h.handleHTTP(ctx, rw, conn.RemoteAddr(), ro, log)
} }
func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net.Addr, log logger.Logger) error { func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
req, err := http.ReadRequest(bufio.NewReader(rw)) req, err := http.ReadRequest(bufio.NewReader(rw))
if err != nil { if err != nil {
return err return err
} }
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
RequestHeader: req.Header,
}
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false) dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump)) log.Trace(string(dump))
@@ -108,13 +146,16 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net
if _, _, err := net.SplitHostPort(host); err != nil { if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80") host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
} }
ro.Host = req.Host
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"host": host, "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) log.Debugf("bypass: %s %s", host, req.RequestURI)
return nil return xbypass.ErrBypass
} }
switch h.md.hash { switch h.md.hash {
@@ -142,28 +183,33 @@ func (h *sniHandler) handleHTTP(ctx context.Context, rw io.ReadWriter, raddr net
return err return err
} }
var rw2 io.ReadWriter = cc br := bufio.NewReader(cc)
if log.IsLevelEnabled(logger.TraceLevel) { resp, err := http.ReadResponse(br, req)
var buf bytes.Buffer if err != nil {
resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(cc, &buf)), req) log.Error(err)
if err != nil { return err
log.Error(err) }
return err defer resp.Body.Close()
}
defer resp.Body.Close()
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false) dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump)) log.Trace(string(dump))
rw2 = xio.NewReadWriter(io.MultiReader(&buf, cc), cc)
} }
netpkg.Transport(rw, rw2) if err := resp.Write(rw); err != nil {
log.Error(err)
return err
}
netpkg.Transport(rw, xio.NewReadWriter(br, cc))
return nil return nil
} }
func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr net.Addr, log logger.Logger) error { func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr net.Addr, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
host, err := h.decodeHost(io.TeeReader(rw, buf)) host, err := h.decodeHost(io.TeeReader(rw, buf))
if err != nil { if err != nil {
@@ -175,6 +221,8 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne
host = net.JoinHostPort(strings.Trim(host, "[]"), "443") host = net.JoinHostPort(strings.Trim(host, "[]"), "443")
} }
ro.Host = host
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"dst": host, "dst": host,
}) })
@@ -182,7 +230,7 @@ func (h *sniHandler) handleHTTPS(ctx context.Context, rw io.ReadWriter, raddr ne
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) log.Debug("bypass: ", host)
return nil return xbypass.ErrBypass
} }
switch h.md.hash { switch h.md.hash {
+36 -7
View File
@@ -12,13 +12,16 @@ import (
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer/stats" "github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
"github.com/go-gost/gosocks4" "github.com/go-gost/gosocks4"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
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"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -33,11 +36,12 @@ func init() {
} }
type socks4Handler struct { type socks4Handler struct {
md metadata md metadata
options handler.Options options handler.Options
stats *stats_util.HandlerStats stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter limiter traffic.TrafficLimiter
cancel context.CancelFunc cancel context.CancelFunc
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -68,28 +72,51 @@ func (h *socks4Handler) Init(md md.Metadata) (err error) {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second) h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "tcp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
if h.md.readTimeout > 0 { if h.md.readTimeout > 0 {
@@ -101,6 +128,8 @@ func (h *socks4Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
log.Error(err) log.Error(err)
return err return err
} }
ro.Host = req.Addr.String()
log.Trace(req) log.Trace(req)
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
+33 -2
View File
@@ -9,11 +9,14 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
"github.com/go-gost/core/limiter/traffic" "github.com/go-gost/core/limiter/traffic"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/gosocks5" "github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
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/socks" "github.com/go-gost/x/internal/util/socks"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -33,6 +36,7 @@ type socks5Handler struct {
stats *stats_util.HandlerStats stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter limiter traffic.TrafficLimiter
cancel context.CancelFunc cancel context.CancelFunc
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -70,28 +74,51 @@ func (h *socks5Handler) Init(md md.Metadata) (err error) {
h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second) h.limiter = limiter_util.NewCachedTrafficLimiter(limiter, 30*time.Second, 60*time.Second)
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "tcp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
if h.md.readTimeout > 0 { if h.md.readTimeout > 0 {
@@ -109,12 +136,14 @@ func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
if clientID := sc.ID(); clientID != "" { if clientID := sc.ID(); clientID != "" {
ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID)) ctx = ctxvalue.ContextWithClientID(ctx, ctxvalue.ClientID(clientID))
log = log.WithFields(map[string]any{"user": clientID}) log = log.WithFields(map[string]any{"user": clientID})
ro.Client = clientID
} }
conn = sc conn = sc
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
address := req.Addr.String() address := req.Addr.String()
ro.Host = address
switch req.Cmd { switch req.Cmd {
case gosocks5.CmdConnect: case gosocks5.CmdConnect:
@@ -124,8 +153,10 @@ func (h *socks5Handler) Handle(ctx context.Context, conn net.Conn, opts ...handl
case socks.CmdMuxBind: case socks.CmdMuxBind:
return h.handleMuxBind(ctx, conn, "tcp", address, log) return h.handleMuxBind(ctx, conn, "tcp", address, log)
case gosocks5.CmdUdp: case gosocks5.CmdUdp:
ro.Network = "udp"
return h.handleUDP(ctx, conn, log) return h.handleUDP(ctx, conn, log)
case socks.CmdUDPTun: case socks.CmdUDPTun:
ro.Network = "udp"
return h.handleUDPTun(ctx, conn, "udp", address, log) return h.handleUDPTun(ctx, conn, "udp", address, log)
default: default:
err = ErrUnknownCmd err = ErrUnknownCmd
+36 -5
View File
@@ -8,10 +8,13 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/gosocks5" "github.com/go-gost/gosocks5"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
"github.com/go-gost/x/internal/util/ss" "github.com/go-gost/x/internal/util/ss"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core" "github.com/shadowsocks/go-shadowsocks2/core"
) )
@@ -21,9 +24,10 @@ func init() {
} }
type ssHandler struct { type ssHandler struct {
cipher core.Cipher cipher core.Cipher
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -50,27 +54,53 @@ func (h *ssHandler) Init(md md.Metadata) (err error) {
} }
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "tcp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
if h.cipher != nil { if h.cipher != nil {
@@ -87,6 +117,7 @@ func (h *ssHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.H
io.Copy(io.Discard, conn) io.Copy(io.Discard, conn)
return err return err
} }
ro.Host = addr.String()
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"dst": addr.String(), "dst": addr.String(),
+42 -9
View File
@@ -10,8 +10,12 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/go-gost/x/internal/util/relay" "github.com/go-gost/x/internal/util/relay"
"github.com/go-gost/x/internal/util/ss" "github.com/go-gost/x/internal/util/ss"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core" "github.com/shadowsocks/go-shadowsocks2/core"
) )
@@ -21,9 +25,10 @@ func init() {
} }
type ssuHandler struct { type ssuHandler struct {
cipher core.Cipher cipher core.Cipher
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -51,27 +56,51 @@ func (h *ssuHandler) Init(md md.Metadata) (err error) {
} }
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now() start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "udp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() { defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(start), "duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr()) }).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
pc, ok := conn.(net.PacketConn) pc, ok := conn.(net.PacketConn)
@@ -106,14 +135,14 @@ func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
t := time.Now() t := time.Now()
log.Infof("%s <-> %s", conn.LocalAddr(), cc.LocalAddr()) log.Infof("%s <-> %s", conn.LocalAddr(), cc.LocalAddr())
h.relayPacket(pc, cc, log) h.relayPacket(ctx, pc, cc, ro, log)
log.WithFields(map[string]any{"duration": time.Since(t)}). log.WithFields(map[string]any{"duration": time.Since(t)}).
Infof("%s >-< %s", conn.LocalAddr(), cc.LocalAddr()) Infof("%s >-< %s", conn.LocalAddr(), cc.LocalAddr())
return nil return nil
} }
func (h *ssuHandler) relayPacket(pc1, pc2 net.PacketConn, log logger.Logger) (err error) { func (h *ssuHandler) relayPacket(ctx context.Context, pc1, pc2 net.PacketConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
bufSize := h.md.bufferSize bufSize := h.md.bufferSize
errc := make(chan error, 2) errc := make(chan error, 2)
@@ -128,7 +157,11 @@ func (h *ssuHandler) relayPacket(pc1, pc2 net.PacketConn, log logger.Logger) (er
return err return err
} }
if h.options.Bypass != nil && h.options.Bypass.Contains(context.Background(), addr.Network(), addr.String()) { if ro.Host == "" {
ro.Host = addr.String()
}
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, addr.Network(), addr.String()) {
log.Warn("bypass: ", addr) log.Warn("bypass: ", addr)
return nil return nil
} }
@@ -160,7 +193,7 @@ func (h *ssuHandler) relayPacket(pc1, pc2 net.PacketConn, log logger.Logger) (er
return err return err
} }
if h.options.Bypass != nil && h.options.Bypass.Contains(context.Background(), raddr.Network(), raddr.String()) { if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, raddr.Network(), raddr.String()) {
log.Warn("bypass: ", raddr) log.Warn("bypass: ", raddr)
return nil return nil
} }
+51 -9
View File
@@ -12,8 +12,13 @@ import (
"github.com/go-gost/core/handler" "github.com/go-gost/core/handler"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx"
netpkg "github.com/go-gost/x/internal/net" netpkg "github.com/go-gost/x/internal/net"
sshd_util "github.com/go-gost/x/internal/util/sshd" sshd_util "github.com/go-gost/x/internal/util/sshd"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@@ -28,8 +33,9 @@ func init() {
} }
type forwardHandler struct { type forwardHandler struct {
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -48,26 +54,60 @@ func (h *forwardHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return nil return nil
} }
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "tcp",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{ log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
if !ro.Time.IsZero() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
}
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return nil return rate_limiter.ErrRateLimit
} }
switch cc := conn.(type) { switch cc := conn.(type) {
case *sshd_util.DirectForwardConn: case *sshd_util.DirectForwardConn:
return h.handleDirectForward(ctx, cc, log) return h.handleDirectForward(ctx, cc, ro, log)
case *sshd_util.RemoteForwardConn: case *sshd_util.RemoteForwardConn:
return h.handleRemoteForward(ctx, cc, log) return h.handleRemoteForward(ctx, cc, ro, log)
default: default:
err := errors.New("sshd: wrong connection type") err := errors.New("sshd: wrong connection type")
log.Error(err) log.Error(err)
@@ -75,9 +115,10 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
} }
} }
func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_util.DirectForwardConn, log logger.Logger) error { func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_util.DirectForwardConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
targetAddr := conn.DstAddr() targetAddr := conn.DstAddr()
ro.Host = targetAddr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", targetAddr, "tcp"), "dst": fmt.Sprintf("%s/%s", targetAddr, "tcp"),
"cmd": "connect", "cmd": "connect",
@@ -87,7 +128,7 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti
if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", targetAddr) { if h.options.Bypass != nil && h.options.Bypass.Contains(ctx, "tcp", targetAddr) {
log.Debugf("bypass %s", targetAddr) log.Debugf("bypass %s", targetAddr)
return nil return xbypass.ErrBypass
} }
cc, err := h.options.Router.Dial(ctx, "tcp", targetAddr) cc, err := h.options.Router.Dial(ctx, "tcp", targetAddr)
@@ -106,7 +147,7 @@ func (h *forwardHandler) handleDirectForward(ctx context.Context, conn *sshd_uti
return nil return nil
} }
func (h *forwardHandler) handleRemoteForward(ctx context.Context, conn *sshd_util.RemoteForwardConn, log logger.Logger) error { func (h *forwardHandler) handleRemoteForward(ctx context.Context, conn *sshd_util.RemoteForwardConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
req := conn.Request() req := conn.Request()
t := tcpipForward{} t := tcpipForward{}
@@ -117,6 +158,7 @@ func (h *forwardHandler) handleRemoteForward(ctx context.Context, conn *sshd_uti
network := "tcp" network := "tcp"
addr := net.JoinHostPort(t.Host, strconv.Itoa(int(t.Port))) addr := net.JoinHostPort(t.Host, strconv.Itoa(int(t.Port)))
ro.Host = addr
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"dst": fmt.Sprintf("%s/%s", addr, network), "dst": fmt.Sprintf("%s/%s", addr, network),
+2
View File
@@ -21,6 +21,7 @@ import (
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/shadowsocks/go-shadowsocks2/core" "github.com/shadowsocks/go-shadowsocks2/core"
"github.com/shadowsocks/go-shadowsocks2/shadowaead" "github.com/shadowsocks/go-shadowsocks2/shadowaead"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/songgao/water/waterutil" "github.com/songgao/water/waterutil"
) )
@@ -87,6 +88,7 @@ func (h *tapHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
+2
View File
@@ -15,6 +15,7 @@ import (
tun_util "github.com/go-gost/x/internal/util/tun" tun_util "github.com/go-gost/x/internal/util/tun"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
"github.com/songgao/water/waterutil" "github.com/songgao/water/waterutil"
ctxvalue "github.com/go-gost/x/ctx"
) )
var ( var (
@@ -74,6 +75,7 @@ func (h *tunHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
+74 -15
View File
@@ -17,6 +17,7 @@ import (
"github.com/go-gost/core/listener" "github.com/go-gost/core/listener"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/sd" "github.com/go-gost/core/sd"
"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"
@@ -25,14 +26,17 @@ import (
"github.com/go-gost/x/internal/net/proxyproto" "github.com/go-gost/x/internal/net/proxyproto"
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"
xrecorder "github.com/go-gost/x/recorder"
) )
type entrypoint struct { type entrypoint struct {
node string node string
pool *ConnectorPool service string
ingress ingress.Ingress pool *ConnectorPool
sd sd.SD ingress ingress.Ingress
log logger.Logger sd sd.SD
log logger.Logger
recorder recorder.RecorderObject
} }
func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error { func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
@@ -77,6 +81,40 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
return err return err
} }
start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Node: ep.node,
Service: ep.service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Network: "tcp",
Host: req.Host,
Time: start,
HTTP: &xrecorder.HTTPRecorderObject{
Host: req.Host,
Method: req.Method,
Proto: req.Proto,
Scheme: req.URL.Scheme,
URI: req.RequestURI,
RequestHeader: req.Header,
},
}
defer func() {
if err != nil {
d := time.Since(start)
log.WithFields(map[string]any{
"duration": d,
}).Debugf("%s >-< %s", conn.RemoteAddr(), req.Host)
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.ResponseHeader = resp.Header
ro.Duration = d
ro.Err = err.Error()
ro.Record(ctx, ep.recorder.Recorder)
}
}()
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false) dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump)) log.Trace(string(dump))
@@ -92,16 +130,21 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
} }
} }
if tunnelID.IsZero() { if tunnelID.IsZero() {
err := fmt.Errorf("no route to host %s", req.Host) err = fmt.Errorf("no route to host %s", req.Host)
log.Error(err) log.Error(err)
resp.StatusCode = http.StatusBadGateway resp.StatusCode = http.StatusBadGateway
return resp.Write(conn) resp.Write(conn)
return err
} }
ro.Client = tunnelID.String()
if tunnelID.IsPrivate() { if tunnelID.IsPrivate() {
err := fmt.Errorf("access denied: tunnel %s is private for host %s", tunnelID, req.Host) err = fmt.Errorf("access denied: tunnel %s is private for host %s", tunnelID, req.Host)
log.Error(err) log.Error(err)
resp.StatusCode = http.StatusBadGateway resp.StatusCode = http.StatusBadGateway
return resp.Write(conn) resp.Write(conn)
return err
} }
log = log.WithFields(map[string]any{ log = log.WithFields(map[string]any{
@@ -116,6 +159,7 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
}) })
remoteAddr = addr remoteAddr = addr
} }
ro.RemoteAddr = remoteAddr.String()
d := &Dialer{ d := &Dialer{
node: ep.node, node: ep.node,
@@ -165,14 +209,15 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
} }
} }
if err := req.Write(c); err != nil { if err = req.Write(c); err != nil {
c.Close() c.Close()
log.Errorf("send request: %v", err) log.Errorf("send request: %v", err)
return resp.Write(conn) resp.Write(conn)
return err
} }
if req.Header.Get("Upgrade") == "websocket" { if req.Header.Get("Upgrade") == "websocket" {
err := xnet.Transport(c, xio.NewReadWriter(br, conn)) err = xnet.Transport(c, xio.NewReadWriter(br, conn))
if err == nil { if err == nil {
err = io.EOF err = io.EOF
} }
@@ -182,21 +227,35 @@ func (ep *entrypoint) handle(ctx context.Context, conn net.Conn) error {
go func() { go func() {
defer c.Close() defer c.Close()
t := time.Now()
log.Debugf("%s <-> %s", remoteAddr, host) log.Debugf("%s <-> %s", remoteAddr, host)
var err error
var res *http.Response
defer func() { defer func() {
d := time.Since(start)
log.WithFields(map[string]any{ log.WithFields(map[string]any{
"duration": time.Since(t), "duration": d,
}).Debugf("%s >-< %s", remoteAddr, host) }).Debugf("%s >-< %s", remoteAddr, host)
ro.Duration = d
if err != nil {
ro.Err = err.Error()
}
if res != nil && ro.HTTP != nil {
ro.HTTP.ResponseHeader = res.Header
ro.HTTP.StatusCode = res.StatusCode
}
ro.Record(ctx, ep.recorder.Recorder)
}() }()
res, err := http.ReadResponse(bufio.NewReader(c), req) res, err = http.ReadResponse(bufio.NewReader(c), req)
if err != nil { if err != nil {
log.Errorf("read response: %v", err) log.Errorf("read response: %v", err)
resp.Write(conn) resp.Write(conn)
return return
} }
defer res.Body.Close()
if log.IsLevelEnabled(logger.TraceLevel) { if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(res, false) dump, _ := httputil.DumpResponse(res, false)
+21 -23
View File
@@ -13,13 +13,13 @@ import (
"github.com/go-gost/core/listener" "github.com/go-gost/core/listener"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
"github.com/go-gost/core/service" "github.com/go-gost/core/service"
"github.com/go-gost/relay" "github.com/go-gost/relay"
ctxvalue "github.com/go-gost/x/ctx" ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
limiter_util "github.com/go-gost/x/internal/util/limiter" limiter_util "github.com/go-gost/x/internal/util/limiter"
stats_util "github.com/go-gost/x/internal/util/stats" stats_util "github.com/go-gost/x/internal/util/stats"
rate_limiter "github.com/go-gost/x/limiter/rate"
xrecorder "github.com/go-gost/x/recorder" xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
xservice "github.com/go-gost/x/service" xservice "github.com/go-gost/x/service"
@@ -32,7 +32,6 @@ var (
ErrTunnelID = errors.New("invalid tunnel ID") ErrTunnelID = errors.New("invalid tunnel ID")
ErrTunnelNotAvailable = errors.New("tunnel not available") ErrTunnelNotAvailable = errors.New("tunnel not available")
ErrUnauthorized = errors.New("unauthorized") ErrUnauthorized = errors.New("unauthorized")
ErrRateLimit = errors.New("rate limiting exceeded")
) )
func init() { func init() {
@@ -40,17 +39,16 @@ func init() {
} }
type tunnelHandler struct { type tunnelHandler struct {
id string id string
options handler.Options options handler.Options
pool *ConnectorPool pool *ConnectorPool
recorder recorder.Recorder epSvc service.Service
epSvc service.Service ep *entrypoint
ep *entrypoint md metadata
md metadata log logger.Logger
log logger.Logger stats *stats_util.HandlerStats
stats *stats_util.HandlerStats limiter traffic.TrafficLimiter
limiter traffic.TrafficLimiter cancel context.CancelFunc
cancel context.CancelFunc
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -79,19 +77,11 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
"node": h.id, "node": h.id,
}) })
if opts := h.options.Router.Options(); opts != nil {
for _, ro := range opts.Recorders {
if ro.Record == xrecorder.RecorderServiceHandlerTunnel {
h.recorder = ro.Recorder
break
}
}
}
h.pool = NewConnectorPool(h.id, h.md.sd) h.pool = NewConnectorPool(h.id, h.md.sd)
h.ep = &entrypoint{ h.ep = &entrypoint{
node: h.id, node: h.id,
service: h.options.Service,
pool: h.pool, pool: h.pool,
ingress: h.md.ingress, ingress: h.md.ingress,
sd: h.md.sd, sd: h.md.sd,
@@ -103,6 +93,13 @@ func (h *tunnelHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.ep.recorder = ro
break
}
}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel h.cancel = cancel
@@ -174,6 +171,7 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
log := h.log.WithFields(map[string]any{ log := h.log.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr()) log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
@@ -188,7 +186,7 @@ func (h *tunnelHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
}() }()
if !h.checkRateLimit(conn.RemoteAddr()) { if !h.checkRateLimit(conn.RemoteAddr()) {
return ErrRateLimit return rate_limiter.ErrRateLimit
} }
if h.md.readTimeout > 0 { if h.md.readTimeout > 0 {
+43 -7
View File
@@ -12,7 +12,10 @@ import (
"github.com/go-gost/core/hop" "github.com/go-gost/core/hop"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
md "github.com/go-gost/core/metadata" md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/recorder"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
xrecorder "github.com/go-gost/x/recorder"
"github.com/go-gost/x/registry" "github.com/go-gost/x/registry"
) )
@@ -21,9 +24,10 @@ func init() {
} }
type unixHandler struct { type unixHandler struct {
hop hop.Hop hop hop.Hop
md metadata md metadata
options handler.Options options handler.Options
recorder recorder.RecorderObject
} }
func NewHandler(opts ...handler.Option) handler.Handler { func NewHandler(opts ...handler.Option) handler.Handler {
@@ -42,6 +46,13 @@ func (h *unixHandler) Init(md md.Metadata) (err error) {
return return
} }
for _, ro := range h.options.Recorders {
if ro.Record == xrecorder.RecorderServiceHandler {
h.recorder = ro
break
}
}
return return
} }
@@ -50,20 +61,43 @@ func (h *unixHandler) Forward(hop hop.Hop) {
h.hop = hop h.hop = hop
} }
func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error { func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close() defer conn.Close()
log := h.options.Logger start := time.Now()
log = log.WithFields(map[string]any{ ro := &xrecorder.HandlerRecorderObject{
Service: h.options.Service,
Network: "unix",
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
Time: start,
SID: string(ctxvalue.SidFromContext(ctx)),
}
log := h.options.Logger.WithFields(map[string]any{
"remote": conn.RemoteAddr().String(), "remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(), "local": conn.LocalAddr().String(),
"sid": ctxvalue.SidFromContext(ctx),
}) })
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.Duration = time.Since(start)
ro.Record(ctx, h.recorder.Recorder)
log.WithFields(map[string]any{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if h.hop != nil { if h.hop != nil {
target := h.hop.Select(ctx) target := h.hop.Select(ctx)
if target == nil { if target == nil {
err := errors.New("target not available") err = errors.New("target not available")
log.Error(err) log.Error(err)
return err return err
} }
@@ -71,6 +105,8 @@ func (h *unixHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler
"node": target.Name, "node": target.Name,
"dst": target.Addr, "dst": target.Addr,
}) })
ro.Host = target.Addr
return h.forwardUnix(ctx, conn, target, log) return h.forwardUnix(ctx, conn, target, log)
} }
+4
View File
@@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net" xnet "github.com/go-gost/x/internal/net"
"github.com/vishvananda/netns" "github.com/vishvananda/netns"
) )
@@ -39,6 +40,9 @@ func (d *Dialer) Dial(ctx context.Context, network, addr string) (conn net.Conn,
if log == nil { if log == nil {
log = logger.Default() log = logger.Default()
} }
log = log.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
if d.Netns != "" { if d.Netns != "" {
runtime.LockOSThread() runtime.LockOSThread()
+8
View File
@@ -8,6 +8,7 @@ import (
"github.com/go-gost/core/hosts" "github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
"github.com/go-gost/core/resolver" "github.com/go-gost/core/resolver"
ctxvalue "github.com/go-gost/x/ctx"
) )
func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) { func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) {
@@ -20,6 +21,13 @@ func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hos
return addr, nil return addr, nil
} }
if log == nil {
log = logger.Default()
}
log = log.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
if hosts != nil { if hosts != nil {
if ips, _ := hosts.Lookup(ctx, network, host); len(ips) > 0 { if ips, _ := hosts.Lookup(ctx, network, host); len(ips) > 0 {
log.Debugf("hit host mapper: %s -> %s", host, ips) log.Debugf("hit host mapper: %s -> %s", host, ips)
+20 -4
View File
@@ -1,11 +1,13 @@
package resolver package resolver
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
"time" "time"
"github.com/go-gost/core/logger" "github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@@ -44,7 +46,7 @@ func (c *Cache) WithLogger(logger logger.Logger) *Cache {
return c return c
} }
func (c *Cache) Load(key CacheKey) (msg *dns.Msg, ttl time.Duration) { func (c *Cache) Load(ctx context.Context, key CacheKey) (msg *dns.Msg, ttl time.Duration) {
v, ok := c.m.Load(key) v, ok := c.m.Load(key)
if !ok { if !ok {
return return
@@ -66,12 +68,19 @@ func (c *Cache) Load(key CacheKey) (msg *dns.Msg, ttl time.Duration) {
} }
ttl = item.ttl - time.Since(item.ts) ttl = item.ttl - time.Since(item.ts)
c.logger.Debugf("resolver cache hit: %s, ttl: %v", key, ttl) if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
log = log.WithFields(map[string]any{
"sid": sid,
})
}
log.Debugf("resolver cache hit: %s, ttl: %v", key, ttl)
}
return return
} }
func (c *Cache) Store(key CacheKey, mr *dns.Msg, ttl time.Duration) { func (c *Cache) Store(ctx context.Context, key CacheKey, mr *dns.Msg, ttl time.Duration) {
if key == "" || mr == nil || ttl < 0 { if key == "" || mr == nil || ttl < 0 {
return return
} }
@@ -98,7 +107,14 @@ func (c *Cache) Store(key CacheKey, mr *dns.Msg, ttl time.Duration) {
ttl: ttl, ttl: ttl,
}) })
c.logger.Debugf("resolver cache store: %s, ttl: %v", key, ttl) if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
log = log.WithFields(map[string]any{
"sid": sid,
})
}
log.Debugf("resolver cache store: %s, ttl: %v", key, ttl)
}
} }
func (c *Cache) RefreshTTL(key CacheKey) { func (c *Cache) RefreshTTL(key CacheKey) {
+5
View File
@@ -1,6 +1,7 @@
package rate package rate
import ( import (
"errors"
"sort" "sort"
"time" "time"
@@ -8,6 +9,10 @@ import (
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
var (
ErrRateLimit = errors.New("rate limit")
)
type rlimiter struct { type rlimiter struct {
limiter *rate.Limiter limiter *rate.Limiter
} }
+2 -2
View File
@@ -44,13 +44,13 @@ func NewMetrics() metrics.Metrics {
Name: string(MetricServiceTransferInputBytesCounter), Name: string(MetricServiceTransferInputBytesCounter),
Help: "Total service input data transfer size in bytes", Help: "Total service input data transfer size in bytes",
}, },
[]string{"host", "service"}), []string{"host", "service", "client"}),
MetricServiceTransferOutputBytesCounter: prometheus.NewCounterVec( MetricServiceTransferOutputBytesCounter: prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: string(MetricServiceTransferOutputBytesCounter), Name: string(MetricServiceTransferOutputBytesCounter),
Help: "Total service output data transfer size in bytes", Help: "Total service output data transfer size in bytes",
}, },
[]string{"host", "service"}), []string{"host", "service", "client"}),
MetricServiceHandlerErrorsCounter: prometheus.NewCounterVec( MetricServiceHandlerErrorsCounter: prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: string(MetricServiceHandlerErrorsCounter), Name: string(MetricServiceHandlerErrorsCounter),
+81 -4
View File
@@ -20,16 +20,21 @@ var (
// serverConn is a server side Conn with metrics supported. // serverConn is a server side Conn with metrics supported.
type serverConn struct { type serverConn struct {
net.Conn net.Conn
service string service string
clientIP string
} }
func WrapConn(service string, c net.Conn) net.Conn { func WrapConn(service string, c net.Conn) net.Conn {
if !xmetrics.IsEnabled() { if !xmetrics.IsEnabled() || c == nil {
return c return c
} }
host, _, _ := net.SplitHostPort(c.RemoteAddr().String())
return &serverConn{ return &serverConn{
service: service, service: service,
Conn: c, Conn: c,
clientIP: host,
} }
} }
@@ -39,6 +44,7 @@ func (c *serverConn) Read(b []byte) (n int, err error) {
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": c.clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -51,6 +57,7 @@ func (c *serverConn) Write(b []byte) (n int, err error) {
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": c.clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -90,10 +97,17 @@ func WrapPacketConn(service string, pc net.PacketConn) net.PacketConn {
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p) n, addr, err = c.PacketConn.ReadFrom(p)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -102,10 +116,17 @@ func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *packetConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr) n, err = c.PacketConn.WriteTo(p, addr)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -155,10 +176,17 @@ func (c *udpConn) SetWriteBuffer(n int) error {
func (c *udpConn) Read(b []byte) (n int, err error) { func (c *udpConn) Read(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Reader); ok { if nc, ok := c.PacketConn.(io.Reader); ok {
n, err = nc.Read(b) n, err = nc.Read(b)
var clientIP string
if addr := c.RemoteAddr(); addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -170,10 +198,17 @@ func (c *udpConn) Read(b []byte) (n int, err error) {
func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p) n, addr, err = c.PacketConn.ReadFrom(p)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -183,10 +218,17 @@ func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) { func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok { if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, addr, err = nc.ReadFromUDP(b) n, addr, err = nc.ReadFromUDP(b)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -199,10 +241,17 @@ func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
if nc, ok := c.PacketConn.(udp.ReadUDP); ok { if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
n, oobn, flags, addr, err = nc.ReadMsgUDP(b, oob) n, oobn, flags, addr, err = nc.ReadMsgUDP(b, oob)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferInputBytesCounter, xmetrics.MetricServiceTransferInputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -215,10 +264,17 @@ func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAd
func (c *udpConn) Write(b []byte) (n int, err error) { func (c *udpConn) Write(b []byte) (n int, err error) {
if nc, ok := c.PacketConn.(io.Writer); ok { if nc, ok := c.PacketConn.(io.Writer); ok {
n, err = nc.Write(b) n, err = nc.Write(b)
var clientIP string
if addr := c.RemoteAddr(); addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -230,10 +286,17 @@ func (c *udpConn) Write(b []byte) (n int, err error) {
func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) { func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
n, err = c.PacketConn.WriteTo(p, addr) n, err = c.PacketConn.WriteTo(p, addr)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -243,10 +306,17 @@ func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) { func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok { if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, err = nc.WriteToUDP(b, addr) n, err = nc.WriteToUDP(b, addr)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
@@ -259,10 +329,17 @@ func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
func (c *udpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { func (c *udpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
if nc, ok := c.PacketConn.(udp.WriteUDP); ok { if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
n, oobn, err = nc.WriteMsgUDP(b, oob, addr) n, oobn, err = nc.WriteMsgUDP(b, oob, addr)
var clientIP string
if addr != nil {
clientIP, _, _ = net.SplitHostPort(addr.String())
}
if counter := xmetrics.GetCounter( if counter := xmetrics.GetCounter(
xmetrics.MetricServiceTransferOutputBytesCounter, xmetrics.MetricServiceTransferOutputBytesCounter,
metrics.Labels{ metrics.Labels{
"service": c.service, "service": c.service,
"client": clientIP,
}); counter != nil { }); counter != nil {
counter.Add(float64(n)) counter.Add(float64(n))
} }
+27 -2
View File
@@ -5,6 +5,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
@@ -12,6 +13,7 @@ import (
type httpRecorderOptions struct { type httpRecorderOptions struct {
timeout time.Duration timeout time.Duration
header http.Header
} }
type HTTPRecorderOption func(opts *httpRecorderOptions) type HTTPRecorderOption func(opts *httpRecorderOptions)
@@ -22,9 +24,16 @@ func TimeoutHTTPRecorderOption(timeout time.Duration) HTTPRecorderOption {
} }
} }
func HeaderHTTPRecorderOption(header http.Header) HTTPRecorderOption {
return func(opts *httpRecorderOptions) {
opts.header = header
}
}
type httpRecorder struct { type httpRecorder struct {
url string url string
httpClient *http.Client httpClient *http.Client
header http.Header
} }
// HTTPRecorder records data to HTTP service. // HTTPRecorder records data to HTTP service.
@@ -34,11 +43,19 @@ func HTTPRecorder(url string, opts ...HTTPRecorderOption) recorder.Recorder {
opt(&options) opt(&options)
} }
if url == "" {
return nil
}
if !strings.HasPrefix(url, "http") {
url = "http://" + url
}
return &httpRecorder{ return &httpRecorder{
url: url, url: url,
httpClient: &http.Client{ httpClient: &http.Client{
Timeout: options.timeout, Timeout: options.timeout,
}, },
header: options.header,
} }
} }
@@ -48,14 +65,22 @@ func (r *httpRecorder) Record(ctx context.Context, b []byte, opts ...recorder.Re
return err return err
} }
if r.header != nil {
req.Header = r.header
}
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/json")
}
resp, err := r.httpClient.Do(req) resp, err := r.httpClient.Do(req)
if err != nil { if err != nil {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("%d %s", resp.StatusCode, resp.Status) return fmt.Errorf(resp.Status)
} }
return nil return nil
+62
View File
@@ -1,6 +1,68 @@
package recorder package recorder
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/go-gost/core/recorder"
)
const ( const (
RecorderServiceHandler = "recorder.service.handler"
RecorderServiceHandlerSerial = "recorder.service.handler.serial" RecorderServiceHandlerSerial = "recorder.service.handler.serial"
RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel" RecorderServiceHandlerTunnel = "recorder.service.handler.tunnel"
) )
type HTTPRecorderObject struct {
Host string `json:"host"`
Method string `json:"method"`
Proto string `json:"proto"`
Scheme string `json:"scheme"`
URI string `json:"uri"`
StatusCode int `json:"statusCode"`
RequestHeader http.Header `json:"requestHeader"`
ResponseHeader http.Header `json:"responseHeader"`
// RequestBody string
// ResponseBody string
}
type DNSRecorderObject struct {
ID int `json:"id"`
Name string `json:"name"`
Class string `json:"class"`
Type string `json:"type"`
Question string `json:"question"`
Answer string `json:"answer"`
Cached bool `json:"cached"`
}
type HandlerRecorderObject struct {
Node string `json:"node,omitempty"`
Service string `json:"service"`
Network string `json:"network"`
RemoteAddr string `json:"remote"`
LocalAddr string `json:"local"`
Host string `json:"host"`
Client string `json:"client,omitempty"`
HTTP *HTTPRecorderObject `json:"http,omitempty"`
DNS *DNSRecorderObject `json:"dns,omitempty"`
Err string `json:"err,omitempty"`
Duration time.Duration `json:"duration"`
Time time.Time `json:"time"`
SID string `json:"sid"`
}
func (p *HandlerRecorderObject) Record(ctx context.Context, r recorder.Recorder) error {
if p == nil || r == nil {
return nil
}
data, err := json.Marshal(p)
if err != nil {
return err
}
return r.Record(ctx, data)
}
+10
View File
@@ -5,11 +5,13 @@ import (
"net" "net"
"time" "time"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/recorder" "github.com/go-gost/core/recorder"
) )
type tcpRecorderOptions struct { type tcpRecorderOptions struct {
timeout time.Duration timeout time.Duration
log logger.Logger
} }
type TCPRecorderOption func(opts *tcpRecorderOptions) type TCPRecorderOption func(opts *tcpRecorderOptions)
@@ -20,9 +22,16 @@ func TimeoutTCPRecorderOption(timeout time.Duration) TCPRecorderOption {
} }
} }
func LogTCPRecorderOption(log logger.Logger) TCPRecorderOption {
return func(opts *tcpRecorderOptions) {
opts.log = log
}
}
type tcpRecorder struct { type tcpRecorder struct {
addr string addr string
dialer *net.Dialer dialer *net.Dialer
log logger.Logger
} }
// TCPRecorder records data to TCP service. // TCPRecorder records data to TCP service.
@@ -37,6 +46,7 @@ func TCPRecorder(addr string, opts ...TCPRecorderOption) recorder.Recorder {
dialer: &net.Dialer{ dialer: &net.Dialer{
Timeout: options.timeout, Timeout: options.timeout,
}, },
log: options.log,
} }
} }
+4 -4
View File
@@ -166,11 +166,11 @@ func (r *localResolver) resolveAsync(ctx context.Context, server *NameServer, ho
return return
} }
func (r *localResolver) lookupCache(_ context.Context, server *NameServer, host string) (ips []net.IP, ttl time.Duration, ok bool) { func (r *localResolver) lookupCache(ctx context.Context, server *NameServer, host string) (ips []net.IP, ttl time.Duration, ok bool) {
lookup := func(t uint16, host string) (ips []net.IP, ttl time.Duration, ok bool) { lookup := func(t uint16, host string) (ips []net.IP, ttl time.Duration, ok bool) {
mq := dns.Msg{} mq := dns.Msg{}
mq.SetQuestion(dns.Fqdn(host), t) mq.SetQuestion(dns.Fqdn(host), t)
mr, ttl := r.cache.Load(resolver_util.NewCacheKey(&mq.Question[0])) mr, ttl := r.cache.Load(ctx, resolver_util.NewCacheKey(&mq.Question[0]))
if mr == nil { if mr == nil {
return return
} }
@@ -223,14 +223,14 @@ func (r *localResolver) resolveIPs(ctx context.Context, server *NameServer, mq *
} }
key := resolver_util.NewCacheKey(&mq.Question[0]) key := resolver_util.NewCacheKey(&mq.Question[0])
mr, ttl := r.cache.Load(key) mr, ttl := r.cache.Load(ctx, key)
if ttl <= 0 { if ttl <= 0 {
resolver_util.AddSubnetOpt(mq, server.ClientIP) resolver_util.AddSubnetOpt(mq, server.ClientIP)
mr, err = r.exchange(ctx, server.exchanger, mq) mr, err = r.exchange(ctx, server.exchanger, mq)
if err != nil { if err != nil {
return return
} }
r.cache.Store(key, mr, server.TTL) r.cache.Store(ctx, key, mr, server.TTL)
if r.options.logger.IsLevelEnabled(logger.TraceLevel) { if r.options.logger.IsLevelEnabled(logger.TraceLevel) {
r.options.logger.Trace(mr.String()) r.options.logger.Trace(mr.String())