k3s-io/k3s · error

hijacking not supported

Error message

hijacking not supported

What it means

serveConnect handles HTTP CONNECT for the remotedialer tunnel by raw TCP splicing: it needs http.Hijacker to take over the connection from the server. If the ResponseWriter does not implement Hijacker (HTTP/2 connections, or middleware that wraps ResponseWriter without forwarding the interface), hijack is impossible and the proxy request fails.

Source

Thrown at pkg/daemons/control/tunnel.go:187

				}
			}
		}
	}
	return pod, nil
}

// serveConnect attempts to handle the HTTP CONNECT request by dialing
// a connection, either locally or via the remotedialer tunnel.
func (t *TunnelServer) serveConnect(resp http.ResponseWriter, req *http.Request) {
	bconn, err := t.dialBackend(req.Context(), req.Host)
	if err != nil {
		util.SendError(err, resp, req, http.StatusBadGateway)
		return
	}

	hijacker, ok := resp.(http.Hijacker)
	if !ok {
		util.SendError(errors.New("hijacking not supported"), resp, req, http.StatusInternalServerError)
		return
	}
	resp.WriteHeader(http.StatusOK)

	rconn, bufrw, err := hijacker.Hijack()
	if err != nil {
		util.SendError(err, resp, req, http.StatusInternalServerError)
		return
	}

	proxy.Proxy(newConnReadWriteCloser(rconn, bufrw), bconn)
}

// dialBackend determines where to route the connection request to, and returns
// a dialed connection if possible. Note that in the case of a remotedialer
// tunnel connection, the agent may return an error if the agent's authorizer
// denies the connection, or if there is some other error in actually dialing
// the requested endpoint.

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Ensure CONNECT traffic reaches the tunnel server over HTTP/1.1: disable HTTP/2 on the fronting proxy for the tunnel routes or connect directly.
  2. Fix any custom wrapping middleware to implement http.Hijacker by delegating to the underlying writer (embed http.ResponseWriter and add Hijack()).
  3. Verify with curl --http1.1 -X CONNECT against the endpoint to isolate the transport.

Example fix

// before: wrapper hides Hijacker
type wrapRW struct{ http.ResponseWriter }
// after: forward the Hijacker interface
type wrapRW struct {
	http.ResponseWriter
}
func (w *wrapRW) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h, ok := w.ResponseWriter.(http.Hijacker)
	if !ok {
		return nil, nil, errors.New("response writer does not support hijacking")
	}
	return h.Hijack()
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Force HTTP/1.1 for CONNECT-bearing clients:
transport := &http.Transport{ForceAttemptHTTP2: false}
client := &http.Client{Transport: transport}

Type guard

// Narrow the ResponseWriter before serving CONNECT-style handlers:
func canHijack(rw http.ResponseWriter) bool {
	_, ok := rw.(http.Hijacker)
	return ok
}
// middleware wrapper that preserves the capability:
type hijackRW struct{ http.ResponseWriter }
func (w hijackRW) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h := w.ResponseWriter.(http.Hijacker)
	return h.Hijack()
}

Try / catch

hijacker, ok := resp.(http.Hijacker)
if !ok {
	// degrade gracefully: 502 with hint to use HTTP/1.1 instead of 500
	http.Error(resp, "connection proxy requires HTTP/1.1 (hijackable connection)", http.StatusBadGateway)
	return
}

Prevention

When it happens

Trigger: A CONNECT request to the tunnel server arriving over HTTP/2 (h2 prior-knowledge or upgraded via TLS ALPN); custom middleware (logging, compression, metrics) wrapping the ResponseWriter in a struct that lacks Hijack(); tests using httptest.ResponseRecorder.

Common situations: Fronting k3s supervisor with an h2-capable proxy that forwards CONNECT; contributed middlewares in forks; clients attempting CONNECT over gRPC-style h2c connections.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/620899219d0500cf. Report an issue: GitHub.