int ossl_quic_channel_is_active(const QUIC_CHANNEL *ch);
int ossl_quic_channel_is_handshake_complete(const QUIC_CHANNEL *ch);
int ossl_quic_channel_is_handshake_confirmed(const QUIC_CHANNEL *ch);
+int ossl_quic_channel_is_server(const QUIC_CHANNEL *ch);
+void ossl_quic_channel_notify_flush_done(QUIC_CHANNEL *ch);
QUIC_PORT *ossl_quic_channel_get0_port(QUIC_CHANNEL *ch);
QUIC_ENGINE *ossl_quic_channel_get0_engine(QUIC_CHANNEL *ch);
const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid,
const QUIC_CONN_ID *odcid);
+void ossl_quic_channel_set_tcause(QUIC_CHANNEL *ch, uint64_t app_error_code,
+ const char *app_reason);
# endif
#endif
*/
struct quic_stream_map_st {
LHASH_OF(QUIC_STREAM) *map;
+ QUIC_CHANNEL *ch;
QUIC_STREAM_LIST_NODE active_list;
QUIC_STREAM_LIST_NODE accept_list;
QUIC_STREAM_LIST_NODE ready_for_gc_list;
void *get_stream_limit_cb_arg;
QUIC_RXFC *max_streams_bidi_rxfc;
QUIC_RXFC *max_streams_uni_rxfc;
- int is_server;
};
/*
void *get_stream_limit_cb_arg,
QUIC_RXFC *max_streams_bidi_rxfc,
QUIC_RXFC *max_streams_uni_rxfc,
- int is_server);
+ QUIC_CHANNEL *ch);
/*
* Any streams still in the map will be released as though
if (!ossl_quic_stream_map_init(&ch->qsm, get_stream_limit, ch,
&ch->max_streams_bidi_rxfc,
&ch->max_streams_uni_rxfc,
- ch->is_server))
+ ch))
goto err;
ch->have_qsm = 1;
++pn_space)
ossl_ackm_on_pkt_space_discarded(ch->ackm, pn_space);
- ossl_quic_lcidm_cull(ch->lcidm, ch);
- ossl_quic_srtm_cull(ch->srtm, ch);
+ if (ch->lcidm != NULL)
+ ossl_quic_lcidm_cull(ch->lcidm, ch);
+
+ if (ch->srtm != NULL)
+ ossl_quic_srtm_cull(ch->srtm, ch);
+
ossl_quic_tx_packetiser_free(ch->txp);
ossl_quic_txpim_free(ch->txpim);
ossl_quic_cfq_free(ch->cfq);
|| ossl_quic_channel_is_terminated(ch);
}
+int ossl_quic_channel_is_server(const QUIC_CHANNEL *ch)
+{
+ return ch->is_server;
+}
+
+void ossl_quic_channel_notify_flush_done(QUIC_CHANNEL *ch)
+{
+ ch_record_state_transition(ch, ch->terminate_cause.remote
+ ? QUIC_CHANNEL_STATE_TERMINATING_DRAINING
+ : QUIC_CHANNEL_STATE_TERMINATING_CLOSING);
+ /*
+ * RFC 9000 s. 10.2 Immediate Close
+ * These states SHOULD persist for at least three times
+ * the current PTO interval as defined in [QUIC-RECOVERY].
+ */
+ ch->terminate_deadline
+ = ossl_time_add(get_time(ch),
+ ossl_time_multiply(ossl_ackm_get_pto_duration(ch->ackm), 3));
+ if (!ch->terminate_cause.remote) {
+ OSSL_QUIC_FRAME_CONN_CLOSE f = {0};
+
+ /* best effort */
+ f.error_code = ch->terminate_cause.error_code;
+ f.frame_type = ch->terminate_cause.frame_type;
+ f.is_app = ch->terminate_cause.app;
+ f.reason = (char *)ch->terminate_cause.reason;
+ f.reason_len = ch->terminate_cause.reason_len;
+ ossl_quic_tx_packetiser_schedule_conn_close(ch->txp, &f);
+ /*
+ * RFC 9000 s. 10.2.2 Draining Connection State:
+ * An endpoint that receives a CONNECTION_CLOSE frame MAY
+ * send a single packet containing a CONNECTION_CLOSE
+ * frame before entering the draining state, using a
+ * NO_ERROR code if appropriate
+ */
+ ch->conn_close_queued = 1;
+ }
+}
+
const QUIC_TERMINATE_CAUSE *
ossl_quic_channel_get_terminate_cause(const QUIC_CHANNEL *ch)
{
static void copy_tcause(QUIC_TERMINATE_CAUSE *dst,
const QUIC_TERMINATE_CAUSE *src)
{
+ /*
+ * do not override reason once it got set.
+ */
+ if (dst->reason != NULL)
+ return;
+
dst->error_code = src->error_code;
dst->frame_type = src->frame_type;
dst->app = src->app;
dst->remote = src->remote;
- dst->reason = NULL;
- dst->reason_len = 0;
-
if (src->reason != NULL && src->reason_len > 0) {
size_t l = src->reason_len;
char *r;
}
}
+void ossl_quic_channel_set_tcause(QUIC_CHANNEL *ch, uint64_t app_error_code,
+ const char *app_reason)
+{
+ QUIC_TERMINATE_CAUSE tcause = {0};
+
+ tcause.app = 1;
+ tcause.error_code = app_error_code;
+ tcause.reason = app_reason;
+ tcause.reason_len = app_reason != NULL ? strlen(app_reason) : 0;
+ copy_tcause(&ch->terminate_cause, &tcause);
+}
+
static void ch_start_terminating(QUIC_CHANNEL *ch,
const QUIC_TERMINATE_CAUSE *tcause,
int force_immediate)
ossl_qlog_event_connectivity_connection_closed(ch_get_qlog(ch), tcause);
if (!force_immediate) {
- ch_record_state_transition(ch, tcause->remote
- ? QUIC_CHANNEL_STATE_TERMINATING_DRAINING
- : QUIC_CHANNEL_STATE_TERMINATING_CLOSING);
- /*
- * RFC 9000 s. 10.2 Immediate Close
- * These states SHOULD persist for at least three times
- * the current PTO interval as defined in [QUIC-RECOVERY].
- */
- ch->terminate_deadline
- = ossl_time_add(get_time(ch),
- ossl_time_multiply(ossl_ackm_get_pto_duration(ch->ackm),
- 3));
-
- if (!tcause->remote) {
- OSSL_QUIC_FRAME_CONN_CLOSE f = {0};
-
- /* best effort */
- f.error_code = ch->terminate_cause.error_code;
- f.frame_type = ch->terminate_cause.frame_type;
- f.is_app = ch->terminate_cause.app;
- f.reason = (char *)ch->terminate_cause.reason;
- f.reason_len = ch->terminate_cause.reason_len;
- ossl_quic_tx_packetiser_schedule_conn_close(ch->txp, &f);
- /*
- * RFC 9000 s. 10.2.2 Draining Connection State:
- * An endpoint that receives a CONNECTION_CLOSE frame MAY
- * send a single packet containing a CONNECTION_CLOSE
- * frame before entering the draining state, using a
- * NO_ERROR code if appropriate
- */
- ch->conn_close_queued = 1;
- }
+ ossl_quic_channel_notify_flush_done(ch);
} else {
ch_on_terminating_timeout(ch);
}
return ossl_quic_channel_is_term_any(qc->ch);
}
+/*
+ * This function deals with local shutdown.
+ * Function must consider those scenarios:
+ * - blocking mode (1)
+ * - non-blocking mode (2)
+ * - non-blocking mode with assistance from SSL_poll() (3)
+ * (1) The function completes shutdown then returns back to caller.
+ * To complete shutdown we must do:
+ * - flush all streams, unless we got SSL_SHUTDOWN_FLAG_NO_STREAM_FLUSH,
+ * which means the connection is closed without waiting for streams
+ * to deliver data written by application.
+ * - let remote peer know local application is going to close connection,
+ * unless we got SSL_SHUTDOWN_FLAG_WAIT_PEER in which case we await
+ * until remote peer closes the connection
+ * - wait for peer to confirm connection close
+ *
+ * (2) The function does not block waiting for streams to be flushed
+ * nor for peer to close connection (when running with SSL_SHUTDOWN_FLAG_WAIT_PEER)
+ * Application is supposed to call SSL_shutdown() repeatedly as long as
+ * function returns 0 which indicates the operation is still in progress.
+ *
+ * (3) In this case application uses SSL_poll() to wait for completion
+ * of each step of shutdown process. Application calls SSL_shutdown()
+ * to start with connection shutdown. The function does not block.
+ * Application then uses SSL_poll() on connection object to monitor
+ * progress of shutdown. The SSL_poll() indicates progress by signaling
+ * SSL_POLL_EVENT_EC event. Application must check connection object
+ * for error. If no error is indicated, then application must call
+ * SSL_shutdown() to move to the next stop in shutdown process.
+ */
QUIC_TAKES_LOCK
int ossl_quic_conn_shutdown(SSL *s, uint64_t flags,
const SSL_SHUTDOWN_EX_ARGS *args,
return 1;
}
+ if (!wait_peer) {
+ /*
+ * Set shutdown reason now when local application wants to do
+ * active close (does not waant to wait for peer to close th
+ * connection). The reason will be sent to peer with connection
+ * close notification as soon as streams will be flushed.
+ */
+ if (args != NULL) {
+ ossl_quic_channel_set_tcause(ctx.qc->ch, args->quic_error_code,
+ args->quic_reason);
+ }
+ }
+
/* Phase 1: Stream Flushing */
if (!wait_peer && stream_flush) {
qc_shutdown_flush_init(ctx.qc);
#include "internal/quic_stream_map.h"
#include "internal/nelem.h"
+#include "internal/quic_channel.h"
/*
* QUIC Stream Map
void *get_stream_limit_cb_arg,
QUIC_RXFC *max_streams_bidi_rxfc,
QUIC_RXFC *max_streams_uni_rxfc,
- int is_server)
+ QUIC_CHANNEL *ch)
{
qsm->map = lh_QUIC_STREAM_new(hash_stream, cmp_stream);
qsm->active_list.prev = qsm->active_list.next = &qsm->active_list;
qsm->get_stream_limit_cb_arg = get_stream_limit_cb_arg;
qsm->max_streams_bidi_rxfc = max_streams_bidi_rxfc;
qsm->max_streams_uni_rxfc = max_streams_uni_rxfc;
- qsm->is_server = is_server;
+ qsm->ch = ch;
return 1;
}
s->id = stream_id;
s->type = type;
- s->as_server = qsm->is_server;
+ s->as_server = ossl_quic_channel_is_server(qsm->ch);
s->send_state = (ossl_quic_stream_is_local_init(s)
|| ossl_quic_stream_is_bidi(s))
? QUIC_SSTREAM_STATE_READY
{
int should_be_active, allowed_by_stream_limit = 1;
- if (ossl_quic_stream_is_server_init(s) == qsm->is_server) {
+ if (ossl_quic_stream_is_server_init(s) == ossl_quic_channel_is_server(qsm->ch)) {
int is_uni = !ossl_quic_stream_is_bidi(s);
uint64_t stream_ordinal = s->id >> 2;
assert(qsm->num_shutdown_flush > 0);
qs->shutdown_flush = 0;
--qsm->num_shutdown_flush;
+
+ /*
+ * when num_shutdown_flush becomes zero we need to poke
+ * SSL_poll() it's time to poke to SSL_shutdown() to proceed
+ * with shutdown process as all streams are gone (flushed).
+ */
+ if (qsm->num_shutdown_flush == 0)
+ ossl_quic_channel_notify_flush_done(qsm->ch);
}
int ossl_quic_stream_map_notify_totally_acked(QUIC_STREAM_MAP *qsm,
#define OPK_C_WRITE_EX2 52
#define OPK_SKIP_IF_BLOCKING 53
#define OPK_C_STREAM_RESET_FAIL 54
+#define OPK_C_SHUTDOWN 55
#define EXPECT_CONN_CLOSE_APP (1U << 0)
#define EXPECT_CONN_CLOSE_REMOTE (1U << 1)
{OPK_C_SET_DEFAULT_STREAM_MODE, NULL, (mode), NULL, NULL},
#define OP_C_SET_INCOMING_STREAM_POLICY(policy) \
{OPK_C_SET_INCOMING_STREAM_POLICY, NULL, (policy), NULL, NULL},
+#define OP_C_SHUTDOWN(reason, flags) \
+ {OPK_C_SHUTDOWN, (reason), (flags), NULL, NULL},
#define OP_C_SHUTDOWN_WAIT(reason, flags) \
{OPK_C_SHUTDOWN_WAIT, (reason), (flags), NULL, NULL},
#define OP_C_EXPECT_CONN_CLOSE_INFO(ec, app, remote) \
}
break;
+ case OPK_C_SHUTDOWN:
+ {
+ int ret;
+ QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
+ SSL_SHUTDOWN_EX_ARGS args = {0};
+
+ ossl_quic_engine_set_inhibit_tick(ossl_quic_channel_get0_engine(ch), 0);
+
+ if (!TEST_ptr(c_tgt))
+ goto out;
+
+ args.quic_reason = (const char *)op->arg0;
+
+ ret = SSL_shutdown_ex(c_tgt, op->arg1, &args, sizeof(args));
+ if (!TEST_int_ge(ret, 0))
+ goto out;
+ }
+ break;
+
case OPK_C_SHUTDOWN_WAIT:
{
int ret;
OP_END
};
+#define POLL_FMT "%s%s%s%s%s%s%s%s%s%s%s%s%s"
+#define POLL_PRINTA(_revents_) \
+ (_revents_) & SSL_POLL_EVENT_F ? "SSL_POLL_EVENT_F " : "", \
+ (_revents_) & SSL_POLL_EVENT_EL ? "SSL_POLL_EVENT_EL " : "", \
+ (_revents_) & SSL_POLL_EVENT_EC ? "SSL_POLL_EVENT_EC " : "", \
+ (_revents_) & SSL_POLL_EVENT_ECD ? "SSL_POLL_EVENT_ECD " : "", \
+ (_revents_) & SSL_POLL_EVENT_ER ? "SSL_POLL_EVENT_ER " : "", \
+ (_revents_) & SSL_POLL_EVENT_EW ? "SSL_POLL_EVENT_EW " : "", \
+ (_revents_) & SSL_POLL_EVENT_R ? "SSL_POLL_EVENT_R " : "", \
+ (_revents_) & SSL_POLL_EVENT_W ? "SSL_POLL_EVENT_W " : "", \
+ (_revents_) & SSL_POLL_EVENT_IC ? "SSL_POLL_EVENT_IC " : "", \
+ (_revents_) & SSL_POLL_EVENT_ISB ? "SSL_POLL_EVENT_ISB " : "", \
+ (_revents_) & SSL_POLL_EVENT_ISU ? "SSL_POLL_EVENT_ISU " : "", \
+ (_revents_) & SSL_POLL_EVENT_OSB ? "SSL_POLL_EVENT_OSB " : "", \
+ (_revents_) & SSL_POLL_EVENT_OSU ? "SSL_POLL_EVENT_OSU " : ""
+
+/* 88. Test SSL_poll (lite, non-blocking) */
+ossl_unused static int script_88_poll(struct helper *h, struct helper_local *hl)
+{
+ int ok = 1, ret, expected_ret = 1;
+ static const struct timeval timeout = {0};
+ size_t result_count, processed;
+ SSL_POLL_ITEM items[2] = {0}, *item = items;
+ SSL *c_a;
+ size_t i;
+ uint64_t mode, expected_revents[2] = {0};
+
+ if (!TEST_ptr(c_a = helper_local_get_c_stream(hl, "a")))
+ return 0;
+
+ item->desc = SSL_as_poll_descriptor(c_a);
+ item->events = UINT64_MAX;
+ item->revents = UINT64_MAX;
+ ++item;
+
+ item->desc = SSL_as_poll_descriptor(h->c_conn);
+ item->events = UINT64_MAX;
+ item->revents = UINT64_MAX;
+ ++item;
+
+ result_count = SIZE_MAX;
+ ret = SSL_poll(items, OSSL_NELEM(items), sizeof(SSL_POLL_ITEM),
+ &timeout, 0,
+ &result_count);
+
+ mode = hl->check_op->arg2;
+ switch (mode) {
+ case 0:
+ /* No incoming data yet */
+ expected_revents[0] = SSL_POLL_EVENT_W;
+ expected_revents[1] = SSL_POLL_EVENT_OS;
+ break;
+ case 1:
+ /* Expect more events */
+ expected_revents[0] = SSL_POLL_EVENT_R;
+ expected_revents[1] = SSL_POLL_EVENT_OS;
+ break;
+ default:
+ return 0;
+ }
+
+ if (!TEST_int_eq(ret, expected_ret))
+ ok = 0;
+
+ /*
+ * Unlike script 85 which always expects all objects
+ * get signaled in single call to SSL_poll() we must
+ * assume here we can get notification for only one.
+ */
+ processed = 0;
+ for (i = 0; i < OSSL_NELEM(items); ++i) {
+ if (items[i].revents == 0)
+ continue;
+
+ processed++;
+ if (!TEST_uint64_t_eq(items[i].revents, expected_revents[i])) {
+ TEST_info("wanted: " POLL_FMT " got: " POLL_FMT,
+ POLL_PRINTA(expected_revents[i]),
+ POLL_PRINTA(items[i].revents));
+ TEST_error("mismatch at index %zu in poll results, mode %d",
+ i, (int)mode);
+ ok = 0;
+ }
+ }
+
+ if (!TEST_size_t_eq(processed, result_count))
+ ok = 0;
+
+ return ok;
+}
+
+ossl_unused static int script_88_poll_conly(struct helper *h, struct helper_local *hl)
+{
+ int ok = 1;
+ static const struct timeval timeout = {0};
+ size_t result_count;
+ SSL_POLL_ITEM items[1] = {0};
+ int done = 0;
+ OSSL_TIME t_limit;
+
+ result_count = SIZE_MAX;
+
+ items[0].desc = SSL_as_poll_descriptor(h->c_conn);
+ items[0].events = UINT64_MAX;
+ items[0].revents = UINT64_MAX;
+
+ t_limit = ossl_time_add(ossl_time_now(),
+ ossl_ticks2time(5 * OSSL_TIME_SECOND));
+ while (done == 0 && ok == 1) {
+ ok = SSL_poll(items, OSSL_NELEM(items), sizeof(SSL_POLL_ITEM),
+ &timeout, 0,
+ &result_count);
+
+ if (!TEST_int_eq(ok, 1))
+ continue;
+
+ if (result_count == 0)
+ OSSL_sleep(10);
+
+ TEST_info("received event " POLL_FMT, POLL_PRINTA(items[0].revents));
+
+ if ((items[0].revents & SSL_POLL_EVENT_EC) == SSL_POLL_EVENT_EC)
+ SSL_shutdown(h->c_conn);
+ done =
+ ((items[0].revents & SSL_POLL_EVENT_ECD) == SSL_POLL_EVENT_ECD);
+
+ if (ossl_time_compare(ossl_time_now(), t_limit) == 1) {
+ TEST_error("shutdown time exceeded 5sec");
+ ok = 0;
+ }
+ }
+
+ return ok;
+}
+
+/*
+ * verify SSL_poll() signals SSL_POLL_EVENT_EC event
+ * to notify client it's time to call SSL_shutdown().
+ */
+static const struct script_op script_88[] = {
+ OP_SKIP_IF_BLOCKING (16)
+ OP_C_SET_ALPN ("ossltest")
+ OP_C_CONNECT_WAIT ()
+
+ OP_C_SET_DEFAULT_STREAM_MODE(SSL_DEFAULT_STREAM_MODE_NONE)
+
+ OP_C_NEW_STREAM_BIDI (a, C_BIDI_ID(0))
+ OP_S_BIND_STREAM_ID (a, C_BIDI_ID(0))
+
+ /* Check nothing readable yet. */
+ OP_CHECK (script_88_poll, 0 /* ->arg2 */)
+
+ OP_C_WRITE (a, "flamingo", 8)
+ OP_C_CONCLUDE (a)
+
+ /* Send something that will make client sockets readable. */
+ OP_S_READ_EXPECT (a, "flamingo", 8)
+ OP_S_WRITE (a, "flamingo", 8)
+ OP_S_CONCLUDE (a)
+
+ OP_CHECK (script_88_poll, 1 /* ->arg2 */)
+
+ OP_C_READ_EXPECT (a, "flamingo", 8)
+
+ /*
+ * client calls non-blocking SSL_shutdown() and gives
+ * server chance to run by calling sleep.
+ */
+ OP_C_SHUTDOWN (NULL, 0)
+ OP_SLEEP(100)
+
+ /*
+ * Here we call SSL_poll() and handle SSL_POLL_EVENT_EC
+ * and SSL_POLL_EVENT_ECD on connection object. Whenever
+ * _EC event comes we call SSL_shutdown() to keep connection
+ * draining. We keep calling SSL_poll()/SSL_shutdown() until
+ * SSL_poll() signals SSL_POLL_EVENT_ECD to let us know connection
+ * has dried out and con be closed.
+ */
+ OP_CHECK (script_88_poll_conly, 0)
+
+ OP_END
+};
/* 86. Event Handling Mode Configuration */
static int set_event_handling_mode_conn(struct helper *h, struct helper_local *hl)
{
script_84,
script_85,
script_86,
- script_87
+ script_87,
+ script_88,
};
static int test_script(int idx)
TEST_info("Running script %d (order=%d, blocking=%d)", script_idx + 1,
free_order, blocking);
+
return run_script(scripts[script_idx], script_name, free_order, blocking);
}
#include "internal/quic_statm.h"
#include "internal/quic_demux.h"
#include "internal/quic_record_rx.h"
+#include "internal/quic_channel.h"
#include "testutil.h"
#include "quic_record_test_util.h"
OSSL_QUIC_FRAME_CONN_CLOSE conn_close;
} frame;
OSSL_QUIC_ACK_RANGE ack_ranges[16];
+ QUIC_CHANNEL *client_ch;
};
static void helper_cleanup(struct helper *h)
ossl_quic_demux_free(h->demux);
BIO_free(h->bio1);
BIO_free(h->bio2);
+ ossl_quic_channel_free(h->client_ch);
}
static void demux_default_handler(QUIC_URXE *e, void *arg,
{
int rc = 0;
size_t i;
+ QUIC_CHANNEL_ARGS client_ch_args;
memset(h, 0, sizeof(*h));
+ memset(&client_ch_args, 0, sizeof(client_ch_args));
/* Initialisation */
if (!TEST_true(BIO_new_bio_dgram_pair(&h->bio1, 0, &h->bio2, 0)))
/* is_server */0)))
goto err;
+ h->client_ch = ossl_quic_channel_alloc(&client_ch_args);
+ if (!TEST_ptr(h->client_ch))
+ goto err;
if (!TEST_true(ossl_quic_stream_map_init(&h->qsm, NULL, NULL,
&h->max_streams_bidi_rxfc,
&h->max_streams_uni_rxfc,
- /*is_server=*/0)))
+ h->client_ch)))
goto err;
h->have_qsm = 1;