mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-02 08:56:38 +08:00
09c58e2298
Add debug logs to trace failover behavior: - FailFilter.Filter(): log node name, fail count, maxFails, timeSince, failTimeout - hop.Select(): log excludeNodes list, node selection results - handler retry loop: log maxRetries, selected nodes, dial failures This helps diagnose issues where failover between multiple target nodes is not working as expected.
277 lines
6.8 KiB
Go
277 lines
6.8 KiB
Go
package local
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/go-gost/core/chain"
|
|
"github.com/go-gost/core/handler"
|
|
"github.com/go-gost/core/hop"
|
|
md "github.com/go-gost/core/metadata"
|
|
"github.com/go-gost/core/observer/stats"
|
|
"github.com/go-gost/core/recorder"
|
|
ctxvalue "github.com/go-gost/x/ctx"
|
|
xnet "github.com/go-gost/x/internal/net"
|
|
"github.com/go-gost/x/internal/util/forwarder"
|
|
"github.com/go-gost/x/internal/util/sniffing"
|
|
tls_util "github.com/go-gost/x/internal/util/tls"
|
|
rate_limiter "github.com/go-gost/x/limiter/rate"
|
|
xstats "github.com/go-gost/x/observer/stats"
|
|
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
|
|
xrecorder "github.com/go-gost/x/recorder"
|
|
"github.com/go-gost/x/registry"
|
|
)
|
|
|
|
func init() {
|
|
registry.HandlerRegistry().Register("tcp", NewHandler)
|
|
registry.HandlerRegistry().Register("udp", NewHandler)
|
|
registry.HandlerRegistry().Register("forward", NewHandler)
|
|
}
|
|
|
|
type forwardHandler struct {
|
|
hop hop.Hop
|
|
md metadata
|
|
options handler.Options
|
|
recorder recorder.RecorderObject
|
|
certPool tls_util.CertPool
|
|
}
|
|
|
|
func NewHandler(opts ...handler.Option) handler.Handler {
|
|
options := handler.Options{}
|
|
for _, opt := range opts {
|
|
opt(&options)
|
|
}
|
|
|
|
return &forwardHandler{
|
|
options: options,
|
|
}
|
|
}
|
|
|
|
func (h *forwardHandler) Init(md md.Metadata) (err error) {
|
|
if err = h.parseMetadata(md); err != nil {
|
|
return
|
|
}
|
|
|
|
for _, ro := range h.options.Recorders {
|
|
if ro.Record == xrecorder.RecorderServiceHandler {
|
|
h.recorder = ro
|
|
break
|
|
}
|
|
}
|
|
|
|
if h.md.certificate != nil && h.md.privateKey != nil {
|
|
h.certPool = tls_util.NewMemoryCertPool()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Forward implements handler.Forwarder.
|
|
func (h *forwardHandler) Forward(hop hop.Hop) {
|
|
h.hop = hop
|
|
}
|
|
|
|
func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
|
|
defer conn.Close()
|
|
|
|
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)),
|
|
}
|
|
|
|
ro.ClientIP = conn.RemoteAddr().String()
|
|
if clientAddr := ctxvalue.ClientAddrFromContext(ctx); clientAddr != "" {
|
|
ro.ClientIP = string(clientAddr)
|
|
} else {
|
|
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(conn.RemoteAddr().String()))
|
|
}
|
|
|
|
if h, _, _ := net.SplitHostPort(ro.ClientIP); h != "" {
|
|
ro.ClientIP = h
|
|
}
|
|
|
|
network := "tcp"
|
|
if conn.RemoteAddr().Network() == "udp" {
|
|
network = "udp"
|
|
}
|
|
ro.Network = network
|
|
|
|
pStats := xstats.Stats{}
|
|
conn = stats_wrapper.WrapConn(conn, &pStats)
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
ro.Err = err.Error()
|
|
}
|
|
ro.InputBytes = pStats.Get(stats.KindInputBytes)
|
|
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
|
|
ro.Duration = time.Since(start)
|
|
|
|
}()
|
|
|
|
if !h.checkRateLimit(conn.RemoteAddr()) {
|
|
return rate_limiter.ErrRateLimit
|
|
}
|
|
|
|
var proto string
|
|
if network == "tcp" && h.md.sniffing {
|
|
if h.md.sniffingTimeout > 0 {
|
|
conn.SetReadDeadline(time.Now().Add(h.md.sniffingTimeout))
|
|
}
|
|
|
|
br := bufio.NewReader(conn)
|
|
proto, _ = sniffing.Sniff(ctx, br)
|
|
ro.Proto = proto
|
|
|
|
if h.md.sniffingTimeout > 0 {
|
|
conn.SetReadDeadline(time.Time{})
|
|
}
|
|
|
|
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
var buf bytes.Buffer
|
|
cc, err := h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), "tcp", address)
|
|
ro.Route = buf.String()
|
|
return cc, err
|
|
}
|
|
sniffer := &forwarder.Sniffer{
|
|
Websocket: h.md.sniffingWebsocket,
|
|
WebsocketSampleRate: h.md.sniffingWebsocketSampleRate,
|
|
Recorder: h.recorder.Recorder,
|
|
RecorderOptions: h.recorder.Options,
|
|
Certificate: h.md.certificate,
|
|
PrivateKey: h.md.privateKey,
|
|
NegotiatedProtocol: h.md.alpn,
|
|
CertPool: h.certPool,
|
|
MitmBypass: h.md.mitmBypass,
|
|
ReadTimeout: h.md.readTimeout,
|
|
}
|
|
|
|
conn = xnet.NewReadWriteConn(br, conn, conn)
|
|
switch proto {
|
|
case sniffing.ProtoHTTP:
|
|
return sniffer.HandleHTTP(ctx, conn,
|
|
forwarder.WithDial(dial),
|
|
forwarder.WithHop(h.hop),
|
|
forwarder.WithBypass(h.options.Bypass),
|
|
forwarder.WithHTTPKeepalive(h.md.httpKeepalive),
|
|
forwarder.WithRecorderObject(ro),
|
|
)
|
|
case sniffing.ProtoTLS:
|
|
return sniffer.HandleTLS(ctx, conn,
|
|
forwarder.WithDial(dial),
|
|
forwarder.WithHop(h.hop),
|
|
forwarder.WithBypass(h.options.Bypass),
|
|
forwarder.WithRecorderObject(ro),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Determine max retry attempts
|
|
maxRetries := h.md.maxRetries
|
|
if maxRetries <= 0 {
|
|
// Default: try all available nodes
|
|
if nl, ok := h.hop.(hop.NodeList); ok {
|
|
maxRetries = len(nl.Nodes())
|
|
}
|
|
if maxRetries <= 0 {
|
|
maxRetries = 1
|
|
}
|
|
}
|
|
|
|
var triedNodes []string
|
|
var lastErr error
|
|
var cc net.Conn
|
|
|
|
h.options.Logger.Debugf("[handler.retry] starting retry loop: maxRetries=%d", maxRetries)
|
|
|
|
for attempt := 0; attempt < maxRetries; attempt++ {
|
|
// Select a target node, excluding previously tried nodes
|
|
selectCtx := ctxvalue.ContextWithExcludeNodes(ctx, triedNodes)
|
|
var target *chain.Node
|
|
if h.hop != nil {
|
|
target = h.hop.Select(selectCtx,
|
|
hop.ProtocolSelectOption(proto),
|
|
)
|
|
}
|
|
if target == nil {
|
|
h.options.Logger.Debugf("[handler.retry] attempt=%d target=nil, triedNodes=%v", attempt, triedNodes)
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return errors.New("node not available")
|
|
}
|
|
|
|
h.options.Logger.Debugf("[handler.retry] attempt=%d selected node=%s addr=%s", attempt, target.Name, target.Addr)
|
|
|
|
// Track this node as tried
|
|
triedNodes = append(triedNodes, target.Addr)
|
|
|
|
addr := target.Addr
|
|
if opts := target.Options(); opts != nil {
|
|
switch opts.Network {
|
|
case "unix":
|
|
network = opts.Network
|
|
default:
|
|
if _, _, err := net.SplitHostPort(addr); err != nil {
|
|
addr += ":0"
|
|
}
|
|
}
|
|
}
|
|
|
|
ro.Network = network
|
|
ro.Host = addr
|
|
|
|
var buf bytes.Buffer
|
|
cc, err = h.options.Router.Dial(ctxvalue.ContextWithBuffer(ctx, &buf), network, addr)
|
|
ro.Route = buf.String()
|
|
if err != nil {
|
|
// Mark node as failed for future selections
|
|
if marker := target.Marker(); marker != nil {
|
|
marker.Mark()
|
|
h.options.Logger.Debugf("[handler.retry] attempt=%d dial failed, marked node=%s count=%d err=%v",
|
|
attempt, target.Addr, marker.Count(), err)
|
|
}
|
|
lastErr = err
|
|
// Try next node
|
|
continue
|
|
}
|
|
|
|
// Success - reset marker and proceed
|
|
if marker := target.Marker(); marker != nil {
|
|
marker.Reset()
|
|
}
|
|
defer cc.Close()
|
|
|
|
xnet.Transport(conn, cc)
|
|
return nil
|
|
}
|
|
|
|
// All retries exhausted
|
|
if lastErr != nil {
|
|
return lastErr
|
|
}
|
|
return errors.New("all nodes failed")
|
|
}
|
|
|
|
func (h *forwardHandler) checkRateLimit(addr net.Addr) bool {
|
|
if h.options.RateLimiter == nil {
|
|
return true
|
|
}
|
|
host, _, _ := net.SplitHostPort(addr.String())
|
|
if limiter := h.options.RateLimiter.Limiter(host); limiter != nil {
|
|
return limiter.Allow(1)
|
|
}
|
|
|
|
return true
|
|
}
|