31878a72ec
- Add package-level architecture doc in handler.go covering: - NAT traversal reverse proxy architecture - CmdBind/CmdConnect roles and data flow - Connector lifecycle and waitClose semantics - Entrypoint protocol dispatch (first-byte sniffing) - SD fallback behavior - Add data-flow-oriented doc comments to all business files: - entrypoint.go: protocol dispatch, dial flow - connector.go: Connector/ConnectorPool semantics - dialer.go: two-phase dial strategy - tunnel.go: MaxWeight semantics, selection algorithm - bind.go: 6-step CmdBind flow - connect.go: CmdConnect flow with relay framing - ephttp.go, eptls.go, eprelay.go: per-protocol entrypoint flow - Fix observeStats: after successful error retry, also flush new events instead of skipping the current tick (handler.go:261-271) - Add test for observeStats retry-then-flush (handler_test.go)
123 lines
3.1 KiB
Go
123 lines
3.1 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-gost/core/logger"
|
|
"github.com/go-gost/relay"
|
|
xnet "github.com/go-gost/x/internal/net"
|
|
xrecorder "github.com/go-gost/x/recorder"
|
|
)
|
|
|
|
// handleConnect (entrypoint relay) processes a relay-protocol connection
|
|
// arriving at the entrypoint.
|
|
//
|
|
// Flow:
|
|
// 1. Read relay.Request (extracts src/dst address, tunnelID, network).
|
|
// 2. Dialer.Dial() → pool.Get() → GetConn() → mux.OpenStream().
|
|
// 3. Write StatusOK response to the public connection.
|
|
// 4. Write relay.Response with src/dst address features to the mux stream.
|
|
// 5. Pipe(publicConn, muxStream).
|
|
//
|
|
// Unlike tunnelHandler.handleConnect, this path does not use ingress routing
|
|
// or bypass checks — the relay request already contains the tunnel ID.
|
|
// Also unlike handleConnect, there is no local-vs-remote framing difference:
|
|
// the entrypoint always writes StatusOK + address features regardless of node.
|
|
func (ep *entrypoint) handleConnect(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
|
|
req := relay.Request{}
|
|
if _, err := req.ReadFrom(conn); err != nil {
|
|
return err
|
|
}
|
|
|
|
resp := relay.Response{
|
|
Version: relay.Version1,
|
|
Status: relay.StatusOK,
|
|
}
|
|
|
|
var srcAddr, dstAddr string
|
|
network := "tcp"
|
|
var tunnelID relay.TunnelID
|
|
for _, f := range req.Features {
|
|
switch f.Type() {
|
|
case relay.FeatureAddr:
|
|
if feature, _ := f.(*relay.AddrFeature); feature != nil {
|
|
v := net.JoinHostPort(feature.Host, strconv.Itoa(int(feature.Port)))
|
|
if srcAddr != "" {
|
|
dstAddr = v
|
|
} else {
|
|
srcAddr = v
|
|
}
|
|
}
|
|
case relay.FeatureTunnel:
|
|
if feature, _ := f.(*relay.TunnelFeature); feature != nil {
|
|
tunnelID = relay.NewTunnelID(feature.ID[:])
|
|
}
|
|
case relay.FeatureNetwork:
|
|
if feature, _ := f.(*relay.NetworkFeature); feature != nil {
|
|
network = feature.Network.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
if tunnelID.IsZero() {
|
|
resp.Status = relay.StatusBadRequest
|
|
resp.WriteTo(conn)
|
|
return ErrTunnelID
|
|
}
|
|
|
|
ro.ClientID = tunnelID.String()
|
|
|
|
d := Dialer{
|
|
pool: ep.pool,
|
|
retry: 3,
|
|
timeout: 15 * time.Second,
|
|
log: log,
|
|
}
|
|
cc, _, cid, err := d.Dial(ctx, network, tunnelID.String())
|
|
if err != nil {
|
|
log.Error(err)
|
|
resp.Status = relay.StatusServiceUnavailable
|
|
resp.WriteTo(conn)
|
|
return err
|
|
}
|
|
defer cc.Close()
|
|
|
|
log.Debugf("new connection to tunnel: %s, connector: %s", tunnelID, cid)
|
|
|
|
if _, err := resp.WriteTo(conn); err != nil {
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
features := relay.Response{
|
|
Version: relay.Version1,
|
|
Status: relay.StatusOK,
|
|
}
|
|
|
|
af := &relay.AddrFeature{}
|
|
af.ParseFrom(srcAddr)
|
|
features.Features = append(features.Features, af) // src address
|
|
|
|
af = &relay.AddrFeature{}
|
|
af.ParseFrom(dstAddr)
|
|
features.Features = append(features.Features, af) // dst address
|
|
|
|
if _, err := features.WriteTo(cc); err != nil {
|
|
log.Error(err)
|
|
cc.Close()
|
|
return err
|
|
}
|
|
|
|
t := time.Now()
|
|
log.Debugf("%s <-> %s", conn.RemoteAddr(), cc.RemoteAddr())
|
|
// xnet.Transport(conn, cc)
|
|
xnet.Pipe(ctx, conn, cc)
|
|
log.WithFields(map[string]any{
|
|
"duration": time.Since(t),
|
|
}).Debugf("%s >-< %s", conn.RemoteAddr(), cc.RemoteAddr())
|
|
|
|
return nil
|
|
} |