29a5b16395
The response address sent back to the tunnel client was unconditionally set to the md5 hash of the tunnel ID. This meant the user's custom host (e.g. "dash" from "dash:8081") was ignored — the client listened on the hash instead. Now the user-supplied host is used directly when: 1. A host is present in the bind address AND ingress is configured. 2. No other tunnel has already claimed that host in ingress (conflict check via GetRule). Fall back to the md5 hash when no host is supplied, ingress is nil, or the host conflicts with an existing ingress rule. Also fixes indentation in the WriteTo error handling block (extra tab removed). Tests: - TestHandleBind_CustomHost: response AddrFeature uses "dash", not hash - TestHandleBind_CustomHostConflict: conflicting host falls back to hash - fakeIngress enhanced with ruleByHost map for conflict simulation
141 lines
4.2 KiB
Go
141 lines
4.2 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"net"
|
|
|
|
"github.com/go-gost/core/ingress"
|
|
"github.com/go-gost/core/logger"
|
|
"github.com/go-gost/core/observer/stats"
|
|
"github.com/go-gost/core/sd"
|
|
"github.com/go-gost/relay"
|
|
"github.com/go-gost/x/internal/util/mux"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// handleBind handles a CmdBind request from an internal client.
|
|
//
|
|
// The bind address host is resolved as follows:
|
|
// 1. If the user supplied a host (e.g. "dash" from "dash:8081") AND
|
|
// ingress is configured, use that host directly — the tunnel is
|
|
// reachable via the user's custom name.
|
|
// 2. Before using the custom host, check for ingress conflicts: if
|
|
// another tunnel already owns this hostname, fall back to the
|
|
// md5 hash to avoid route hijacking.
|
|
// 3. If no host was supplied (e.g. ":8081") or ingress is nil, use
|
|
// the deterministic md5 hash of tunnelID as a stable ingress key.
|
|
//
|
|
// Flow:
|
|
// 1. Generate a random connector ID (copies weight from tunnelID).
|
|
// 2. Resolve the response address host per the rules above.
|
|
// 3. Send relay response with the bind address + tunnel features back.
|
|
// 4. Upgrade the TCP connection to a mux.ClientSession (smux).
|
|
// 5. Create a Connector wrapping the mux session.
|
|
// 6. Register:
|
|
// a. Add Connector to ConnectorPool (under tunnelID).
|
|
// b. Set ingress rules: the resolved host → tunnelID. When the user
|
|
// supplied a custom host that differs from the hash, also set a
|
|
// fallback rule: hash → tunnelID.
|
|
// c. If SD is configured, register the service (tunnelID, node, network).
|
|
//
|
|
// The mux session ownership is transferred to the Connector — conn is NOT
|
|
// closed after this function returns (no defer conn.Close()). The Connector's
|
|
// waitClose goroutine handles session lifecycle.
|
|
func (h *tunnelHandler) handleBind(ctx context.Context, conn net.Conn, network, address string, tunnelID relay.TunnelID, log logger.Logger) (err error) {
|
|
resp := relay.Response{
|
|
Version: relay.Version1,
|
|
Status: relay.StatusOK,
|
|
}
|
|
|
|
uuid, err := uuid.NewRandom()
|
|
if err != nil {
|
|
resp.Status = relay.StatusInternalServerError
|
|
resp.WriteTo(conn)
|
|
return
|
|
}
|
|
connectorID := relay.NewConnectorID(uuid[:])
|
|
if network == "udp" {
|
|
connectorID = relay.NewUDPConnectorID(uuid[:])
|
|
}
|
|
// copy weight from tunnelID
|
|
connectorID = connectorID.SetWeight(tunnelID.Weight())
|
|
|
|
v := md5.Sum([]byte(tunnelID.String()))
|
|
endpoint := hex.EncodeToString(v[:8])
|
|
|
|
host, port, _ := net.SplitHostPort(address)
|
|
if host == "" || h.md.ingress == nil {
|
|
host = endpoint
|
|
} else if host != endpoint {
|
|
if rule := h.md.ingress.GetRule(ctx, host, ingress.WithService(h.options.Service)); rule != nil && rule.Endpoint != tunnelID.String() {
|
|
host = endpoint
|
|
}
|
|
}
|
|
addr := net.JoinHostPort(host, port)
|
|
|
|
af := &relay.AddrFeature{}
|
|
err = af.ParseFrom(addr)
|
|
if err != nil {
|
|
log.Warn(err)
|
|
}
|
|
resp.Features = append(resp.Features, af,
|
|
&relay.TunnelFeature{
|
|
ID: connectorID,
|
|
},
|
|
)
|
|
if _, err = resp.WriteTo(conn); err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
// Upgrade connection to multiplex session.
|
|
session, err := mux.ClientSession(conn, h.md.muxCfg)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var stats stats.Stats
|
|
if h.stats != nil {
|
|
stats = h.stats.Stats(tunnelID.String())
|
|
}
|
|
|
|
c := NewConnector(connectorID, tunnelID, h.id, session, &ConnectorOptions{
|
|
service: h.options.Service,
|
|
sd: h.md.sd,
|
|
stats: stats,
|
|
limiter: h.limiter,
|
|
})
|
|
|
|
h.pool.Add(tunnelID, c, h.md.tunnelTTL)
|
|
if h.md.ingress != nil {
|
|
h.md.ingress.SetRule(ctx, &ingress.Rule{
|
|
Hostname: endpoint,
|
|
Endpoint: tunnelID.String(),
|
|
}, ingress.WithService(h.options.Service))
|
|
if host != "" && host != endpoint {
|
|
h.md.ingress.SetRule(ctx, &ingress.Rule{
|
|
Hostname: host,
|
|
Endpoint: tunnelID.String(),
|
|
}, ingress.WithService(h.options.Service))
|
|
}
|
|
}
|
|
if h.md.sd != nil {
|
|
err := h.md.sd.Register(ctx, &sd.Service{
|
|
ID: connectorID.String(),
|
|
Name: tunnelID.String(),
|
|
Node: h.id,
|
|
Network: network,
|
|
Address: h.md.entryPoint,
|
|
})
|
|
if err != nil {
|
|
h.log.Error(err)
|
|
}
|
|
}
|
|
|
|
log.Debugf("%s/%s: tunnel=%s, connector=%s, weight=%d established", addr, network, tunnelID, connectorID, connectorID.Weight())
|
|
|
|
return
|
|
}
|