--- /dev/null
+# A CONNECT request ends at its headers, even when the tunnel is subsequently
+# rejected. DATA frames received after the rejection must still carry raw
+# tunnel data, not HTTP payload. Otherwise, transferring them into a request
+# buffer whose message already ended triggers an HTX assertion.
+
+varnishtest "In-flight H2 tunnel data after a CONNECT rejection"
+feature ignore_unknown_macro
+
+barrier b0 cond 2
+barrier b1 cond 2
+
+server s1 {
+ rxreq
+ expect req.method == "CONNECT"
+ barrier b0 sync
+ # Keep the response unfinished while the client sends more tunnel data.
+ txresp -status 403 -hdr "content-length: 6" -nolen
+ barrier b1 sync
+ send "denied"
+ expect_close
+} -start
+
+haproxy hap -conf {
+ global
+ .if feature(THREAD)
+ thread-groups 1
+ .endif
+
+ defaults
+ mode http
+ timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
+ timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+
+ listen fe
+ bind "fd@${fe}" proto h2
+ server s1 "${s1_addr}:${s1_port}"
+} -start
+
+client c1 -connect ${hap_fe_sock} {
+ txpri
+ stream 0 {
+ txsettings
+ rxsettings
+ txsettings -ack
+ rxsettings
+ } -run
+
+ stream 1 {
+ txreq -noadd \
+ -litHdr inc plain ":method" plain "CONNECT" \
+ -litHdr inc plain ":authority" plain "example.com:443" \
+ -nostrend
+ # Leave raw data pending in the request buffer, preserving its EOM.
+ txdata -data "early tunnel data" -nostrend
+ barrier b0 sync
+ rxwinup
+ rxwinup
+ rxhdrs
+ expect resp.status == 403
+
+ # Simulate data already in flight when the rejection was sent.
+ # Wait for its window update before allowing the response to end.
+ txdata -data "late tunnel data" -nostrend
+ rxwinup
+ delay 0.1
+ barrier b1 sync
+ rxdata
+ expect resp.body == "denied"
+ } -run
+} -run
/* Tunneled data must never be mixed with HTTP data. A dedicated block
* type is used for them. This way, the end of the HTTP message, reported
- * just after the headers for a tunneled stream, is preserved.
+ * just after the headers for a tunneled stream, is preserved. On the
+ * request side, this remains true after a tunnel attempt was aborted:
+ * some tunneled data may still be in flight after the early end of the
+ * message was reported. On the response side, an aborted tunnel carries
+ * a regular HTTP response instead.
*/
- if (h2s->flags & H2_SF_BODY_TUNNEL)
+ if ((h2s->flags & H2_SF_BODY_TUNNEL) ||
+ (!(h2c->flags & H2_CF_IS_BACK) && (h2s->flags & H2_SF_TUNNEL_ABRT)))
sent = htx_add_raw_data(htx, ist2(b_head(&h2c->dbuf), flen));
else
sent = htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen));