diff options
author | Hugo Landau <hlandau@openssl.org> | 2023-04-18 19:30:54 +0100 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2023-05-12 14:47:11 +0100 |
commit | f20fdd16d817a095f58f9c016044abef24e50e58 (patch) | |
tree | 24d8ac2709db424c03f1825f02a9ab4ba25a25c7 | |
parent | 2dbc39deacf9d5850eecef515cbc50331750dd22 (diff) |
QUIC CHANNEL: Handle incoming remotely-created streams
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20765)
-rw-r--r-- | include/internal/quic_channel.h | 13 | ||||
-rw-r--r-- | include/internal/quic_stream_map.h | 27 | ||||
-rw-r--r-- | ssl/quic/quic_channel.c | 25 | ||||
-rw-r--r-- | ssl/quic/quic_impl.c | 11 | ||||
-rw-r--r-- | ssl/quic/quic_rx_depack.c | 80 | ||||
-rw-r--r-- | ssl/quic/quic_stream_map.c | 41 |
6 files changed, 183 insertions, 14 deletions
diff --git a/include/internal/quic_channel.h b/include/internal/quic_channel.h index 7ccf6e0bb2..084c465c3a 100644 --- a/include/internal/quic_channel.h +++ b/include/internal/quic_channel.h @@ -273,9 +273,18 @@ CRYPTO_MUTEX *ossl_quic_channel_get_mutex(QUIC_CHANNEL *ch); /* * Creates a new locally-initiated stream in the stream mapper, choosing an * appropriate stream ID. If is_uni is 1, creates a unidirectional stream, else - * creates a bidirectional stream. + * creates a bidirectional stream. Returns NULL on failure. */ -QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni); +QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni); + +/* + * Creates a new remotely-initiated stream in the stream mapper. The stream ID + * is used to confirm the initiator and determine the stream type. The stream is + * automatically added to the QSM's accept queue. A pointer to the stream is + * also returned. Returns NULL on failure. + */ +QUIC_STREAM *ossl_quic_channel_new_stream_remote(QUIC_CHANNEL *ch, + uint64_t stream_id); # endif diff --git a/include/internal/quic_stream_map.h b/include/internal/quic_stream_map.h index 0bdd7e88cf..78ec703fbc 100644 --- a/include/internal/quic_stream_map.h +++ b/include/internal/quic_stream_map.h @@ -36,6 +36,7 @@ struct quic_stream_list_node_st { struct quic_stream_st { QUIC_STREAM_LIST_NODE active_node; /* for use by QUIC_STREAM_MAP */ + QUIC_STREAM_LIST_NODE accept_node; /* accept queue of remotely-created streams */ /* Temporary link used by TXP. */ QUIC_STREAM *txp_next; @@ -132,7 +133,8 @@ int ossl_quic_stream_reset(QUIC_STREAM *s, uint64_t aec); typedef struct quic_stream_map_st { LHASH_OF(QUIC_STREAM) *map; QUIC_STREAM_LIST_NODE active_list; - size_t rr_stepping, rr_counter; + QUIC_STREAM_LIST_NODE accept_list; + size_t rr_stepping, rr_counter, num_accept; QUIC_STREAM *rr_cur; uint64_t (*get_stream_limit_cb)(int uni, void *arg); void *get_stream_limit_cb_arg; @@ -232,6 +234,29 @@ void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s); void ossl_quic_stream_map_set_rr_stepping(QUIC_STREAM_MAP *qsm, size_t stepping); /* + * Adds a stream to the accept queue. + */ +void ossl_quic_stream_map_push_accept_queue(QUIC_STREAM_MAP *qsm, + QUIC_STREAM *s); + +/* + * Returns the next item to be popped from the accept queue, or NULL if it is + * empty. + */ +QUIC_STREAM *ossl_quic_stream_map_peek_accept_queue(QUIC_STREAM_MAP *qsm); + +/* + * Removes a stream from the accept queue. + * + * Precondition: s is in the accept queue. + */ +void ossl_quic_stream_map_remove_from_accept_queue(QUIC_STREAM_MAP *qsm, + QUIC_STREAM *s); + +/* Returns the length of the accept queue. */ +size_t ossl_quic_stream_map_get_accept_queue_len(QUIC_STREAM_MAP *qsm); + +/* * QUIC Stream Iterator * ==================== * diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index 8e1d415935..b11393955d 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -2214,7 +2214,7 @@ SSL *ossl_quic_channel_get0_ssl(QUIC_CHANNEL *ch) return ch->tls; } -QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni) +QUIC_STREAM *ossl_quic_channel_new_stream_local(QUIC_CHANNEL *ch, int is_uni) { QUIC_STREAM *qs; int type = 0; @@ -2242,3 +2242,26 @@ QUIC_STREAM *ossl_quic_channel_new_stream(QUIC_CHANNEL *ch, int is_uni) ++*p_next_ordinal; return qs; } + +QUIC_STREAM *ossl_quic_channel_new_stream_remote(QUIC_CHANNEL *ch, + uint64_t stream_id) +{ + uint64_t peer_role; + QUIC_STREAM *qs; + + peer_role = ch->is_server + ? QUIC_STREAM_INITIATOR_CLIENT + : QUIC_STREAM_INITIATOR_SERVER; + + if ((stream_id & QUIC_STREAM_INITIATOR_MASK) != peer_role) + return NULL; + + qs = ossl_quic_stream_map_alloc(&ch->qsm, stream_id, + stream_id & (QUIC_STREAM_INITIATOR_MASK + | QUIC_STREAM_DIR_MASK)); + if (qs == NULL) + return NULL; + + ossl_quic_stream_map_push_accept_queue(&ch->qsm, qs); + return qs; +} diff --git a/ssl/quic/quic_impl.c b/ssl/quic/quic_impl.c index 0dd6576633..41de167dd2 100644 --- a/ssl/quic/quic_impl.c +++ b/ssl/quic/quic_impl.c @@ -296,6 +296,15 @@ void ossl_quic_free(SSL *s) ctx.xso->stream->deleted = 1; + /* Auto-conclude stream. */ + /* TODO(QUIC): Do RESET_STREAM here instead of auto-conclude */ + if (ctx.xso->stream->sstream != NULL) + ossl_quic_sstream_fin(ctx.xso->stream->sstream); + + /* Update stream state. */ + ossl_quic_stream_map_update_state(ossl_quic_channel_get_qsm(ctx.xso->conn->ch), + ctx.xso->stream); + quic_unlock(ctx.qc); /* Note: SSL_free calls OPENSSL_free(xso) for us */ @@ -1106,7 +1115,7 @@ SSL *ossl_quic_conn_stream_new(SSL *s, uint64_t flags) xso->blocking = ctx.qc->default_blocking; xso->ssl_mode = ctx.qc->default_ssl_mode; - xso->stream = ossl_quic_channel_new_stream(ctx.qc->ch, is_uni); + xso->stream = ossl_quic_channel_new_stream_local(ctx.qc->ch, is_uni); if (xso->stream == NULL) goto err; diff --git a/ssl/quic/quic_rx_depack.c b/ssl/quic/quic_rx_depack.c index ea54bc3c08..7adec6d7b0 100644 --- a/ssl/quic/quic_rx_depack.c +++ b/ssl/quic/quic_rx_depack.c @@ -254,12 +254,80 @@ static int depack_do_frame_stream(PACKET *pkt, QUIC_CHANNEL *ch, stream = ossl_quic_stream_map_get_by_id(&ch->qsm, frame_data.stream_id); if (stream == NULL) { - ossl_quic_channel_raise_protocol_error(ch, - QUIC_ERR_STREAM_STATE_ERROR, - frame_type, - "STREAM frame for nonexistent " - "stream"); - return 0; + uint64_t peer_role, stream_ordinal, *p_next_ordinal; + int is_uni; + + /* + * If we do not yet have a stream with the given ID, there are three + * possibilities: + * + * (a) The stream ID is for a remotely-created stream and the peer + * is creating a stream. + * + * (b) The stream ID is for a locally-created stream which has + * previously been deleted. + * + * (c) The stream ID is for a locally-created stream which does + * not exist yet. This is a protocol violation and we must + * terminate the connection in this case. + * + * We distinguish between (b) and (c) using the stream ID allocator + * variable. Since stream ordinals are allocated monotonically, we + * simply determine if the stream ordinal is in the future. + */ + + peer_role = ch->is_server + ? QUIC_STREAM_INITIATOR_CLIENT + : QUIC_STREAM_INITIATOR_SERVER; + + is_uni = ((frame_data.stream_id & QUIC_STREAM_DIR_MASK) + == QUIC_STREAM_DIR_UNI); + + stream_ordinal = frame_data.stream_id >> 2; + + if ((frame_data.stream_id & QUIC_STREAM_INITIATOR_MASK) == peer_role) { + /* Peer-created stream which does not yet exist. Create it. */ + stream = ossl_quic_channel_new_stream_remote(ch, frame_data.stream_id); + if (stream == NULL) { + ossl_quic_channel_raise_protocol_error(ch, + QUIC_ERR_INTERNAL_ERROR, + frame_type, + "internal error (stream allocation)"); + return 0; + } + + /* + * Fallthrough to processing of stream data for newly created + * stream. + */ + } else { + /* Locally-created stream which does not yet exist. */ + + p_next_ordinal = is_uni + ? &ch->next_local_stream_ordinal_uni + : &ch->next_local_stream_ordinal_bidi; + + if (stream_ordinal >= *p_next_ordinal) { + /* + * We never created this stream yet, this is a protocol + * violation. + */ + ossl_quic_channel_raise_protocol_error(ch, + QUIC_ERR_STREAM_STATE_ERROR, + frame_type, + "STREAM frame for nonexistent " + "stream"); + return 0; + } + + /* + * Otherwise this is for an old locally-initiated stream which we + * have subsequently deleted. Ignore the data; it may simply be a + * retransmission. We already take care of notifying the peer of the + * termination of the stream during the stream deletion lifecycle. + */ + return 1; + } } if (stream->rstream == NULL) { diff --git a/ssl/quic/quic_stream_map.c b/ssl/quic/quic_stream_map.c index 52e8e14bf8..06bf1cfa34 100644 --- a/ssl/quic/quic_stream_map.c +++ b/ssl/quic/quic_stream_map.c @@ -66,17 +66,26 @@ static void list_remove(QUIC_STREAM_LIST_NODE *l, n->next = n->prev = NULL; } -static QUIC_STREAM *active_next(QUIC_STREAM_LIST_NODE *l, QUIC_STREAM *s) +static QUIC_STREAM *list_next(QUIC_STREAM_LIST_NODE *l, QUIC_STREAM_LIST_NODE *n, + size_t off) { - QUIC_STREAM_LIST_NODE *n = s->active_node.next; + n = n->next; if (n == l) n = n->next; if (n == l) return NULL; - return (QUIC_STREAM *)n; + + return (QUIC_STREAM *)(((char *)n) - off); } +#define active_next(l, s) list_next((l), &(s)->active_node, \ + offsetof(QUIC_STREAM, active_node)) +#define accept_next(l, s) list_next((l), &(s)->accept_node, \ + offsetof(QUIC_STREAM, accept_node)) +#define accept_head(l) list_next((l), (l), \ + offsetof(QUIC_STREAM, accept_node)) + static unsigned long hash_stream(const QUIC_STREAM *s) { return (unsigned long)s->id; @@ -97,9 +106,11 @@ int ossl_quic_stream_map_init(QUIC_STREAM_MAP *qsm, { qsm->map = lh_QUIC_STREAM_new(hash_stream, cmp_stream); qsm->active_list.prev = qsm->active_list.next = &qsm->active_list; + qsm->accept_list.prev = qsm->accept_list.next = &qsm->accept_list; qsm->rr_stepping = 1; qsm->rr_counter = 0; qsm->rr_cur = NULL; + qsm->num_accept = 0; qsm->get_stream_limit_cb = get_stream_limit_cb; qsm->get_stream_limit_cb_arg = get_stream_limit_cb_arg; @@ -271,6 +282,30 @@ void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s) stream_map_mark_inactive(qsm, s); } +QUIC_STREAM *ossl_quic_stream_map_peek_accept_queue(QUIC_STREAM_MAP *qsm) +{ + return accept_head(&qsm->accept_list); +} + +void ossl_quic_stream_map_push_accept_queue(QUIC_STREAM_MAP *qsm, + QUIC_STREAM *s) +{ + list_insert_tail(&qsm->accept_list, &s->accept_node); + ++qsm->num_accept; +} + +void ossl_quic_stream_map_remove_from_accept_queue(QUIC_STREAM_MAP *qsm, + QUIC_STREAM *s) +{ + list_remove(&qsm->accept_list, &s->accept_node); + --qsm->num_accept; +} + +size_t ossl_quic_stream_map_get_accept_queue_len(QUIC_STREAM_MAP *qsm) +{ + return qsm->num_accept; +} + /* * QUIC Stream Iterator * ==================== |