#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>
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;
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;
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;
*/
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;
}
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;
}
*/
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;
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;
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);
}
*/
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;
}
* 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);
}
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;
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;
}
}
}
+/* 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.
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;
}
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;
}
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;
}
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;
}
* 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 {
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;
}
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;
}
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)
{
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;
}
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;
}
*/
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;
}
}
*/
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:
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;
}
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;
}
*/
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;
}
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;
}
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;
}
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;
}
*/
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);
}
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;
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;
}
{
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);