MINOR: mux-quic: adjust local error API
When a fatal error is detected by the QUIC MUX or H3 layer, the
connection should be closed with a CONNECTION_CLOSE with an error code
as the reason.
Previously, a direct call was used to the quic_conn layer to try to
close the connection. This API was adjusted to be more flexible. Now,
when an error is detected, the function qcc_set_error() is called. This
set the flag QC_CF_ERRL with the error code stored by the MUX. The
connection will be closed soon so most of the operations are not
conducted anymore. Connection is then finally closed during qc_send()
via quic_conn layer if QC_CF_ERRL is set. This will set the flag
QC_CF_ERRL_DONE which indicates that the MUX instance can be freed.
This model is cleaner and brings the following improvments :
- interaction with quic_conn layer for closure is centralized on a
single function
- CO_FL_ERROR is not set anymore. This was incorrect as this should be
reserved to errors reported by the transport layer to be similar with
other haproxy components. As a consequence, qcc_is_dead() has been
adjusted to check for QC_CF_ERRL_DONE to release the MUX instance.
This should be backported up to 2.7.
diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h
index a4bae20..327d347 100644
--- a/include/haproxy/mux_quic-t.h
+++ b/include/haproxy/mux_quic-t.h
@@ -13,6 +13,7 @@
#include <haproxy/htx-t.h>
#include <haproxy/list-t.h>
#include <haproxy/ncbuf-t.h>
+#include <haproxy/quic_frame-t.h>
#include <haproxy/quic_stream-t.h>
#include <haproxy/stconn-t.h>
@@ -27,10 +28,11 @@
QCS_MAX_TYPES
};
-#define QC_CF_CC_EMIT 0x00000001 /* A CONNECTION_CLOSE is set by the MUX */
-#define QC_CF_BLK_MFCTL 0x00000002 /* sending blocked due to connection flow-control */
-#define QC_CF_CONN_FULL 0x00000004 /* no stream buffers available on connection */
-#define QC_CF_APP_SHUT 0x00000008 /* Application layer shutdown done. */
+#define QC_CF_ERRL 0x00000001 /* fatal error detected locally, connection should be closed soon */
+#define QC_CF_ERRL_DONE 0x00000002 /* local error properly handled, connection can be released */
+#define QC_CF_BLK_MFCTL 0x00000004 /* sending blocked due to connection flow-control */
+#define QC_CF_CONN_FULL 0x00000008 /* no stream buffers available on connection */
+#define QC_CF_APP_SHUT 0x00000010 /* Application layer shutdown done. */
struct qcc {
struct connection *conn;
@@ -107,6 +109,7 @@
int timeout;
int shut_timeout;
int idle_start; /* base time for http-keep-alive timeout */
+ struct quic_err err; /* code for locally detected error */
const struct qcc_app_ops *app_ops;
void *ctx; /* Application layer context */
diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h
index 501a27a..e425e61 100644
--- a/include/haproxy/mux_quic.h
+++ b/include/haproxy/mux_quic.h
@@ -12,6 +12,7 @@
#include <haproxy/mux_quic-t.h>
#include <haproxy/stream.h>
+void qcc_set_error(struct qcc *qcc, int err);
struct qcs *qcc_init_stream_local(struct qcc *qcc, int bidi);
struct buffer *qc_get_buf(struct qcs *qcs, struct buffer *bptr);
diff --git a/src/h3.c b/src/h3.c
index 09099ec..a364212 100644
--- a/src/h3.c
+++ b/src/h3.c
@@ -38,6 +38,7 @@
#include <haproxy/qpack-enc.h>
#include <haproxy/quic_conn-t.h>
#include <haproxy/quic_enc.h>
+#include <haproxy/quic_frame.h>
#include <haproxy/stats-t.h>
#include <haproxy/tools.h>
#include <haproxy/trace.h>
@@ -190,7 +191,7 @@
case H3_UNI_S_T_CTRL:
if (h3c->flags & H3_CF_UNI_CTRL_SET) {
TRACE_ERROR("duplicated control stream", H3_EV_H3S_NEW, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_STREAM_CREATION_ERROR, 1);
+ qcc_set_error(qcs->qcc, H3_STREAM_CREATION_ERROR);
goto err;
}
h3c->flags |= H3_CF_UNI_CTRL_SET;
@@ -205,7 +206,7 @@
case H3_UNI_S_T_QPACK_DEC:
if (h3c->flags & H3_CF_UNI_QPACK_DEC_SET) {
TRACE_ERROR("duplicated qpack decoder stream", H3_EV_H3S_NEW, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_STREAM_CREATION_ERROR, 1);
+ qcc_set_error(qcs->qcc, H3_STREAM_CREATION_ERROR);
goto err;
}
h3c->flags |= H3_CF_UNI_QPACK_DEC_SET;
@@ -216,7 +217,7 @@
case H3_UNI_S_T_QPACK_ENC:
if (h3c->flags & H3_CF_UNI_QPACK_ENC_SET) {
TRACE_ERROR("duplicated qpack encoder stream", H3_EV_H3S_NEW, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_STREAM_CREATION_ERROR, 1);
+ qcc_set_error(qcs->qcc, H3_STREAM_CREATION_ERROR);
goto err;
}
h3c->flags |= H3_CF_UNI_QPACK_ENC_SET;
@@ -1030,7 +1031,7 @@
*/
if (h3s->type == H3S_T_CTRL && fin) {
TRACE_ERROR("control stream closed by remote peer", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_CLOSED_CRITICAL_STREAM, 1);
+ qcc_set_error(qcs->qcc, H3_CLOSED_CRITICAL_STREAM);
goto err;
}
@@ -1066,7 +1067,7 @@
if (!h3_is_frame_valid(h3c, qcs, ftype)) {
TRACE_ERROR("received an invalid frame", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_FRAME_UNEXPECTED, 1);
+ qcc_set_error(qcs->qcc, H3_FRAME_UNEXPECTED);
goto err;
}
@@ -1089,7 +1090,7 @@
*/
if (flen > QC_S_RX_BUF_SZ) {
TRACE_ERROR("received a too big frame", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_EXCESSIVE_LOAD, 1);
+ qcc_set_error(qcs->qcc, H3_EXCESSIVE_LOAD);
goto err;
}
break;
@@ -1128,7 +1129,7 @@
ret = h3_parse_settings_frm(qcs->qcc->ctx, b, flen);
if (ret < 0) {
TRACE_ERROR("error on SETTINGS parsing", H3_EV_RX_FRAME, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, h3c->err, 1);
+ qcc_set_error(qcs->qcc, h3c->err);
goto err;
}
h3c->flags |= H3_CF_SETTINGS_RECV;
@@ -1162,7 +1163,7 @@
return b_data(b);
}
else if (h3c->err) {
- qcc_emit_cc_app(qcs->qcc, h3c->err, 1);
+ qcc_set_error(qcs->qcc, h3c->err);
return b_data(b);
}
@@ -1669,7 +1670,7 @@
*/
if (qcs == h3c->ctrl_strm || h3s->type == H3S_T_CTRL) {
TRACE_ERROR("closure detected on control stream", H3_EV_H3S_END, qcs->qcc->conn, qcs);
- qcc_emit_cc_app(qcs->qcc, H3_CLOSED_CRITICAL_STREAM, 1);
+ qcc_set_error(qcs->qcc, H3_CLOSED_CRITICAL_STREAM);
return 1;
}
@@ -1870,7 +1871,7 @@
* graceful shutdown SHOULD use the H3_NO_ERROR error code when closing
* the connection.
*/
- qcc_emit_cc_app(h3c->qcc, H3_NO_ERROR, 0);
+ h3c->qcc->err = quic_err_app(H3_NO_ERROR);
TRACE_LEAVE(H3_EV_H3C_END, h3c->qcc->conn);
}
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 3ed1c13..dc02306 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -24,24 +24,6 @@
DECLARE_POOL(pool_head_qcc, "qcc", sizeof(struct qcc));
DECLARE_POOL(pool_head_qcs, "qcs", sizeof(struct qcs));
-/* Emit a CONNECTION_CLOSE with error <err>. This will interrupt all future
- * send/receive operations.
- */
-static void qcc_emit_cc(struct qcc *qcc, int err)
-{
- TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
-
- /* This function must not be called multiple times. */
- BUG_ON(qcc->flags & QC_CF_CC_EMIT);
-
- TRACE_STATE("set CONNECTION_CLOSE on quic-conn", QMUX_EV_QCC_ERR, qcc->conn);
- quic_set_connection_close(qcc->conn->handle.qc, quic_err_transport(err));
- qcc->flags |= QC_CF_CC_EMIT;
- tasklet_wakeup(qcc->wait_event.tasklet);
-
- TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
-}
-
static void qc_free_ncbuf(struct qcs *qcs, struct ncbuf *ncbuf)
{
struct buffer buf;
@@ -225,13 +207,19 @@
static inline int qcc_is_dead(const struct qcc *qcc)
{
- /* Mux connection is considered dead if :
- * - all stream-desc are detached AND
- * = connection is on error OR
- * = mux timeout has already fired or is unset
+ /* Maintain connection if stream endpoints are still active. */
+ if (qcc->nb_sc)
+ return 0;
+
+ /* Connection considered dead if either :
+ * - remote error detected at tranport level
+ * - error detected locally
+ * - MUX timeout expired or unset
*/
- if (!qcc->nb_sc && ((qcc->conn->flags & CO_FL_ERROR) || !qcc->task))
+ if (qcc->conn->flags & CO_FL_ERROR || qcc->flags & QC_CF_ERRL_DONE ||
+ !qcc->task) {
return 1;
+ }
return 0;
}
@@ -508,6 +496,21 @@
}
}
+/* A fatal error is detected locally for <qcc> connection. It should be closed
+ * with a CONNECTION_CLOSE using <err> code. This function must not be called
+ * more than once by connection.
+ */
+void qcc_set_error(struct qcc *qcc, int err)
+{
+ /* This must not be called multiple times per connection. */
+ BUG_ON(qcc->flags & QC_CF_ERRL);
+
+ TRACE_STATE("connection on error", QMUX_EV_QCC_ERR, qcc->conn);
+
+ qcc->flags |= QC_CF_ERRL;
+ qcc->err = quic_err_app(err);
+}
+
/* Open a locally initiated stream for the connection <qcc>. Set <bidi> for a
* bidirectional stream, else an unidirectional stream is opened. The next
* available ID on the connection will be used according to the stream type.
@@ -538,7 +541,7 @@
qcs = qcs_new(qcc, *next, type);
if (!qcs) {
TRACE_LEAVE(QMUX_EV_QCS_NEW, qcc->conn);
- qcc_emit_cc(qcc, QC_ERR_INTERNAL_ERROR);
+ qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR);
return NULL;
}
@@ -585,7 +588,7 @@
qcc->lfctl.ms_uni * 4;
if (id >= max_id) {
TRACE_ERROR("flow control error", QMUX_EV_QCS_NEW|QMUX_EV_PROTO_ERR, qcc->conn);
- qcc_emit_cc(qcc, QC_ERR_STREAM_LIMIT_ERROR);
+ qcc_set_error(qcc, QC_ERR_STREAM_LIMIT_ERROR);
goto err;
}
@@ -599,7 +602,7 @@
qcs = qcs_new(qcc, *largest, type);
if (!qcs) {
TRACE_ERROR("stream fallocation failure", QMUX_EV_QCS_NEW, qcc->conn);
- qcc_emit_cc(qcc, QC_ERR_INTERNAL_ERROR);
+ qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR);
goto err;
}
@@ -658,13 +661,13 @@
if (!receive_only && quic_stream_is_uni(id) && quic_stream_is_remote(qcc, id)) {
TRACE_ERROR("receive-only stream not allowed", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
- qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR);
+ qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR);
goto err;
}
if (!send_only && quic_stream_is_uni(id) && quic_stream_is_local(qcc, id)) {
TRACE_ERROR("send-only stream not allowed", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
- qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR);
+ qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR);
goto err;
}
@@ -696,7 +699,7 @@
* stream.
*/
TRACE_ERROR("locally initiated stream not yet created", QMUX_EV_QCC_RECV|QMUX_EV_QCC_NQCS|QMUX_EV_PROTO_ERR, qcc->conn, NULL, &id);
- qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR);
+ qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR);
goto err;
}
else {
@@ -752,7 +755,7 @@
TRACE_DATA("increase stream credit via MAX_STREAM_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
frm = qc_frm_alloc(QUIC_FT_MAX_STREAM_DATA);
if (!frm) {
- qcc_emit_cc(qcc, QC_ERR_INTERNAL_ERROR);
+ qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR);
return;
}
@@ -771,7 +774,7 @@
TRACE_DATA("increase conn credit via MAX_DATA", QMUX_EV_QCS_RECV, qcc->conn, qcs);
frm = qc_frm_alloc(QUIC_FT_MAX_DATA);
if (!frm) {
- qcc_emit_cc(qcc, QC_ERR_INTERNAL_ERROR);
+ qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR);
return;
}
@@ -830,38 +833,6 @@
return 1;
}
-/* Emit a CONNECTION_CLOSE_APP with error <err>. Reserved for application error
- * code. To close the connection right away, set <immediate> : this is useful
- * when dealing with a connection fatal error. Else a graceful shutdown will be
- * conducted : the error-code is only registered. The lower layer is
- * responsible to close the connection when deemed suitable. Note that in this
- * case the error code might be overwritten if an immediate close is requested
- * in the interval.
- */
-void qcc_emit_cc_app(struct qcc *qcc, int err, int immediate)
-{
- TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
-
- /* This function must not be called multiple times after immediate is set. */
- BUG_ON(qcc->flags & QC_CF_CC_EMIT);
-
- if (immediate) {
- quic_set_connection_close(qcc->conn->handle.qc, quic_err_app(err));
- qcc->flags |= QC_CF_CC_EMIT;
- tasklet_wakeup(qcc->wait_event.tasklet);
- }
- else {
- /* Only register the error code for graceful shutdown.
- * Do not overwrite quic-conn existing code if already set.
- * TODO implement a wrapper function for this in quic-conn module
- */
- if (!(qcc->conn->handle.qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE))
- qcc->conn->handle.qc->err = quic_err_app(err);
- }
-
- TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
-}
-
/* Prepare for the emission of RESET_STREAM on <qcs> with error code <err>. */
void qcc_reset_stream(struct qcs *qcs, int err)
{
@@ -985,8 +956,8 @@
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
- if (qcc->flags & QC_CF_CC_EMIT) {
- TRACE_DATA("connection closed", QMUX_EV_QCC_RECV, qcc->conn);
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
goto err;
}
@@ -1018,7 +989,7 @@
if (qcs->flags & QC_SF_SIZE_KNOWN &&
(offset + len > qcs->rx.offset_max || (fin && offset + len < qcs->rx.offset_max))) {
TRACE_ERROR("final size error", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR, qcc->conn, qcs);
- qcc_emit_cc(qcc, QC_ERR_FINAL_SIZE_ERROR);
+ qcc_set_error(qcc, QC_ERR_FINAL_SIZE_ERROR);
goto err;
}
@@ -1051,7 +1022,7 @@
*/
TRACE_ERROR("flow control error", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR,
qcc->conn, qcs);
- qcc_emit_cc(qcc, QC_ERR_FLOW_CONTROL_ERROR);
+ qcc_set_error(qcc, QC_ERR_FLOW_CONTROL_ERROR);
goto err;
}
}
@@ -1090,7 +1061,7 @@
*/
TRACE_ERROR("overlapping data rejected", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV|QMUX_EV_PROTO_ERR,
qcc->conn, qcs);
- qcc_emit_cc(qcc, QC_ERR_PROTOCOL_VIOLATION);
+ qcc_set_error(qcc, QC_ERR_PROTOCOL_VIOLATION);
return 1;
case NCB_RET_GAP_SIZE:
@@ -1158,8 +1129,8 @@
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
- if (qcc->flags & QC_CF_CC_EMIT) {
- TRACE_DATA("connection closed", QMUX_EV_QCC_RECV, qcc->conn);
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
goto err;
}
@@ -1211,8 +1182,8 @@
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
- if (qcc->flags & QC_CF_CC_EMIT) {
- TRACE_DATA("connection closed", QMUX_EV_QCC_RECV, qcc->conn);
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
goto err;
}
@@ -1223,7 +1194,7 @@
*/
if (qcc_get_qcs(qcc, id, 1, 0, &qcs)) {
TRACE_ERROR("RESET_STREAM for send-only stream received", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
- qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR);
+ qcc_set_error(qcc, QC_ERR_STREAM_STATE_ERROR);
goto err;
}
@@ -1243,7 +1214,7 @@
if (qcs->rx.offset_max > final_size ||
((qcs->flags & QC_SF_SIZE_KNOWN) && qcs->rx.offset_max != final_size)) {
TRACE_ERROR("final size error on RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
- qcc_emit_cc(qcc, QC_ERR_FINAL_SIZE_ERROR);
+ qcc_set_error(qcc, QC_ERR_FINAL_SIZE_ERROR);
goto err;
}
@@ -1277,8 +1248,8 @@
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
- if (qcc->flags & QC_CF_CC_EMIT) {
- TRACE_DATA("connection closed", QMUX_EV_QCC_RECV, qcc->conn);
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
goto err;
}
@@ -1370,7 +1341,7 @@
TRACE_DATA("increase max stream limit with MAX_STREAMS_BIDI", QMUX_EV_QCC_SEND, qcc->conn);
frm = qc_frm_alloc(QUIC_FT_MAX_STREAMS_BIDI);
if (!frm) {
- qcc_emit_cc(qcc, QC_ERR_INTERNAL_ERROR);
+ qcc_set_error(qcc, QC_ERR_INTERNAL_ERROR);
goto err;
}
@@ -1414,7 +1385,7 @@
*/
BUG_ON(qcs->tx.offset < qcs->tx.sent_offset);
- if (!(qcc->flags & QC_CF_CC_EMIT)) {
+ if (!(qcc->flags & QC_CF_ERRL)) {
if (quic_stream_is_remote(qcc, id))
qcc_release_remote_stream(qcc, id);
}
@@ -1885,7 +1856,19 @@
TRACE_ENTER(QMUX_EV_QCC_SEND, qcc->conn);
- if (qcc->conn->flags & CO_FL_SOCK_WR_SH || qcc->flags & QC_CF_CC_EMIT) {
+ /* Check for locally detected connection error. */
+ if (qcc->flags & QC_CF_ERRL) {
+ /* Prepare a CONNECTION_CLOSE if not already done. */
+ if (!(qcc->flags & QC_CF_ERRL_DONE)) {
+ TRACE_DATA("report a connection error", QMUX_EV_QCC_SEND|QMUX_EV_QCC_ERR, qcc->conn);
+ quic_set_connection_close(qcc->conn->handle.qc, qcc->err);
+ qcc->flags |= QC_CF_ERRL_DONE;
+ }
+ TRACE_DEVEL("connection on error", QMUX_EV_QCC_SEND, qcc->conn);
+ goto err;
+ }
+
+ if (qcc->conn->flags & CO_FL_SOCK_WR_SH) {
qcc->conn->flags |= CO_FL_ERROR;
TRACE_DEVEL("connection on error", QMUX_EV_QCC_SEND, qcc->conn);
goto err;
@@ -2032,7 +2015,8 @@
TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
- if (qcc->flags & QC_CF_CC_EMIT) {
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_RECV, qcc->conn);
TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
return 0;
}
@@ -2113,19 +2097,33 @@
{
TRACE_ENTER(QMUX_EV_QCC_END, qcc->conn);
- if (qcc->flags & (QC_CF_APP_SHUT|QC_CF_CC_EMIT)) {
- TRACE_DATA("connection closed", QMUX_EV_QCC_END, qcc->conn);
+ if (qcc->flags & QC_CF_ERRL) {
+ TRACE_DATA("connection on error", QMUX_EV_QCC_END, qcc->conn);
goto out;
}
+ if (qcc->flags & QC_CF_APP_SHUT)
+ goto out;
+
+ TRACE_STATE("perform graceful shutdown", QMUX_EV_QCC_END, qcc->conn);
if (qcc->app_ops && qcc->app_ops->shutdown) {
qcc->app_ops->shutdown(qcc->ctx);
qc_send(qcc);
}
else {
- qcc_emit_cc_app(qcc, QC_ERR_NO_ERROR, 0);
+ qcc->err = quic_err_app(QC_ERR_NO_ERROR);
}
+ /* Register "no error" code at transport layer. Do not use
+ * quic_set_connection_close() as retransmission may be performed to
+ * finalized transfers. Do not overwrite quic-conn existing code if
+ * already set.
+ *
+ * TODO implement a wrapper function for this in quic-conn module
+ */
+ if (!(qcc->conn->handle.qc->flags & QUIC_FL_CONN_IMMEDIATE_CLOSE))
+ qcc->conn->handle.qc->err = qcc->err;
+
out:
qcc->flags |= QC_CF_APP_SHUT;
TRACE_LEAVE(QMUX_EV_QCC_END, qcc->conn);
diff --git a/src/qpack-dec.c b/src/qpack-dec.c
index a6e2923..1ae2ef3 100644
--- a/src/qpack-dec.c
+++ b/src/qpack-dec.c
@@ -111,7 +111,7 @@
* connection error of type H3_CLOSED_CRITICAL_STREAM.
*/
if (fin) {
- qcc_emit_cc_app(qcs->qcc, H3_CLOSED_CRITICAL_STREAM, 1);
+ qcc_set_error(qcs->qcc, H3_CLOSED_CRITICAL_STREAM);
return -1;
}
@@ -158,7 +158,7 @@
* connection error of type H3_CLOSED_CRITICAL_STREAM.
*/
if (fin) {
- qcc_emit_cc_app(qcs->qcc, H3_CLOSED_CRITICAL_STREAM, 1);
+ qcc_set_error(qcs->qcc, H3_CLOSED_CRITICAL_STREAM);
return -1;
}