refactor(handler/tunnel): split 794-line entrypoint.go into 6 files, fix 3 bugs, add 48 tests
Breaking Change: moves Connector, ConnectorPool, parseTunnelID from tunnel.go into dedicated connector.go and id.go. Bug Fixes: - dialer.go: clear stale retry err so SD fallback is not masked (nil err from pool.Get() left previous iteration's error live) - connect.go: check WriteTo errors in both mux and non-mux paths - eprelay.go: use fresh relay.Response for features sent over mux (previously reused same resp struct after resp.WriteTo(muxConn)) Refactor: - entrypoint.go: split ~790 lines into 6 files (ephttp, eptls, eprelay, epwebsocket, eplistener, entrypointsvc), moved to dedicated package - handler.go: moved initEntrypoints/createEntrypointService to entrypointsvc.go - tunnel.go: moved Connector/ConnectorPool/parseTunnelID to connector.go and id.go - connect.go: moved entrypoint-originated handleConnect to eprelay.go (handler-side handleConnect kept in connect.go) Tests (48 new, 88 total): - id_test.go: parseTunnelID variants (empty, UUID, private '$', invalid) - tunnel_test.go: Tunnel lifecycle (AddConnector, GetConnector weighted/closed filtering, CloseOnIdle, clean), Connector (NewConnector nil-opts, GetConn nil/closed session, Close, IsClosed), ConnectorPool (Add/Get/Close, idle cleanup, concurrency) - dialer_test.go: SD error paths (sd.Get error, empty address) - handler_test.go: observeStats (nil observer, cancelled context loop), initEntrypoints (no entrypoints configured) - helpers_test.go: testLogger, fakeConn for handler tests - eprelay_test.go: handleConnect ingress/dial/SD round-trip
This commit is contained in:
@@ -2,54 +2,30 @@ package tunnel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/ingress"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-gost/core/sd"
|
||||
"github.com/go-gost/relay"
|
||||
dissector "github.com/go-gost/tls-dissector"
|
||||
admission "github.com/go-gost/x/admission/wrapper"
|
||||
xctx "github.com/go-gost/x/ctx"
|
||||
ictx "github.com/go-gost/x/internal/ctx"
|
||||
xio "github.com/go-gost/x/internal/io"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
xhttp "github.com/go-gost/x/internal/net/http"
|
||||
"github.com/go-gost/x/internal/net/proxyproto"
|
||||
"github.com/go-gost/x/internal/util/sniffing"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
ws_util "github.com/go-gost/x/internal/util/ws"
|
||||
climiter "github.com/go-gost/x/limiter/conn/wrapper"
|
||||
metrics "github.com/go-gost/x/metrics/wrapper"
|
||||
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"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
const (
|
||||
httpHeaderSID = "Gost-Sid"
|
||||
httpHeaderForwardedNode = "Gost-Forwarded-Node"
|
||||
)
|
||||
|
||||
// entrypoint is a public tunnel entry point that accepts external connections
|
||||
// and routes them through the tunnel network. It supports three protocols
|
||||
// determined by the first byte of the connection: relay (Version1), TLS, or HTTP.
|
||||
type entrypoint struct {
|
||||
node string
|
||||
service string
|
||||
@@ -127,7 +103,7 @@ func (ep *entrypoint) Handle(ctx context.Context, conn net.Conn) (err error) {
|
||||
return ep.handleConnect(ctx, conn, ro, log)
|
||||
}
|
||||
if v[0] == dissector.Handshake {
|
||||
return ep.HandleTLS(ctx, conn, ro, log)
|
||||
return ep.handleTLS(ctx, conn, ro, log)
|
||||
}
|
||||
return ep.handleHTTP(ctx, conn, ro, log)
|
||||
}
|
||||
@@ -210,584 +186,4 @@ func (ep *entrypoint) dial(ctx context.Context, network, addr string) (conn net.
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (ep *entrypoint) handleHTTP(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) (err error) {
|
||||
pStats := xstats.Stats{}
|
||||
conn = stats_wrapper.WrapConn(conn, &pStats)
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
req, err := http.ReadRequest(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
ro.HTTP = &xrecorder.HTTPRecorderObject{
|
||||
Host: req.Host,
|
||||
Proto: req.Proto,
|
||||
Scheme: req.URL.Scheme,
|
||||
Method: req.Method,
|
||||
URI: req.RequestURI,
|
||||
Request: xrecorder.HTTPRequestRecorderObject{
|
||||
ContentLength: req.ContentLength,
|
||||
Header: req.Header.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
ro.Time = time.Time{}
|
||||
|
||||
if err := ep.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), req, ro, &pStats, log); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
pStats.Reset()
|
||||
|
||||
req, err := http.ReadRequest(br)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if err := ep.httpRoundTrip(ctx, xio.NewReadWriteCloser(br, conn, conn), req, ro, &pStats, log); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriteCloser, req *http.Request, ro *xrecorder.HandlerRecorderObject, pStats stats.Stats, log logger.Logger) (err error) {
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro = ro2
|
||||
|
||||
if sid := req.Header.Get(httpHeaderSID); sid != "" {
|
||||
ro.SID = sid
|
||||
} else {
|
||||
req.Header.Set(httpHeaderSID, ro.SID)
|
||||
}
|
||||
|
||||
// Loop detection: if Gost-Forwarded-Node contains our own node ID,
|
||||
// the request has already passed through this entrypoint and is looping.
|
||||
for _, node := range strings.Split(req.Header.Get(httpHeaderForwardedNode), ",") {
|
||||
if strings.TrimSpace(node) == ep.node {
|
||||
log.Warn("forwarding loop detected, rejecting request")
|
||||
res := &http.Response{
|
||||
ProtoMajor: req.ProtoMajor,
|
||||
ProtoMinor: req.ProtoMinor,
|
||||
Header: http.Header{},
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
ro.HTTP.StatusCode = res.StatusCode
|
||||
res.Write(rw)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
req.Header.Set(httpHeaderForwardedNode, ro.Node)
|
||||
|
||||
host := req.Host
|
||||
if _, port, _ := net.SplitHostPort(host); port == "" {
|
||||
host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
|
||||
}
|
||||
ro.Host = host
|
||||
ro.Time = time.Now()
|
||||
|
||||
log = log.WithFields(map[string]any{
|
||||
"host": host,
|
||||
})
|
||||
log.Infof("%s <-> %s", ro.RemoteAddr, req.Host)
|
||||
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(ro.Time)
|
||||
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
|
||||
log.WithFields(map[string]any{
|
||||
"duration": time.Since(ro.Time),
|
||||
"inputBytes": ro.InputBytes,
|
||||
"outputBytes": ro.OutputBytes,
|
||||
}).Infof("%s >-< %s", ro.RemoteAddr, req.Host)
|
||||
}()
|
||||
|
||||
if req.URL.Scheme == "" {
|
||||
req.URL.Scheme = "http"
|
||||
}
|
||||
if req.URL.Host == "" {
|
||||
req.URL.Host = req.Host
|
||||
}
|
||||
|
||||
ro.HTTP = &xrecorder.HTTPRecorderObject{
|
||||
Host: req.Host,
|
||||
Proto: req.Proto,
|
||||
Scheme: req.URL.Scheme,
|
||||
Method: req.Method,
|
||||
URI: req.RequestURI,
|
||||
Request: xrecorder.HTTPRequestRecorderObject{
|
||||
ContentLength: req.ContentLength,
|
||||
Header: req.Header.Clone(),
|
||||
},
|
||||
}
|
||||
|
||||
res := &http.Response{
|
||||
ProtoMajor: req.ProtoMajor,
|
||||
ProtoMinor: req.ProtoMinor,
|
||||
Header: http.Header{},
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
}
|
||||
ro.HTTP.StatusCode = res.StatusCode
|
||||
|
||||
var reqBody *xhttp.Body
|
||||
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
|
||||
if req.Body != nil {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = sniffing.DefaultBodySize
|
||||
}
|
||||
if bodySize > sniffing.MaxBodySize {
|
||||
bodySize = sniffing.MaxBodySize
|
||||
}
|
||||
reqBody = xhttp.NewBody(req.Body, bodySize)
|
||||
req.Body = reqBody
|
||||
}
|
||||
}
|
||||
|
||||
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
|
||||
ro.ClientIP = clientIP.String()
|
||||
ctx = xctx.ContextWithSrcAddr(ctx, (&net.TCPAddr{IP: clientIP}))
|
||||
}
|
||||
|
||||
ctx = ictx.ContextWithRecorderObject(ctx, ro)
|
||||
ctx = ictx.ContextWithLogger(ctx, log)
|
||||
|
||||
resp, err := ep.transport.RoundTrip(req.WithContext(ctx))
|
||||
|
||||
if reqBody != nil {
|
||||
ro.HTTP.Request.Body = reqBody.Content()
|
||||
ro.HTTP.Request.ContentLength = reqBody.Length()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrTunnelRoute) || errors.Is(err, ErrPrivateTunnel) {
|
||||
res.StatusCode = http.StatusBadGateway
|
||||
ro.HTTP.StatusCode = http.StatusBadGateway
|
||||
}
|
||||
res.Write(rw)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ro.HTTP.StatusCode = resp.StatusCode
|
||||
ro.HTTP.Response.Header = resp.Header
|
||||
ro.HTTP.Response.ContentLength = resp.ContentLength
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusSwitchingProtocols {
|
||||
return ep.handleUpgradeResponse(ctx, rw, req, resp, ro, log)
|
||||
}
|
||||
|
||||
var respBody *xhttp.Body
|
||||
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = sniffing.DefaultBodySize
|
||||
}
|
||||
if bodySize > sniffing.MaxBodySize {
|
||||
bodySize = sniffing.MaxBodySize
|
||||
}
|
||||
respBody = xhttp.NewBody(resp.Body, bodySize)
|
||||
resp.Body = respBody
|
||||
}
|
||||
|
||||
err = resp.Write(rw)
|
||||
|
||||
if respBody != nil {
|
||||
ro.HTTP.Response.Body = respBody.Content()
|
||||
ro.HTTP.Response.ContentLength = respBody.Length()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("write response: %v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func upgradeType(h http.Header) string {
|
||||
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||
return ""
|
||||
}
|
||||
return h.Get("Upgrade")
|
||||
}
|
||||
|
||||
func (ep *entrypoint) handleUpgradeResponse(ctx context.Context, rw io.ReadWriteCloser, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
||||
reqUpType := upgradeType(req.Header)
|
||||
resUpType := upgradeType(res.Header)
|
||||
if !strings.EqualFold(reqUpType, resUpType) {
|
||||
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
|
||||
}
|
||||
|
||||
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||
if !ok {
|
||||
return fmt.Errorf("internal error: 101 switching protocols response with non-writable body")
|
||||
}
|
||||
defer backConn.Close()
|
||||
|
||||
res.Body = nil
|
||||
if err := res.Write(rw); err != nil {
|
||||
return fmt.Errorf("response write: %v", err)
|
||||
}
|
||||
|
||||
if reqUpType == "websocket" && ep.sniffingWebsocket {
|
||||
return ep.sniffingWebsocketFrame(ctx, rw, backConn, ro, log)
|
||||
}
|
||||
|
||||
// return xnet.Transport(rw, backConn)
|
||||
return xnet.Pipe(ctx, rw, backConn)
|
||||
}
|
||||
|
||||
func (ep *entrypoint) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriteCloser, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
||||
errc := make(chan error, 2)
|
||||
|
||||
sampleRate := ep.websocketSampleRate
|
||||
if sampleRate == 0 {
|
||||
sampleRate = sniffing.DefaultSampleRate
|
||||
}
|
||||
if sampleRate < 0 {
|
||||
sampleRate = math.MaxFloat64
|
||||
}
|
||||
|
||||
go func() {
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro := ro2
|
||||
|
||||
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for {
|
||||
start := time.Now()
|
||||
|
||||
if err := ep.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
if limiter.Allow() {
|
||||
ro.Duration = time.Since(start)
|
||||
ro.Time = time.Now()
|
||||
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
ro2 := &xrecorder.HandlerRecorderObject{}
|
||||
*ro2 = *ro
|
||||
ro := ro2
|
||||
|
||||
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
for {
|
||||
start := time.Now()
|
||||
|
||||
if err := ep.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
|
||||
if limiter.Allow() {
|
||||
ro.Duration = time.Since(start)
|
||||
ro.Time = time.Now()
|
||||
if err := ro.Record(ctx, ep.recorder.Recorder); err != nil {
|
||||
log.Errorf("record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
<-errc
|
||||
rw.Close()
|
||||
cc.Close()
|
||||
<-errc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *entrypoint) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
|
||||
fr := ws_util.Frame{}
|
||||
if _, err = fr.ReadFrom(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ws := &xrecorder.WebsocketRecorderObject{
|
||||
From: from,
|
||||
Fin: fr.Header.Fin,
|
||||
Rsv1: fr.Header.Rsv1,
|
||||
Rsv2: fr.Header.Rsv2,
|
||||
Rsv3: fr.Header.Rsv3,
|
||||
OpCode: int(fr.Header.OpCode),
|
||||
Masked: fr.Header.Masked,
|
||||
MaskKey: fr.Header.MaskKey,
|
||||
Length: fr.Header.PayloadLength,
|
||||
}
|
||||
if opts := ep.recorder.Options; opts != nil && opts.HTTPBody {
|
||||
bodySize := opts.MaxBodySize
|
||||
if bodySize <= 0 {
|
||||
bodySize = sniffing.DefaultBodySize
|
||||
}
|
||||
if bodySize > sniffing.MaxBodySize {
|
||||
bodySize = sniffing.MaxBodySize
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
|
||||
return err
|
||||
}
|
||||
ws.Payload = buf.Bytes()
|
||||
}
|
||||
|
||||
ro.Websocket = ws
|
||||
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
|
||||
if from == "client" {
|
||||
ro.InputBytes = length
|
||||
ro.OutputBytes = 0
|
||||
} else {
|
||||
ro.InputBytes = 0
|
||||
ro.OutputBytes = length
|
||||
}
|
||||
|
||||
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
|
||||
if _, err := fr.WriteTo(w); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ep *entrypoint) HandleTLS(ctx context.Context, conn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
|
||||
buf := new(bytes.Buffer)
|
||||
clientHello, err := dissector.ParseClientHello(io.TeeReader(conn, buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ro.TLS = &xrecorder.TLSRecorderObject{
|
||||
ServerName: clientHello.ServerName,
|
||||
ClientHello: hex.EncodeToString(buf.Bytes()),
|
||||
}
|
||||
if len(clientHello.SupportedProtos) > 0 {
|
||||
ro.TLS.Proto = clientHello.SupportedProtos[0]
|
||||
}
|
||||
|
||||
host := clientHello.ServerName
|
||||
if host != "" {
|
||||
if _, _, err := net.SplitHostPort(host); err != nil {
|
||||
host = net.JoinHostPort(strings.Trim(host, "[]"), "443")
|
||||
}
|
||||
ro.Host = host
|
||||
}
|
||||
|
||||
// ctx = xctx.ContextWithClientAddr(ctx, xctx.ClientAddr(ro.RemoteAddr))
|
||||
ctx = ictx.ContextWithRecorderObject(ctx, ro)
|
||||
ctx = ictx.ContextWithLogger(ctx, log)
|
||||
|
||||
cc, err := ep.dial(ctx, "tcp", host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
if _, err := buf.WriteTo(cc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xio.SetReadDeadline(cc, time.Now().Add(ep.readTimeout))
|
||||
serverHello, err := dissector.ParseServerHello(io.TeeReader(cc, buf))
|
||||
xio.SetReadDeadline(cc, time.Time{})
|
||||
|
||||
if serverHello != nil {
|
||||
ro.TLS.CipherSuite = tls_util.CipherSuite(serverHello.CipherSuite).String()
|
||||
ro.TLS.CompressionMethod = serverHello.CompressionMethod
|
||||
if serverHello.Proto != "" {
|
||||
ro.TLS.Proto = serverHello.Proto
|
||||
}
|
||||
if serverHello.Version > 0 {
|
||||
ro.TLS.Version = tls_util.Version(serverHello.Version).String()
|
||||
}
|
||||
}
|
||||
|
||||
if buf.Len() > 0 {
|
||||
ro.TLS.ServerHello = hex.EncodeToString(buf.Bytes())
|
||||
}
|
||||
|
||||
if _, err := buf.WriteTo(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// xnet.Transport(conn, cc)
|
||||
xnet.Pipe(ctx, conn, cc)
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
af := &relay.AddrFeature{}
|
||||
af.ParseFrom(srcAddr)
|
||||
resp.Features = append(resp.Features, af) // src address
|
||||
|
||||
af = &relay.AddrFeature{}
|
||||
af.ParseFrom(dstAddr)
|
||||
resp.Features = append(resp.Features, af) // dst address
|
||||
|
||||
resp.WriteTo(cc)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type tcpListener struct {
|
||||
ln net.Listener
|
||||
options listener.Options
|
||||
}
|
||||
|
||||
func newTCPListener(ln net.Listener, opts ...listener.Option) listener.Listener {
|
||||
options := listener.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
return &tcpListener{
|
||||
ln: ln,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *tcpListener) Init(md md.Metadata) (err error) {
|
||||
// l.logger.Debugf("pp: %d", l.options.ProxyProtocol)
|
||||
ln := l.ln
|
||||
ln = proxyproto.WrapListener(l.options.ProxyProtocol, ln, 10*time.Second)
|
||||
ln = metrics.WrapListener(l.options.Service, ln)
|
||||
ln = admission.WrapListener(l.options.Service, l.options.Admission, ln)
|
||||
ln = climiter.WrapListener(l.options.ConnLimiter, ln)
|
||||
l.ln = ln
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (l *tcpListener) Accept() (conn net.Conn, err error) {
|
||||
return l.ln.Accept()
|
||||
}
|
||||
|
||||
func (l *tcpListener) Addr() net.Addr {
|
||||
return l.ln.Addr()
|
||||
}
|
||||
|
||||
func (l *tcpListener) Close() error {
|
||||
return l.ln.Close()
|
||||
}
|
||||
|
||||
type entrypointHandler struct {
|
||||
ep *entrypoint
|
||||
}
|
||||
|
||||
func (h *entrypointHandler) Init(md md.Metadata) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (h *entrypointHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) error {
|
||||
return h.ep.Handle(ctx, conn)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user