Files
x/handler/relay/handler.go
T
ginuerzh d5fd62aa47 docs(handler/relay): convert all comments to English
Translate all Chinese comments in the relay handler package to English:
handler.go, connect.go, bind.go, forward.go, conn.go, entrypoint.go,
observe.go, metadata.go. All inline and doc comments are now in English.

Also set a persistent preference: all future code comments must be
written in English only.
2026-06-03 23:05:08 +08:00

305 lines
9.6 KiB
Go

package relay
import (
"context"
"errors"
"net"
"strconv"
"time"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/handler"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/limiter"
"github.com/go-gost/core/limiter/traffic"
md "github.com/go-gost/core/metadata"
"github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
"github.com/go-gost/relay"
xctx "github.com/go-gost/x/ctx"
stats_util "github.com/go-gost/x/internal/util/stats"
tls_util "github.com/go-gost/x/internal/util/tls"
rate_limiter "github.com/go-gost/x/limiter/rate"
cache_limiter "github.com/go-gost/x/limiter/traffic/cache"
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"
)
var (
ErrBadVersion = errors.New("relay: bad version")
ErrUnknownCmd = errors.New("relay: unknown command")
ErrUnauthorized = errors.New("relay: unauthorized")
)
func init() {
registry.HandlerRegistry().Register("relay", NewHandler)
}
// relayHandler is the GOST relay protocol server handler.
//
// The GOST relay protocol is a custom multiplexed transport supporting three modes:
// 1. Connect — the client requests a connection to a target address.
// The handler dials via the configured Router and pipes data bidirectionally.
// - Direct: handleConnect(), target address from the relay request.
// - Forward: handleForward(), target from hop selector (load balancing).
// 2. Bind — the client asks the handler to listen on a local port and forward
// incoming connections back through a mux session. Used for reverse proxying.
// 3. Forward — when a hop is set, the target is selected by the hop's strategy
// rather than specified by the client.
//
// Data flow:
//
// ┌─────────────────────────────────────────────────────────┐
// │ Handle() → parse relay.Request │
// │ ├─ extract auth (UserAuthFeature) → authenticate │
// │ ├─ extract target address (AddrFeature) │
// │ └─ extract network type (NetworkFeature) │
// │ │
// │ ┌── hop set? ──→ handleForward() │
// │ │ ├─ hop.Select() pick target node │
// │ │ └─ Router.Dial() → Pipe bidir copy │
// │ │
// │ └── no hop, dispatch by command: │
// │ ├─ CmdConnect → handleConnect() │
// │ │ ├─ bypass check │
// │ │ ├─ consistent-hashing │
// │ │ ├─ Router.Dial() / net.Dial() / serial.Open │
// │ │ ├─ send response header (noDelay) │
// │ │ ├─ optional protocol sniffing (HTTP/TLS MITM) │
// │ │ └─ Pipe bidir data copy │
// │ └─ CmdBind → handleBind() │
// │ ├─ bindTCP: net.Listen → mux session → tcpHandler │
// │ └─ bindUDP: net.ListenPacket → udp.Relay │
// └─────────────────────────────────────────────────────────┘
type relayHandler struct {
hop hop.Hop
md metadata
options handler.Options
stats *stats_util.HandlerStats
limiter traffic.TrafficLimiter
cancel context.CancelFunc
recorder recorder.RecorderObject
certPool tls_util.CertPool
}
func NewHandler(opts ...handler.Option) handler.Handler {
options := handler.Options{}
for _, opt := range opts {
opt(&options)
}
return &relayHandler{
options: options,
}
}
// Init 初始化 relay handler。在 handler 被注册到 service 后调用。
//
// 初始化流程:
// 1. 解析元数据配置(超时、嗅探、mux、MITM 等)
// 2. 如果配置了 Observer,创建 stats 统计器并启动后台轮询协程
// 3. 如果配置了 TrafficLimiter,创建带缓存的流量限制器
// 4. 从 Recorders 列表中选取 ServiceHandler 类型的 recorder
// 5. 如果配置了 MITM 证书,创建内存证书池
func (h *relayHandler) Init(md md.Metadata) (err error) {
if err := h.parseMetadata(md); err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
h.cancel = cancel
if h.options.Observer != nil {
h.stats = stats_util.NewHandlerStats(h.options.Service, h.md.observerResetTraffic)
go h.observeStats(ctx)
}
if h.options.Limiter != nil {
h.limiter = cache_limiter.NewCachedTrafficLimiter(h.options.Limiter,
cache_limiter.RefreshIntervalOption(h.md.limiterRefreshInterval),
cache_limiter.CleanupIntervalOption(h.md.limiterCleanupInterval),
cache_limiter.ScopeOption(limiter.ScopeClient),
)
}
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 nil
}
// Forward implements handler.Forwarder.
func (h *relayHandler) Forward(hop hop.Hop) {
h.hop = hop
}
// Handle is the main entry point for each inbound connection.
//
// Flow:
// 1. Create a recorder object with connection metadata.
// 2. Wrap the connection for stats collection (input/output bytes).
// 3. Rate-limit check.
// 4. Set read deadline, read the relay.Request.
// 5. Clear the read deadline.
// 6. Check the relay protocol version.
// 7. Parse request features (auth, address, network type).
// 8. Authenticate (if an Auther is configured).
// 9. Dispatch:
// - If hop is set → handleForward.
// - CmdConnect → handleConnect.
// - CmdBind → handleBind.
// 10. Deferred final stats recording.
func (h *relayHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
defer conn.Close()
start := time.Now()
ro := &xrecorder.HandlerRecorderObject{
Network: "tcp",
Service: h.options.Service,
RemoteAddr: conn.RemoteAddr().String(),
LocalAddr: conn.LocalAddr().String(),
SID: xctx.SidFromContext(ctx).String(),
Time: start,
}
if srcAddr := xctx.SrcAddrFromContext(ctx); srcAddr != nil {
ro.ClientAddr = srcAddr.String()
}
log := h.options.Logger.WithFields(map[string]any{
"network": ro.Network,
"remote": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
"client": ro.ClientAddr,
"sid": ro.SID,
})
log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(start)
if err := ro.Record(ctx, h.recorder.Recorder); err != nil {
log.Errorf("record: %v", err)
}
log.WithFields(map[string]any{
"duration": time.Since(start),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
if !h.checkRateLimit(conn.RemoteAddr()) {
return rate_limiter.ErrRateLimit
}
if h.md.readTimeout > 0 {
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
}
req := relay.Request{}
if _, err := req.ReadFrom(conn); err != nil {
return err
}
conn.SetReadDeadline(time.Time{})
resp := relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
if req.Version != relay.Version1 {
resp.Status = relay.StatusBadRequest
resp.WriteTo(conn)
return ErrBadVersion
}
var user, pass string
var address string
var networkID relay.NetworkID
for _, f := range req.Features {
switch f.Type() {
case relay.FeatureUserAuth:
if feature, _ := f.(*relay.UserAuthFeature); feature != nil {
user, pass = feature.Username, feature.Password
}
case relay.FeatureAddr:
if feature, _ := f.(*relay.AddrFeature); feature != nil {
address = net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port)))
}
case relay.FeatureNetwork:
if feature, _ := f.(*relay.NetworkFeature); feature != nil {
networkID = feature.Network
}
}
}
if user != "" {
ro.ClientID = user
log = log.WithFields(map[string]any{"user": user})
}
if h.options.Auther != nil {
clientID, ok := h.options.Auther.Authenticate(ctx, user, pass, auth.WithService(h.options.Service))
if !ok {
resp.Status = relay.StatusUnauthorized
resp.WriteTo(conn)
return ErrUnauthorized
}
log = log.WithFields(map[string]any{"clientID": clientID})
ro.ClientID = clientID
ctx = xctx.ContextWithClientID(ctx, xctx.ClientID(clientID))
}
network := networkID.String()
if (req.Cmd & relay.FUDP) == relay.FUDP {
network = "udp"
}
ro.Network = network
ro.Host = address
log = log.WithFields(map[string]any{"network": network})
if h.hop != nil {
// Forward mode: target is selected from the hop.
return h.handleForward(ctx, conn, network, ro, log)
}
switch req.Cmd & relay.CmdMask {
case 0, relay.CmdConnect:
return h.handleConnect(ctx, conn, network, address, ro, log)
case relay.CmdBind:
return h.handleBind(ctx, conn, network, address, ro, log)
default:
resp.Status = relay.StatusBadRequest
resp.WriteTo(conn)
return ErrUnknownCmd
}
}
// Close implements io.Closer interface.
func (h *relayHandler) Close() error {
if h.cancel != nil {
h.cancel()
}
return nil
}