fix: nil HTTP recorder ref in WebSocket goroutines, add tunnel loop detection

- handler/http/websocket, forwarder/sniffer_ws, sniffing/sniffer_ws: nil
  ro2.HTTP in WebSocket copy goroutines to avoid data races on the shared
  HTTP recorder object
- handler/tunnel/bind: remove ingress rule check that incorrectly
  overrode the endpoint host for non-matching ingress rules
- handler/tunnel/entrypoint: add forwarding loop detection by checking
  Gost-Forwarded-Node header for the entrypoint's own node ID
This commit is contained in:
ginuerzh
2026-06-01 18:28:01 +08:00
parent 5a09a48b2d
commit 382d47ea12
5 changed files with 24 additions and 3 deletions
+1
View File
@@ -51,6 +51,7 @@ func (h *httpHandler) copyWebsocketDirection(ctx context.Context, r io.Reader, w
}
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro2.HTTP = nil
r2 := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
+1 -3
View File
@@ -41,9 +41,7 @@ func (h *tunnelHandler) handleBind(ctx context.Context, conn net.Conn, network,
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
}
host = endpoint
}
addr := net.JoinHostPort(host, port)
+18
View File
@@ -278,6 +278,24 @@ func (ep *entrypoint) httpRoundTrip(ctx context.Context, rw io.ReadWriteCloser,
} 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
+2
View File
@@ -31,6 +31,7 @@ func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWrit
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro2.HTTP = nil
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
@@ -57,6 +58,7 @@ func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWrit
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro2.HTTP = nil
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
+2
View File
@@ -30,6 +30,7 @@ func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWrit
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro2.HTTP = nil
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
@@ -56,6 +57,7 @@ func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWrit
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro2.HTTP = nil
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))