Patch subject is complete summary.
src/event/quic/ngx_event_quic.c | 617 ++++++++++++++++++++++++- src/event/quic/ngx_event_quic.h | 11 + src/event/quic/ngx_event_quic_ack.c | 13 + src/event/quic/ngx_event_quic_ack.h | 2 + src/event/quic/ngx_event_quic_connection.h | 10 + src/event/quic/ngx_event_quic_connid.c | 64 ++- src/event/quic/ngx_event_quic_connid.h | 7 +- src/event/quic/ngx_event_quic_migration.c | 10 +- src/event/quic/ngx_event_quic_openssl_compat.c | 4 + src/event/quic/ngx_event_quic_output.c | 109 +++- src/event/quic/ngx_event_quic_protection.c | 101 ++- src/event/quic/ngx_event_quic_protection.h | 3 + src/event/quic/ngx_event_quic_socket.c | 71 ++- src/event/quic/ngx_event_quic_socket.h | 4 +- src/event/quic/ngx_event_quic_ssl.c | 172 +++++- src/event/quic/ngx_event_quic_ssl.h | 3 + src/event/quic/ngx_event_quic_streams.c | 550 +++++++++++++++------ src/event/quic/ngx_event_quic_streams.h | 3 + src/event/quic/ngx_event_quic_tokens.c | 48 + src/event/quic/ngx_event_quic_tokens.h | 9 + src/event/quic/ngx_event_quic_transport.c | 214 ++++++-- src/event/quic/ngx_event_quic_transport.h | 7 +- 22 files changed, 1677 insertions(+), 355 deletions(-)
# HG changeset patch # User Vladimir Khomutov <v...@wbsrv.ru> # Date 1703082264 -10800 # Wed Dec 20 17:24:24 2023 +0300 # Node ID f39271dd260b831fac70c776904d9f5ded053968 # Parent f54423e057f909b1d644cc0af316d67b91cd408f QUIC: client support. diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -18,6 +18,11 @@ static ngx_int_t ngx_quic_handle_statele static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_close_handler(ngx_event_t *ev); +static void ngx_quic_dummy_handler(ngx_event_t *ev); +static void ngx_quic_client_input_handler(ngx_event_t *rev); +static ngx_int_t ngx_quic_client_start(ngx_connection_t *c, + ngx_quic_header_t *pkt); + static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf); static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, @@ -188,8 +193,16 @@ ngx_quic_apply_transport_params(ngx_conn qc->streams.server.bidi.max = peer_tp->initial_max_streams_bidi; qc->streams.server.uni.max = peer_tp->initial_max_streams_uni; + if (qc->client) { + ngx_memcpy(qc->path->cid->sr_token, + peer_tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + } + ngx_memcpy(&qc->peer_tp, peer_tp, sizeof(ngx_quic_tp_t)); + /* apply transport parameters to early created streams */ + ngx_quic_streams_init_state(c); + return NGX_OK; } @@ -222,10 +235,339 @@ ngx_quic_run(ngx_connection_t *c, ngx_qu } +static void +ngx_quic_dummy_handler(ngx_event_t *ev) +{ +} + + +ngx_int_t +ngx_quic_create_client(ngx_quic_conf_t *conf, ngx_connection_t *c) +{ + int value; + ngx_log_t *log; + ngx_quic_connection_t *qc; + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (c->sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(c->fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for quic conn failed, ignored"); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (c->sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(c->fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for quic conn failed, ignored"); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (c->sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(c->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for quic conn failed, ignored"); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (c->sockaddr->sa_family == AF_INET6) { + + value = 1; + + if (setsockopt(c->fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for quic conn failed, ignored"); + } + } +#endif + +#endif + + c->read->handler = ngx_quic_client_input_handler; + c->write->handler = ngx_quic_dummy_handler; + + if (conf->active_connection_id_limit == 0) { + /* + * TODO: remove when done with testing + * + * this case exists purely for testing/coverage purposes; + * (RFC requires minimum value of 2, and default is 2, so no real + * configurations want to set this zero) + */ + return NGX_ERROR; + } + + /* + * each stream calls c->listening()->handler for initialization; + * handler is set by the caller + */ + c->listening = ngx_pcalloc(c->pool, sizeof(ngx_listening_t)); + if (c->listening == NULL) { + return NGX_ERROR; + } + + /* + * 'c' is a new connection to upstream and c->log is inherited from + * the r->connection->log (allocated from r->pool) + * + * main quic connection (this) may exist longer than client connection + * due to keepalive and/or non-immediate closing + * + * unlike tcp keepalive, main quic connection is alive during the + * time between requests, and may produce events with logging. + * + * so, use log from ngx_cycle instead of client log, which may be + * destroyed. + */ + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NGX_ERROR; + } + + *log = *ngx_cycle->log; + c->log = log; + + log->connection = c->number; + + c->read->log = c->log; + c->write->log = c->log; + c->pool->log = c->log; + + qc = ngx_quic_new_connection(c, conf, NULL); + if (qc == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client initialized on c:%p", c); + + return NGX_OK; +} + + +void +ngx_quic_client_set_ssl_data(ngx_connection_t *c, void *data) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + return; + } + + qc->init_ssl_data = data; +} + + +void * +ngx_quic_client_get_ssl_data(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + return NULL; + } + + return qc->init_ssl_data; +} + + +ngx_int_t +ngx_quic_connect(ngx_connection_t *c, ngx_quic_init_ssl_pt init_ssl, void *data) +{ + ngx_str_t id; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + qc->init_ssl = init_ssl; + qc->init_ssl_data = data; + + qc->peer_tp.max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + + /* use initial dcid we generated on start */ + id.data = qc->incid; + id.len = NGX_QUIC_SERVER_CID_LEN; + + cid = ngx_quic_create_client_id(c, &id, 0, NULL); + if (cid == NULL) { + return NGX_ERROR; + } + + qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); + if (qc->path == NULL) { + return NGX_ERROR; + } + + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + ngx_quic_path_dbg(c, "set active", qc->path); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client initiating connection"); + + return ngx_quic_client_start(c, NULL); +} + + +static ngx_int_t +ngx_quic_client_start(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_str_t dcid; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (pkt == NULL) { + /* not a retry packet */ + goto start; + } + + if (pkt->token.len <= 16) { + /* + * A client MUST discard a Retry packet with a zero-length + * Retry Token field. + */ + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client bad token length"); + return NGX_ERROR; + } + + if (ngx_quic_verify_retry_token_integrity(c, pkt) != NGX_OK) { + /* + * A client MUST accept and process at most one Retry packet + * for each connection attempt. + */ + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client retry token integrity check failed"); + return NGX_ERROR; + } + + /* server responded with new id, update */ + ngx_memcpy(qc->path->cid->id, pkt->scid.data, pkt->scid.len); + qc->path->cid->len = pkt->scid.len; + qc->server_id_known = 1; + + qc->client_retry.len = pkt->token.len - 16; + + qc->client_retry.data = ngx_pnalloc(c->pool, + qc->client_retry.len); + if (qc->client_retry.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(qc->client_retry.data, pkt->token.data, + qc->client_retry.len); + + /* prepare for one more SID change later */ + qc->server_id_known = 0; + + /* + * RFC 9002 6.3 Handling Retry Packets + * + * Clients that receive a Retry packet reset congestion control and loss + * recovery state, including resetting any pending timers. Other connection + * state, in particular cryptographic handshake messages, is retained; see + * Section 17.2.5 of [QUIC-TRANSPORT]. + */ + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + ctx->send_ack = 0; + qc->pto_count = 0; + + ngx_quic_congestion_reset(qc); + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + ngx_quic_set_lost_timer(c); + + /* now we need to restart handshake from the beginning */ + + /* reset offset in CRYPTO frames */ + ctx->crypto_sent = 0; + + /* since SID has changed, new keys need to be generated */ + dcid.data = pkt->scid.data; + dcid.len = pkt->scid.len; + + ngx_quic_keys_cleanup(qc->keys); + + if (ngx_quic_keys_set_initial_secret(qc->keys, &dcid, c->log) != NGX_OK) { + return NGX_ERROR; + } + +start: + + if (ngx_quic_client_handshake(c) != NGX_OK) { + qc->error_reason = "handshake failed"; + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client handshake started"); + + return NGX_AGAIN; +} + + static ngx_quic_connection_t * ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { + ngx_str_t dcid; ngx_uint_t i; ngx_quic_tp_t *peer_tp; ngx_quic_connection_t *qc; @@ -235,12 +577,18 @@ ngx_quic_new_connection(ngx_connection_t return NULL; } + /* server connection requires a packet from client */ + qc->client = (pkt == NULL); + qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); if (qc->keys == NULL) { return NULL; } - qc->version = pkt->version; + qc->keys->client = qc->client; + + /* client always initiates QUIC v.1 */ + qc->version = qc->client ? 1 : pkt->version; ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -306,27 +654,40 @@ ngx_quic_new_connection(ngx_connection_t qc->streams.client.uni.max = qc->tp.initial_max_streams_uni; qc->streams.client.bidi.max = qc->tp.initial_max_streams_bidi; - qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, - ngx_max(2 * qc->tp.max_udp_payload_size, - 14720)); - qc->congestion.ssthresh = (size_t) -1; - qc->congestion.recovery_start = ngx_current_msec; - - if (pkt->validated && pkt->retried) { - qc->tp.retry_scid.len = pkt->dcid.len; - qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); - if (qc->tp.retry_scid.data == NULL) { + ngx_quic_congestion_reset(qc); + + if (!qc->client) { + if (pkt->validated && pkt->retried) { + qc->tp.retry_scid.len = pkt->dcid.len; + + qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->tp.retry_scid.data == NULL) { + return NULL; + } + } + } + + if (qc->client) { + if (ngx_quic_create_server_id(c, qc->incid, 1) != NGX_OK) { return NULL; } + + dcid.data = qc->incid; + dcid.len = NGX_QUIC_SERVER_CID_LEN; + + } else { + dcid = pkt->dcid; } - if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + if (ngx_quic_keys_set_initial_secret(qc->keys, &dcid, c->log) != NGX_OK) { return NULL; } - qc->validated = pkt->validated; + if (!qc->client) { + qc->validated = pkt->validated; + } if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { ngx_quic_keys_cleanup(qc->keys); @@ -454,10 +815,151 @@ ngx_quic_input_handler(ngx_event_t *rev) } +static void +ngx_quic_client_input_handler(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_str_t key; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + ssize_t n; + ngx_buf_t dbuf; + static u_char cbuf[65535]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, + "quic client input handler"); + + c = rev->data; + qc = ngx_quic_get_connection(c); + + c->log->action = "handling quic client input"; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "quic server timed out"); + ngx_quic_close_connection(c, NGX_DONE); + return; + } + + if (c->close) { + c->close = 0; + + if (!ngx_exiting) { + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + + return; + } + + if (!rev->ready) { + if (qc->closing) { + ngx_quic_close_connection(c, NGX_OK); + + } else if (qc->shutdown) { + ngx_quic_shutdown_quic(c); + } + + return; + } + + for ( ;; ) { + + ngx_memzero(&dbuf, sizeof(ngx_buf_t)); + + n = c->recv(c, cbuf, sizeof(cbuf)); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_input_handler recv: fd:%d %z", + c->fd, n); + + if (n == NGX_ERROR) { + qc->error_reason = "failed read"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + /* + * actually, since client uses connected UDP socket, there should + * be no different addresses of incoming packets; + * + * we only need to dispatch between different quic sockets, + * as client may use different DCIDs + */ + + if (ngx_quic_get_packet_dcid(c->log, cbuf, n, &key) != NGX_OK) { + /* broken packet, ignore */ + continue; + } + + qsock = ngx_quic_find_socket_by_id(c, &key); + if (qsock == NULL) { + /* client uses unknown dcid, ignore */ + continue; + } + + c->udp = &qsock->udp; + + qsock = ngx_quic_get_socket(c); + + ngx_memcpy(&qsock->sockaddr, c->sockaddr, c->socklen); + qsock->socklen = c->socklen; + + dbuf.pos = cbuf; + dbuf.last = cbuf + n; + dbuf.start = dbuf.pos; + dbuf.end = cbuf + sizeof(cbuf); + + c->udp->buffer = &dbuf; + + b = c->udp->buffer; + + rc = ngx_quic_handle_datagram(c, b, NULL); + + if (rc == NGX_ERROR) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (rc == NGX_DECLINED) { + continue; + } + + /* rc == NGX_OK */ + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); +} + + void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) { ngx_uint_t i; +#if (NGX_STAT_STUB) + ngx_uint_t is_client; +#endif ngx_pool_t *pool; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -467,12 +969,19 @@ ngx_quic_close_connection(ngx_connection if (qc == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet rejected rc:%i, cleanup connection", rc); +#if (NGX_STAT_STUB) + is_client = 0; +#endif goto quic_done; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close %s rc:%i", - qc->closing ? "resumed": "initiated", rc); +#if (NGX_STAT_STUB) + is_client = qc->client; +#endif + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close %s rc:%i c:%p", + qc->closing ? "resumed": "initiated", rc, c); if (!qc->closing) { @@ -547,6 +1056,8 @@ ngx_quic_close_connection(ngx_connection } if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close: waiting for streams"); return; } @@ -571,6 +1082,8 @@ ngx_quic_close_connection(ngx_connection } if (qc->close.timer_set) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close: waiting for timers"); return; } @@ -598,7 +1111,9 @@ quic_done: } #if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, -1); + if (!is_client) { + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); + } #endif c->destroyed = 1; @@ -623,11 +1138,32 @@ ngx_quic_finalize_connection(ngx_connect return; } - qc->error = err; - qc->error_reason = reason; - qc->error_app = 1; qc->error_ftype = 0; + /* 10.2.3. Immediate Close during the Handshake + * + * Sending a CONNECTION_CLOSE of type 0x1d in an Initial or Handshake + * packet could expose application state or be used to alter application + * state. A CONNECTION_CLOSE of type 0x1d MUST be replaced by a + * CONNECTION_CLOSE of type 0x1c when sending the frame in Initial or + * Handshake packets. + * + * Endpoints MUST clear the value of the Reason Phrase field and SHOULD use + * the APPLICATION_ERROR code when converting to a CONNECTION_CLOSE of type + * 0x1c. + */ + + if (c->ssl == NULL || !c->ssl->handshaked) { + qc->error = NGX_QUIC_ERR_APPLICATION_ERROR; + qc->error_reason = ""; + qc->error_app = 0; + + } else { + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + } + ngx_post_event(&qc->close, &ngx_posted_events); } @@ -667,11 +1203,14 @@ ngx_quic_handle_datagram(ngx_connection_ size_t size; u_char *p, *start; ngx_int_t rc; - ngx_uint_t good; + ngx_uint_t good, is_server_packet; ngx_quic_path_t *path; ngx_quic_header_t pkt; ngx_quic_connection_t *qc; + qc = ngx_quic_get_connection(c); + is_server_packet = qc ? qc->client : 0; + good = 0; path = NULL; @@ -690,6 +1229,7 @@ ngx_quic_handle_datagram(ngx_connection_ pkt.path = path; pkt.flags = p[0]; pkt.raw->pos++; + pkt.server = is_server_packet; rc = ngx_quic_handle_packet(c, conf, &pkt); @@ -835,10 +1375,18 @@ ngx_quic_handle_packet(ngx_connection_t } } - if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { - return NGX_DECLINED; + if (qc->client) { + + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_client_start(c, pkt); + } + + } else { + + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } } - } rc = ngx_quic_handle_payload(c, pkt); @@ -991,6 +1539,14 @@ ngx_quic_handle_payload(ngx_connection_t c->log->action = "handling decrypted packet"; + if (qc->client && !qc->server_id_known) { + /* server generated new ID, use it (only if decrypted) */ + + ngx_memcpy(qc->path->cid->id, pkt->scid.data, pkt->scid.len); + qc->path->cid->len = pkt->scid.len; + qc->server_id_known = 1; + } + if (pkt->path == NULL) { rc = ngx_quic_set_path(c, pkt); if (rc != NGX_OK) { @@ -1378,6 +1934,19 @@ ngx_quic_handle_frames(ngx_connection_t break; + case NGX_QUIC_FT_HANDSHAKE_DONE: + ngx_quic_streams_notify_write(c); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + + if (ngx_quic_handle_new_token_frame(c, &frame.u.token) + != NGX_OK) + { + return NGX_ERROR; + } + break; + default: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic missing frame handler"); diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -31,6 +32,8 @@ typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); +typedef ngx_int_t (*ngx_quic_init_ssl_pt)(ngx_connection_t *c, void *data); + typedef enum { NGX_QUIC_STREAM_SEND_READY = 0, @@ -83,6 +86,7 @@ typedef struct { u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; + ngx_str_t alpn; } ngx_quic_conf_t; @@ -113,6 +117,13 @@ struct ngx_quic_stream_s { void ngx_quic_recvmsg(ngx_event_t *ev); void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); + +ngx_int_t ngx_quic_create_client(ngx_quic_conf_t *conf, ngx_connection_t *c); +ngx_int_t ngx_quic_connect(ngx_connection_t *c, ngx_quic_init_ssl_pt init_ssl, + void *data); +void ngx_quic_client_set_ssl_data(ngx_connection_t *c, void *data); +void *ngx_quic_client_get_ssl_data(ngx_connection_t *c); + ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -377,6 +377,19 @@ done: } +void +ngx_quic_congestion_reset(ngx_quic_connection_t *qc) +{ + ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; +} + + static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t pn) diff --git a/src/event/quic/ngx_event_quic_ack.h b/src/event/quic/ngx_event_quic_ack.h --- a/src/event/quic/ngx_event_quic_ack.h +++ b/src/event/quic/ngx_event_quic_ack.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -17,6 +18,7 @@ ngx_int_t ngx_quic_handle_ack_frame(ngx_ void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *frame); +void ngx_quic_congestion_reset(ngx_quic_connection_t *qc); void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); void ngx_quic_set_lost_timer(ngx_connection_t *c); void ngx_quic_pto_handler(ngx_event_t *ev); diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -284,6 +284,13 @@ struct ngx_quic_connection_s { ngx_uint_t shutdown_code; const char *shutdown_reason; + u_char incid[NGX_QUIC_SERVER_CID_LEN]; + + ngx_str_t client_retry; + ngx_str_t client_new_token; + ngx_quic_init_ssl_pt init_ssl; + void *init_ssl_data; + unsigned error_app:1; unsigned send_timer_set:1; unsigned closing:1; @@ -292,6 +299,9 @@ struct ngx_quic_connection_s { unsigned key_phase:1; unsigned validated:1; unsigned client_tp_done:1; + unsigned server_id_known:1; + unsigned client:1; + unsigned switch_keys:1; }; diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -25,12 +25,16 @@ static ngx_int_t ngx_quic_send_server_id ngx_int_t -ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id, ngx_uint_t client) { if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { return NGX_ERROR; } + if (client) { + return NGX_OK; + } + #if (NGX_QUIC_BPF) if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, @@ -204,6 +208,64 @@ done: } +ngx_int_t +ngx_quic_handle_new_token_frame(ngx_connection_t *c, + ngx_quic_new_token_frame_t *f) +{ + u_char *p; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->length == 0) { + + /* + * A client MUST treat receipt of a NEW_TOKEN frame with an empty + * Token field as a connection error of type FRAME_ENCODING_ERROR. + */ + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + qc->error_reason = "zero length NEW_TOKEN frame"; + + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic NEW_TOKEN frame of zero length"); + return NGX_ERROR; + } + + if (f->length > NGX_QUIC_MAX_NEW_TOKEN) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic NEW_TOKEN frame is too big: %ui", f->length); + return NGX_ERROR; + } + + p = qc->client_new_token.data; + if (p == NULL) { + + p = ngx_pnalloc(c->pool, NGX_QUIC_MAX_NEW_TOKEN); + if (p == NULL) { + return NGX_ERROR; + } + + qc->client_new_token.data = p; + } + + /* + * currently, keep only one token, and rewrite if multiple + * tokens received. + * + * the token is for use in 'future connections', so it is not + * really used currently. + */ + + ngx_memcpy(p, f->data, f->length); + qc->client_new_token.len = f->length; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new token received: %*xs", f->length, f->data); + + return NGX_OK; +} + + static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) { diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h --- a/src/event/quic/ngx_event_quic_connid.h +++ b/src/event/quic/ngx_event_quic_connid.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -17,8 +18,12 @@ ngx_int_t ngx_quic_handle_retire_connect ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_new_conn_id_frame_t *f); +ngx_int_t ngx_quic_handle_new_token_frame(ngx_connection_t *c, + ngx_quic_new_token_frame_t *f); + ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); -ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); +ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id, + ngx_uint_t client); ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, uint64_t seqnum, u_char *token); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -181,13 +181,7 @@ valid: ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); qc->rst_pnum = ctx->pnum; - ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); - - qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, - ngx_max(2 * qc->tp.max_udp_payload_size, - 14720)); - qc->congestion.ssthresh = (size_t) -1; - qc->congestion.recovery_start = ngx_current_msec; + ngx_quic_congestion_reset(qc); ngx_quic_init_rtt(qc); } @@ -313,7 +307,7 @@ ngx_quic_set_path(ngx_connection_t *c, n len = pkt->raw->last - pkt->raw->start; - if (c->udp->buffer == NULL) { + if (c->udp->buffer == NULL && !qc->client) { /* first ever packet in connection, path already exists */ path = qc->path; goto update; diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c --- a/src/event/quic/ngx_event_quic_openssl_compat.c +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -208,6 +208,10 @@ ngx_quic_compat_keylog_callback(const SS com = qc->compat; cipher = SSL_get_current_cipher(ssl); + if (qc->client) { + write = !write; + } + if (write) { com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); com->write_level = level; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -393,12 +394,13 @@ static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment) { - size_t clen; - ssize_t n; - uint16_t *valp; - struct iovec iov; - struct msghdr msg; - struct cmsghdr *cmsg; + size_t clen; + ssize_t n; + uint16_t *valp; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + ngx_quic_connection_t *qc; #if (NGX_HAVE_ADDRINFO_CMSG) char msg_control[CMSG_SPACE(sizeof(uint16_t)) @@ -416,8 +418,13 @@ ngx_quic_send_segments(ngx_connection_t msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; - msg.msg_namelen = socklen; + qc = ngx_quic_get_connection(c); + + if (qc == NULL || !qc->client) { + /* TODO: *BSD: socket already connected */ + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + } msg.msg_control = msg_control; msg.msg_controllen = sizeof(msg_control); @@ -467,15 +474,36 @@ ngx_quic_get_padding_level(ngx_connectio /* * RFC 9000, 14.1. Initial Datagram Size - * + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + if (qc->client) { + + /* + * A client MUST expand the payload of all UDP datagrams carrying + * Initial packets to at least the smallest allowed maximum datagram + * size of 1200 bytes + */ + + for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; + + if (ngx_queue_empty(&ctx->frames)) { + break; + } + } + + return i; + } + + /* * Similarly, a server MUST expand the payload of all UDP datagrams * carrying ack-eliciting Initial packets to at least the smallest * allowed maximum datagram size of 1200 bytes. */ - qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); q = ngx_queue_next(q)) @@ -660,6 +688,11 @@ ngx_quic_init_packet(ngx_connection_t *c if (ctx->level == ssl_encryption_initial) { pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + if (qc->client_retry.len) { + pkt->token = qc->client_retry; + pkt->token.len = qc->client_retry.len; + } + } else if (ctx->level == ssl_encryption_handshake) { pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; @@ -688,13 +721,14 @@ static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen) { - ssize_t n; - struct iovec iov; - struct msghdr msg; + ssize_t n; + struct iovec iov; + struct msghdr msg; #if (NGX_HAVE_ADDRINFO_CMSG) - struct cmsghdr *cmsg; - char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; + struct cmsghdr *cmsg; + char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif + ngx_quic_connection_t *qc; ngx_memzero(&msg, sizeof(struct msghdr)); @@ -704,8 +738,13 @@ ngx_quic_send(ngx_connection_t *c, u_cha msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; - msg.msg_namelen = socklen; + qc = ngx_quic_get_connection(c); + + if (qc == NULL || !qc->client) { + /* TODO: *BSD: socket already connected */ + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + } #if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { @@ -920,6 +959,11 @@ ngx_quic_send_early_cc(ngx_connection_t ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); + /* + * ngx_quic_send_early_cc() is only called from token check, i.e. server + * thus keys.client = 0 + */ + pkt.keys = &keys; if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) @@ -1216,10 +1260,18 @@ ngx_quic_frame_sendto(ngx_connection_t * ngx_quic_init_packet(c, ctx, &pkt, path); + if (qc->client && frame->level == ssl_encryption_initial + && min < NGX_QUIC_MIN_INITIAL_SIZE) + { + /* client must expand all initial packets */ + min = NGX_QUIC_MIN_INITIAL_SIZE; + } + min_payload = ngx_quic_payload_size(&pkt, min); max_payload = ngx_quic_payload_size(&pkt, max); /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; min_payload = ngx_max(min_payload, pad); @@ -1304,9 +1356,26 @@ ngx_quic_frame_sendto(ngx_connection_t * size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) { - off_t max; + off_t max; + ngx_quic_connection_t *qc; if (!path->validated) { + + qc = ngx_quic_get_connection(c); + + if (qc->client) { + + /* + * RFC 9000 21.1.1.1. Anti-Amplification + * + * The anti-amplification limit does not apply to clients when + * establishing a new connection or when initiating connection + * migration. + */ + + return size; + } + max = path->received * 3; max = (path->sent >= max) ? 0 : max - path->sent; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -40,6 +41,8 @@ static ngx_int_t ngx_quic_crypto_hp_init static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, ngx_log_t *log); static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); +static ngx_quic_secret_t *ngx_quic_select_secret(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write); static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -190,13 +193,13 @@ ngx_quic_keys_set_initial_secret(ngx_qui return NGX_ERROR; } - if (ngx_quic_crypto_init(ciphers.c, client, &client_key, 0, log) + if (ngx_quic_crypto_init(ciphers.c, client, &client_key, keys->client, log) == NGX_ERROR) { return NGX_ERROR; } - if (ngx_quic_crypto_init(ciphers.c, server, &server_key, 1, log) + if (ngx_quic_crypto_init(ciphers.c, server, &server_key, !keys->client, log) == NGX_ERROR) { goto failed; @@ -649,6 +652,17 @@ ngx_quic_crypto_hp_cleanup(ngx_quic_secr } +static ngx_quic_secret_t * +ngx_quic_select_secret(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write) +{ + return keys->client ? (is_write ? &keys->secrets[level].client + : &keys->secrets[level].server) + : (is_write ? &keys->secrets[level].server + : &keys->secrets[level].client); +} + + ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, @@ -662,8 +676,7 @@ ngx_quic_keys_set_encryption_secret(ngx_ ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; - peer_secret = is_write ? &keys->secrets[level].server - : &keys->secrets[level].client; + peer_secret = ngx_quic_select_secret(keys, level, is_write); keys->cipher = SSL_CIPHER_get_id(cipher); @@ -720,11 +733,11 @@ ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, ngx_uint_t is_write) { - if (is_write == 0) { - return keys->secrets[level].client.ctx != NULL; - } + ngx_quic_secret_t *s; - return keys->secrets[level].server.ctx != NULL; + s = ngx_quic_select_secret(keys, level, is_write); + + return (s->ctx != NULL); } @@ -827,13 +840,15 @@ ngx_quic_keys_update(ngx_event_t *ev) } } - if (ngx_quic_crypto_init(ciphers.c, &next->client, &client_key, 0, c->log) + if (ngx_quic_crypto_init(ciphers.c, &next->client, &client_key, + qc->client, c->log) == NGX_ERROR) { goto failed; } - if (ngx_quic_crypto_init(ciphers.c, &next->server, &server_key, 1, c->log) + if (ngx_quic_crypto_init(ciphers.c, &next->server, &server_key, + !qc->client, c->log) == NGX_ERROR) { goto failed; @@ -847,6 +862,16 @@ ngx_quic_keys_update(ngx_event_t *ev) ngx_explicit_memzero(client_key.data, client_key.len); ngx_explicit_memzero(server_key.data, server_key.len); + if (qc->switch_keys) { + qc->key_phase ^= 1; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic switching keys, phase: %ui", qc->key_phase); + + ngx_quic_keys_switch(c, qc->keys); + qc->switch_keys = 0; + } + return; failed: @@ -897,7 +922,8 @@ ngx_quic_create_packet(ngx_quic_header_t "quic ad len:%uz %xV", ad.len, &ad); #endif - secret = &pkt->keys->secrets[pkt->level].server; + secret = pkt->keys->client ? &pkt->keys->secrets[pkt->level].client + : &pkt->keys->secrets[pkt->level].server; ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); @@ -926,11 +952,9 @@ ngx_quic_create_packet(ngx_quic_header_t } -static ngx_int_t -ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +ngx_int_t +ngx_quic_retry_seal(ngx_str_t *ad, ngx_str_t *itag, ngx_log_t *log) { - u_char *start; - ngx_str_t ad, itag; ngx_quic_md_t key; ngx_quic_secret_t secret; ngx_quic_ciphers_t ciphers; @@ -942,15 +966,10 @@ ngx_quic_create_retry_packet(ngx_quic_he "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; static ngx_str_t in = ngx_string(""); - ad.data = res->data; - ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); - - itag.data = ad.data + ad.len; - itag.len = NGX_QUIC_TAG_LEN; #ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic retry itag len:%uz %xV", ad.len, &ad); + "quic retry itag len:%uz %xV", ad->len, ad); #endif if (ngx_quic_ciphers(NGX_QUIC_INITIAL_CIPHER, &ciphers) == NGX_ERROR) { @@ -961,13 +980,13 @@ ngx_quic_create_retry_packet(ngx_quic_he ngx_memcpy(key.data, key_data, sizeof(key_data)); secret.iv.len = NGX_QUIC_IV_LEN; - if (ngx_quic_crypto_init(ciphers.c, &secret, &key, 1, pkt->log) + if (ngx_quic_crypto_init(ciphers.c, &secret, &key, 1, log) == NGX_ERROR) { return NGX_ERROR; } - if (ngx_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) + if (ngx_quic_crypto_seal(&secret, itag, nonce, &in, ad, log) != NGX_OK) { ngx_quic_crypto_cleanup(&secret); @@ -976,6 +995,26 @@ ngx_quic_create_retry_packet(ngx_quic_he ngx_quic_crypto_cleanup(&secret); + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = NGX_QUIC_TAG_LEN; + + if (ngx_quic_retry_seal(&ad, &itag, pkt->log) != NGX_OK) { + return NGX_ERROR; + } + res->len = itag.data + itag.len - start; res->data = start; @@ -1109,10 +1148,14 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_int_t pnl; ngx_str_t in, ad; ngx_uint_t key_phase; - ngx_quic_secret_t *secret; + ngx_quic_secret_t *secret, *next; uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; - secret = &pkt->keys->secrets[pkt->level].client; + secret = pkt->keys->client ? &pkt->keys->secrets[pkt->level].server + : &pkt->keys->secrets[pkt->level].client; + + next = pkt->keys->client ? &pkt->keys->next_key.server + : &pkt->keys->next_key.client; p = pkt->raw->pos; len = pkt->data + pkt->len - p; @@ -1143,8 +1186,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, if (ngx_quic_short_pkt(pkt->flags)) { key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; - if (key_phase != pkt->key_phase) { - secret = &pkt->keys->next_key.client; + /* + * we don't want to switch to next key if we don't have it yet; + * the fact of being here may be caused by the header corruption + */ + if (key_phase != pkt->key_phase && next->ctx != NULL) { + secret = next; pkt->key_update = 1; } } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -64,6 +65,7 @@ struct ngx_quic_keys_s { ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; ngx_quic_secrets_t next_key; ngx_uint_t cipher; + ngx_uint_t client; }; @@ -115,6 +117,7 @@ ngx_int_t ngx_quic_crypto_seal(ngx_quic_ void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, ngx_log_t *log); +ngx_int_t ngx_quic_retry_seal(ngx_str_t *ad, ngx_str_t *itag, ngx_log_t *log); #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -35,10 +36,15 @@ ngx_quic_open_sockets(ngx_connection_t * ngx_queue_init(&qc->client_ids); ngx_queue_init(&qc->free_client_ids); - qc->tp.original_dcid.len = pkt->odcid.len; - qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); - if (qc->tp.original_dcid.data == NULL) { - return NGX_ERROR; + if (qc->client) { + qc->tp.original_dcid.len = 0; + + } else { + qc->tp.original_dcid.len = pkt->odcid.len; + qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->tp.original_dcid.data == NULL) { + return NGX_ERROR; + } } /* socket to use for further processing (id auto-generated) */ @@ -66,6 +72,10 @@ ngx_quic_open_sockets(ngx_connection_t * /* ngx_quic_get_connection(c) macro is now usable */ + if (qc->client) { + return NGX_OK; + } + /* we have a client identified by scid */ cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); if (cid == NULL) { @@ -104,7 +114,10 @@ ngx_quic_open_sockets(ngx_connection_t * failed: - ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (!qc->client) { + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } + c->udp = NULL; return NGX_ERROR; @@ -135,7 +148,7 @@ ngx_quic_create_socket(ngx_connection_t } sock->sid.len = NGX_QUIC_SERVER_CID_LEN; - if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { + if (ngx_quic_create_server_id(c, sock->sid.id, qc->client) != NGX_OK) { return NULL; } @@ -155,7 +168,10 @@ ngx_quic_close_socket(ngx_connection_t * ngx_queue_remove(&qsock->queue); ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); - ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (!qc->client) { + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } + qc->nsockets--; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -176,11 +192,13 @@ ngx_quic_listen(ngx_connection_t *c, ngx id.data = sid->id; id.len = sid->len; - qsock->udp.connection = c; - qsock->udp.node.key = ngx_crc32_long(id.data, id.len); - qsock->udp.key = id; + if (!qc->client) { + qsock->udp.connection = c; + qsock->udp.node.key = ngx_crc32_long(id.data, id.len); + qsock->udp.key = id; - ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + } ngx_queue_insert_tail(&qc->sockets, &qsock->queue); @@ -235,3 +253,34 @@ ngx_quic_find_socket(ngx_connection_t *c return NULL; } + + +ngx_quic_socket_t * +ngx_quic_find_socket_by_id(ngx_connection_t *c, ngx_str_t *key) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + if (key->len == 0) { + return NULL; + } + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (ngx_memn2cmp(key->data, qsock->sid.id, + key->len, qsock->sid.len) + == 0) + { + return qsock; + } + } + + return NULL; +} diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -23,6 +24,7 @@ ngx_int_t ngx_quic_listen(ngx_connection void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); - +ngx_quic_socket_t *ngx_quic_find_socket_by_id(ngx_connection_t *c, + ngx_str_t *key); #endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -195,18 +195,19 @@ ngx_quic_add_handshake_data(ngx_ssl_conn */ #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + if (!qc->client) { - SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); - if (alpn_len == 0) { - qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); - qc->error_reason = "unsupported protocol in ALPN extension"; + if (alpn_len == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "unsupported protocol in ALPN extension"; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } } - #endif SSL_get_peer_quic_transport_params(ssl_conn, &client_params, @@ -216,7 +217,37 @@ ngx_quic_add_handshake_data(ngx_ssl_conn "quic SSL_get_peer_quic_transport_params():" " params_len:%ui", client_params_len); - if (client_params_len == 0) { + if (client_params_len) { + + p = (u_char *) client_params; + end = p + client_params_len; + + /* defaults for parameters not sent by client */ + ngx_memcpy(&peer_tp, &qc->peer_tp, sizeof(ngx_quic_tp_t)); + + if (ngx_quic_parse_transport_params(p, end, &peer_tp, c->log, + qc->client) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + + return 0; + } + + if (ngx_quic_apply_transport_params(c, &peer_tp) != NGX_OK) { + return 0; + } + + qc->client_tp_done = 1; + + } else { + + if (qc->client) { + /* when we send 1st packet, no response from server yet */ + goto skip; + } + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); qc->error_reason = "missing transport parameters"; @@ -225,28 +256,9 @@ ngx_quic_add_handshake_data(ngx_ssl_conn "missing transport parameters"); return 0; } - - p = (u_char *) client_params; - end = p + client_params_len; - - /* defaults for parameters not sent by client */ - ngx_memcpy(&peer_tp, &qc->peer_tp, sizeof(ngx_quic_tp_t)); + } - if (ngx_quic_parse_transport_params(p, end, &peer_tp, c->log) - != NGX_OK) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "failed to process transport parameters"; - - return 0; - } - - if (ngx_quic_apply_transport_params(c, &peer_tp) != NGX_OK) { - return 0; - } - - qc->client_tp_done = 1; - } +skip: ctx = ngx_quic_get_send_ctx(qc, level); @@ -450,21 +462,51 @@ ngx_quic_crypto_input(ngx_connection_t * ngx_ssl_handshake_log(c); #endif - c->ssl->handshaked = 1; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { +#if !defined(NGX_QUIC_OPENSSL_COMPAT) + /* missing in compat, session reuse is not going to work there */ + if (SSL_process_quic_post_handshake(c->ssl->connection) != 1) { return NGX_ERROR; } +#endif - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(qc, frame); + c->ssl->handshaked = 1; + + if (qc->client) { + if (level != ssl_encryption_application) { + /* + * Server sends TLS Handshake Finished twice: + * in handshake and application level packets; + * + * perform h/s complete actions only once, when app received + */ + + return NGX_OK; + } + + /* flush output: need to ack with previous keys */ + if (ngx_quic_output(c) != NGX_OK) { + return NGX_ERROR; + } - if (qc->conf->retry) { - if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + /* initiate key update procedure */ + qc->switch_keys = 1; + + } else { + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { return NGX_ERROR; } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (qc->conf->retry) { + if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } } /* @@ -485,7 +527,7 @@ ngx_quic_crypto_input(ngx_connection_t * ngx_quic_discover_path_mtu(c, qc->path); - /* start accepting clients on negotiated number of server ids */ + /* start accepting packets on negotiated number of server ids */ if (ngx_quic_create_sockets(c) != NGX_OK) { return NGX_ERROR; } @@ -505,6 +547,7 @@ ngx_quic_init_connection(ngx_connection_ size_t clen; ssize_t len; ngx_str_t dcid; + ngx_uint_t flags; ngx_ssl_conn_t *ssl_conn; ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; @@ -512,7 +555,14 @@ ngx_quic_init_connection(ngx_connection_ qc = ngx_quic_get_connection(c); - if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { + flags = qc->client ? NGX_SSL_CLIENT : 0; + + if (c->ssl) { + /* retry packet case: reinit ssl */ + SSL_free(c->ssl->connection); + } + + if (ngx_ssl_create_connection(qc->conf->ssl, c, flags) != NGX_OK) { return NGX_ERROR; } @@ -538,6 +588,10 @@ ngx_quic_init_connection(ngx_connection_ return NGX_ERROR; } + if (qc->init_ssl && qc->init_ssl(c, qc->init_ssl_data) != NGX_OK) { + return NGX_ERROR; + } + #ifdef OPENSSL_INFO_QUIC if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { SSL_set_quic_early_data_enabled(ssl_conn, 1); @@ -555,7 +609,8 @@ ngx_quic_init_connection(ngx_connection_ return NGX_ERROR; } - len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen, + qc->client); /* always succeeds */ p = ngx_pnalloc(c->pool, len); @@ -563,7 +618,8 @@ ngx_quic_init_connection(ngx_connection_ return NGX_ERROR; } - len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL, + qc->client); if (len < 0) { return NGX_ERROR; } @@ -589,3 +645,33 @@ ngx_quic_init_connection(ngx_connection_ return NGX_OK; } + + +ngx_int_t +ngx_quic_client_handshake(ngx_connection_t *c) +{ + int n, sslerr; + ngx_ssl_conn_t *ssl_conn; + + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + + n = SSL_do_handshake(ssl_conn); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h --- a/src/event/quic/ngx_event_quic_ssl.h +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -16,4 +17,6 @@ ngx_int_t ngx_quic_init_connection(ngx_c ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_client_handshake(ngx_connection_t *c); + #endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -13,6 +13,17 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +#define ngx_quic_uni_stream(id) \ + ((id) & NGX_QUIC_STREAM_UNIDIRECTIONAL) + +#define ngx_quic_out_stream(qc, id) \ + (((qc)->client && (((id) & NGX_QUIC_STREAM_SERVER_INITIATED) == 0)) \ + || ((!(qc)->client) && ((id) & NGX_QUIC_STREAM_SERVER_INITIATED))) + +#define ngx_quic_input_stream(qc, id) \ + (((qc)->client && ((id) & NGX_QUIC_STREAM_SERVER_INITIATED)) \ + || ((!(qc)->client) && (((id) & NGX_QUIC_STREAM_SERVER_INITIATED) == 0))) + static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err); @@ -25,6 +36,8 @@ static void ngx_quic_init_streams_handle static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, uint64_t id); +static void ngx_quic_stream_init_state(ngx_quic_connection_t *qc, + ngx_quic_stream_t *qs); static void ngx_quic_empty_handler(ngx_event_t *ev); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -32,6 +45,10 @@ static ssize_t ngx_quic_stream_send(ngx_ size_t size); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); +static ssize_t ngx_quic_stream_recv_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static void ngx_quic_copy_chain_data(ngx_chain_t *dst_chain, + ngx_chain_t *src_chain); static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); static void ngx_quic_stream_cleanup_handler(void *data); static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); @@ -46,62 +63,51 @@ static void ngx_quic_set_event(ngx_event ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { - uint64_t id; - ngx_connection_t *pc, *sc; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + uint64_t id; + ngx_connection_t *pc, *sc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_ctl_t *sctl; + ngx_quic_stream_peer_t *peer; pc = c->quic ? c->quic->parent : c; qc = ngx_quic_get_connection(pc); if (qc->closing) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic open stream failed: already closing"); + return NULL; + } + + peer = qc->client ? &qc->streams.client : &qc->streams.server; + sctl = bidi ? &peer->bidi : &peer->uni; + + if (sctl->count >= sctl->max) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many %s %s streams:%uL", + qc->client ? "client" : "server", bidi ? "bidi": "uni", + sctl->count); return NULL; } - if (bidi) { - if (qc->streams.server.bidi.count - >= qc->streams.server.bidi.max) - { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server bidi streams:%uL", - qc->streams.server.bidi.count); - return NULL; - } - - id = (qc->streams.server.bidi.count << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server.bidi.count, - qc->streams.server.bidi.max, id); - - qc->streams.server.bidi.count++; - - } else { - if (qc->streams.server.uni.count - >= qc->streams.server.uni.max) - { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server uni streams:%uL", - qc->streams.server.uni.count); - return NULL; - } - - id = (qc->streams.server.uni.count << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED - | NGX_QUIC_STREAM_UNIDIRECTIONAL; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server uni stream" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server.uni.count, - qc->streams.server.uni.max, id); - - qc->streams.server.uni.count++; + id = (sctl->count << 2); + + if (!bidi) { + id |= NGX_QUIC_STREAM_UNIDIRECTIONAL; + } + + if (!qc->client) { + id |= NGX_QUIC_STREAM_SERVER_INITIATED; } + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating %s %s stream" + " streams:%uL max:%uL id:0x%xL", + qc->client ? "client" : "server", bidi ? "bidi" : "uni", + sctl->count, sctl->max, id); + + sctl->count++; + qs = ngx_quic_create_stream(pc, id); if (qs == NULL) { return NULL; @@ -237,6 +243,11 @@ ngx_quic_close_streams(ngx_connection_t ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) { + if (!c->quic->parent->ssl->handshaked) { + /* the stream was created early, do not try to send anything */ + return NGX_OK; + } + return ngx_quic_do_reset_stream(c->quic, err); } @@ -290,6 +301,15 @@ ngx_quic_do_reset_stream(ngx_quic_stream ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how) { + ngx_connection_t *pc; + + pc = c->quic->parent; + + if (!pc->ssl->handshaked) { + /* the stream was created early, do not try to send anything */ + return NGX_OK; + } + if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { return NGX_ERROR; @@ -374,10 +394,12 @@ ngx_quic_shutdown_stream_recv(ngx_connec static ngx_quic_stream_t * ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) { - uint64_t min_id; - ngx_event_t *rev; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + uint64_t min_id; + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_ctl_t *sctl; + ngx_quic_stream_peer_t *peer; qc = ngx_quic_get_connection(c); @@ -394,54 +416,45 @@ ngx_quic_get_stream(ngx_connection_t *c, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL is missing", id); - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server.uni.count) { - return NGX_QUIC_STREAM_GONE; - } - - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } - - if ((id >> 2) < qc->streams.client.uni.count) { + if (ngx_quic_out_stream(qc, id)) { + /* stream is initiated by us, but peer is trying to use it */ + + peer = qc->client ? &qc->streams.client : &qc->streams.server; + + sctl = ngx_quic_uni_stream(id) ? &peer->uni : &peer->bidi; + + if ((id >> 2) < sctl->count) { return NGX_QUIC_STREAM_GONE; } - if ((id >> 2) >= qc->streams.client.uni.max) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } - - min_id = (qc->streams.client.uni.count << 2) - | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client.uni.count = (id >> 2) + 1; - - } else { - - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server.bidi.count) { - return NGX_QUIC_STREAM_GONE; - } - - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } - - if ((id >> 2) < qc->streams.client.bidi.count) { - return NGX_QUIC_STREAM_GONE; - } - - if ((id >> 2) >= qc->streams.client.bidi.max) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } - - min_id = (qc->streams.client.bidi.count << 2); - qc->streams.client.bidi.count = (id >> 2) + 1; + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + peer = qc->client ? &qc->streams.server: &qc->streams.client; + sctl = ngx_quic_uni_stream(id) ? &peer->uni : &peer->bidi; + + if ((id >> 2) < sctl->count) { + return NGX_QUIC_STREAM_GONE; } + if ((id >> 2) >= sctl->max) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = sctl->count << 2; + + if (qc->client) { + min_id++; + } + + if (ngx_quic_uni_stream(id)) { + min_id |= NGX_QUIC_STREAM_UNIDIRECTIONAL; + } + + sctl->count = (id >> 2) + 1; + /* * RFC 9000, 2.1. Stream Types and Identifiers * @@ -504,9 +517,8 @@ ngx_quic_reject_stream(ngx_connection_t qc = ngx_quic_get_connection(c); - code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - ? qc->conf->stream_reject_code_uni - : qc->conf->stream_reject_code_bidi; + code = ngx_quic_uni_stream(id) ? qc->conf->stream_reject_code_uni + : qc->conf->stream_reject_code_bidi; if (code == 0) { return NGX_DECLINED; @@ -555,7 +567,7 @@ ngx_quic_init_stream_handler(ngx_event_t ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); - if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + if (!ngx_quic_uni_stream(qs->id)) { c->write->active = 1; c->write->ready = 1; } @@ -755,40 +767,17 @@ ngx_quic_create_stream(ngx_connection_t sc->recv = ngx_quic_stream_recv; sc->send = ngx_quic_stream_send; sc->send_chain = ngx_quic_stream_send_chain; - - sc->read->log = log; - sc->write->log = log; + sc->recv_chain = ngx_quic_stream_recv_chain; + + sc->read->log = c->log; + sc->write->log = c->log; sc->read->handler = ngx_quic_empty_handler; sc->write->handler = ngx_quic_empty_handler; log->connection = sc->number; - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - qs->send_max_data = qc->peer_tp.initial_max_stream_data_uni; - qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; - qs->send_state = NGX_QUIC_STREAM_SEND_READY; - - } else { - qs->recv_max_data = qc->tp.initial_max_stream_data_uni; - qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; - qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; - } - - } else { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - qs->send_max_data = qc->peer_tp.initial_max_stream_data_bidi_remote; - qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; - - } else { - qs->send_max_data = qc->peer_tp.initial_max_stream_data_bidi_local; - qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; - } - - qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; - qs->send_state = NGX_QUIC_STREAM_SEND_READY; - } + ngx_quic_stream_init_state(qc, qs); qs->recv_window = qs->recv_max_data; @@ -810,6 +799,101 @@ ngx_quic_create_stream(ngx_connection_t } +static void +ngx_quic_stream_init_state(ngx_quic_connection_t *qc, ngx_quic_stream_t *qs) +{ + ngx_uint_t out; + + out = ngx_quic_out_stream(qc, qs->id); + + if (ngx_quic_uni_stream(qs->id)) { + if (out) { + qs->send_max_data = qc->peer_tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + + } else { + qs->recv_max_data = qc->tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + } else { + if (out) { + qs->send_max_data = qc->peer_tp.initial_max_stream_data_bidi_remote; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; + + } else { + qs->send_max_data = qc->peer_tp.initial_max_stream_data_bidi_local; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + } +} + + +void +ngx_quic_streams_init_state(ngx_connection_t *c) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return; + } + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + ngx_quic_stream_init_state(qc, qs); + } +} + + +void +ngx_quic_streams_notify_write(ngx_connection_t *c) +{ + ngx_rbtree_t *tree; + ngx_connection_t *sc; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return; + } + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + sc = qs->connection; + if (sc == NULL) { + continue; + } + + ngx_post_event(sc->write, &ngx_posted_events); + } +} + + void ngx_quic_cancelable_stream(ngx_connection_t *c) { @@ -859,6 +943,8 @@ ngx_quic_stream_recv(ngx_connection_t *c || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) { qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL bad recv state", qs->id); return NGX_ERROR; } @@ -1002,6 +1088,129 @@ ngx_quic_stream_send_chain(ngx_connectio } +static ssize_t +ngx_quic_stream_recv_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + size_t len; + ngx_buf_t *b; + ngx_chain_t *cl, *out; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + + qs = c->quic; + pc = qs->parent; + rev = c->read; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL bad recv state", qs->id); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv chain", qs->id); + + len = 0; + for (cl = in; cl; cl = cl->next) { + len += cl->buf->end - cl->buf->last; + } + + if (limit && len > (size_t) limit) { + len = limit; + } + + out = ngx_quic_read_buffer(pc, &qs->recv, len); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = 0; + + for (cl = out; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + } + + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD + && qs->recv_offset == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv chain() not ready", qs->id); + return NGX_AGAIN; + } + + ngx_quic_copy_chain_data(in, out); + + ngx_quic_free_chain(pc, out); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv chain len:%z", qs->id, len); + + if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { + return NGX_ERROR; + } + + return len; +} + + +static void +ngx_quic_copy_chain_data(ngx_chain_t *dst_chain, ngx_chain_t *src_chain) +{ + u_char *rpos, *wpos; + size_t data_size, buf_size, len; + ngx_chain_t *src, *dst; + + src = src_chain; + dst = dst_chain; + + rpos = src->buf->pos; + wpos = dst->buf->last; + + while (src && dst) { + + data_size = src->buf->last - rpos; + buf_size = dst->buf->end - wpos; + + len = ngx_min(data_size, buf_size); + + ngx_memcpy(wpos, rpos, len); + + rpos += len; + wpos += len; + + if (rpos == src->buf->last) { + src = src->next; + if (src) { + rpos = src->buf->pos; + } + } + + if (wpos == dst->buf->end) { + dst = dst->next; + if (dst) { + wpos = dst->buf->last; + } + } + } +} + + static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs) { @@ -1094,7 +1303,13 @@ ngx_quic_stream_cleanup_handler(void *da qs = c->quic; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + /* stream log was allocated from pool, now deleted */ + c->log = qs->parent->log; + c->read->log = c->log; + c->write->log = c->log; + c->pool->log = c->log; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL cleanup", qs->id); if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { @@ -1121,9 +1336,11 @@ failed: static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs) { - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + ngx_quic_stream_ctl_t *sctl; + ngx_quic_stream_peer_t *peer; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -1166,7 +1383,12 @@ ngx_quic_close_stream(ngx_quic_stream_t return NGX_OK; } - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + if (ngx_quic_input_stream(qc, qs->id)) { + + peer = qc->client ? &qc->streams.server : &qc->streams.client; + + sctl = ngx_quic_uni_stream(qs->id) ? &peer->uni : &peer->bidi; + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_ERROR; @@ -1175,14 +1397,8 @@ ngx_quic_close_stream(ngx_quic_stream_t frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_MAX_STREAMS; - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - frame->u.max_streams.limit = ++qc->streams.client.uni.max; - frame->u.max_streams.bidi = 0; - - } else { - frame->u.max_streams.limit = ++qc->streams.client.bidi.max; - frame->u.max_streams.bidi = 1; - } + frame->u.max_streams.limit = ++sctl->max; + frame->u.max_streams.bidi = !ngx_quic_uni_stream(qs->id); ngx_quic_queue_frame(qc, frame); } @@ -1232,8 +1448,8 @@ ngx_quic_handle_stream_frame(ngx_connect qc = ngx_quic_get_connection(c); f = &frame->u.stream; - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + if (ngx_quic_uni_stream(f->stream_id) + && ngx_quic_out_stream(qc, f->stream_id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; @@ -1377,9 +1593,7 @@ ngx_quic_handle_stream_data_blocked_fram qc = ngx_quic_get_connection(c); - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { + if (ngx_quic_uni_stream(f->id) && ngx_quic_out_stream(qc, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; } @@ -1407,9 +1621,14 @@ ngx_quic_handle_max_stream_data_frame(ng qc = ngx_quic_get_connection(c); - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { + /* + * RFC 9000, 19.10. MAX_STREAM_DATA Frames + * + * An endpoint that receives a MAX_STREAM_DATA frame for a receive-only + * stream MUST terminate the connection with error STREAM_STATE_ERROR. + */ + + if (ngx_quic_uni_stream(f->id) && ngx_quic_input_stream(qc, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; } @@ -1450,9 +1669,7 @@ ngx_quic_handle_reset_stream_frame(ngx_c qc = ngx_quic_get_connection(c); - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { + if (ngx_quic_uni_stream(f->id) && ngx_quic_out_stream(qc, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; } @@ -1519,9 +1736,14 @@ ngx_quic_handle_stop_sending_frame(ngx_c qc = ngx_quic_get_connection(c); - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { + /* + * RFC 9000, 19.5. STOP_SENDING Frames + * + * An endpoint that receives a STOP_SENDING frame for a receive-only + * stream MUST terminate the connection with error STREAM_STATE_ERROR. + */ + + if (ngx_quic_uni_stream(f->id) && ngx_quic_input_stream(qc, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; } @@ -1554,25 +1776,19 @@ ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) { + ngx_quic_stream_ctl_t *sctl; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - if (f->bidi) { - if (qc->streams.server.bidi.max < f->limit) { - qc->streams.server.bidi.max = f->limit; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_bidi:%uL", f->limit); - } - - } else { - if (qc->streams.server.uni.max < f->limit) { - qc->streams.server.uni.max = f->limit; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_uni:%uL", f->limit); - } + sctl = f->bidi ? &qc->streams.server.bidi : &qc->streams.server.uni; + + if (sctl->max < f->limit) { + sctl->max = f->limit; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_%s:%uL", + f->bidi? "bidi": "uni", f->limit); } return NGX_OK; diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -34,6 +35,8 @@ ngx_int_t ngx_quic_handle_max_streams_fr ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f); ngx_int_t ngx_quic_init_streams(ngx_connection_t *c); +void ngx_quic_streams_init_state(ngx_connection_t *c); +void ngx_quic_streams_notify_write(ngx_connection_t *c); void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -307,3 +308,50 @@ bad_token: return NGX_DECLINED; } + + +ngx_int_t +ngx_quic_verify_retry_token_integrity(ngx_connection_t *c, + ngx_quic_header_t *pkt) +{ + u_char *p; + size_t size; + ngx_str_t ad, itag, pkt_tag; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* integrity tag from retry packet */ + pkt_tag.data = pkt->data + pkt->len - NGX_QUIC_TAG_LEN; + + /* pseudo packet size */ + size = pkt->len + 1 /* od len */ + 20 /* odcid */; + + p = ngx_pcalloc(c->pool, size); + if (p == NULL) { + return NGX_ERROR; + } + + ad.data = p; + + *p++ = NGX_QUIC_SERVER_CID_LEN; + p = ngx_cpymem(p, qc->incid, NGX_QUIC_SERVER_CID_LEN); + p = ngx_cpymem(p, pkt->data, pkt->len - NGX_QUIC_TAG_LEN); + + ad.len = p - ad.data; + + /* integrity tag to calculate using pseudo packet input */ + itag.data = ad.data + ad.len; + itag.len = NGX_QUIC_TAG_LEN; + + if (ngx_quic_retry_seal(&ad, &itag, pkt->log) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_memcmp(pkt_tag.data, itag.data, NGX_QUIC_TAG_LEN) != 0) { + return NGX_ERROR; + } + + return NGX_OK; +} + diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h --- a/src/event/quic/ngx_event_quic_tokens.h +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -15,6 +16,12 @@ #define NGX_QUIC_MAX_TOKEN_SIZE 64 /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ +/* + * max size is not specified, this is arbitrary limit + * to match values found in the wild + */ +#define NGX_QUIC_MAX_NEW_TOKEN (NGX_QUIC_MAX_TOKEN_SIZE * 2) + #define NGX_QUIC_AES_256_GCM_IV_LEN 12 #define NGX_QUIC_AES_256_GCM_TAG_LEN 16 @@ -30,5 +37,7 @@ ngx_int_t ngx_quic_new_token(ngx_log_t * time_t expires, ngx_uint_t is_retry); ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, u_char *key, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_verify_retry_token_integrity(ngx_connection_t *c, + ngx_quic_header_t *pkt); #endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -441,7 +442,7 @@ ngx_quic_parse_long_header_v1(ngx_quic_h if (ngx_quic_pkt_in(pkt->flags)) { - if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + if (!pkt->server && pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic UDP datagram is too small for initial packet"); return NGX_DECLINED; @@ -471,6 +472,29 @@ ngx_quic_parse_long_header_v1(ngx_quic_h } else if (ngx_quic_pkt_hs(pkt->flags)) { pkt->level = ssl_encryption_handshake; + } else if (ngx_quic_pkt_retry(pkt->flags)) { + pkt->level = ssl_encryption_initial; + + pkt->token.len = end - p; + + if (pkt->token.len < 17) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic retry packet too small"); + return NGX_ERROR; + } + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p - pkt->data; + + return NGX_OK; + } else { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet type"); @@ -619,15 +643,19 @@ static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) { - size_t rem_len; + size_t rem_len, tlen; u_char *p, *start; + tlen = (pkt->level != ssl_encryption_initial) + ? 0 + : ngx_quic_varint_len(pkt->token.len) + pkt->token.len; + rem_len = pkt->num_len + pkt->payload.len + NGX_QUIC_TAG_LEN; if (out == NULL) { return 5 + 2 + pkt->dcid.len + pkt->scid.len + ngx_quic_varint_len(rem_len) + pkt->num_len - + (pkt->level == ssl_encryption_initial ? 1 : 0); + + tlen; } p = start = out; @@ -643,7 +671,11 @@ ngx_quic_create_long_header(ngx_quic_hea p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); if (pkt->level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, pkt->token.len); + + if (pkt->token.len) { + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + } } ngx_quic_build_int(&p, rem_len); @@ -1128,6 +1160,26 @@ ngx_quic_parse_frame(ngx_quic_header_t * break; + case NGX_QUIC_FT_HANDSHAKE_DONE: + break; + + case NGX_QUIC_FT_NEW_TOKEN: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.token.length = varint; + + p = ngx_quic_read_bytes(p, end, f->u.token.length, + &f->u.token.data); + if (p == NULL) { + goto error; + } + + break; + default: ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unknown frame type 0x%xi", f->type); @@ -1157,43 +1209,48 @@ ngx_quic_frame_allowed(ngx_quic_header_t { uint8_t ptype; +#define PKT_SRV 0x10 +#define PKT_CLN 0x20 +#define PKT_ANY 0x30 + /* * RFC 9000, 12.4. Frames and Frame Types: Table 3 * - * Frame permissions per packet: 4 bits: IH01 + * Frame permissions per packet: 6 bits: CSIH01 + * Two high bits: 'allowed on client' and 'allowed on server' */ static uint8_t ngx_quic_frame_masks[] = { - /* PADDING */ 0xF, - /* PING */ 0xF, - /* ACK */ 0xD, - /* ACK_ECN */ 0xD, - /* RESET_STREAM */ 0x3, - /* STOP_SENDING */ 0x3, - /* CRYPTO */ 0xD, - /* NEW_TOKEN */ 0x0, /* only sent by server */ - /* STREAM */ 0x3, - /* STREAM1 */ 0x3, - /* STREAM2 */ 0x3, - /* STREAM3 */ 0x3, - /* STREAM4 */ 0x3, - /* STREAM5 */ 0x3, - /* STREAM6 */ 0x3, - /* STREAM7 */ 0x3, - /* MAX_DATA */ 0x3, - /* MAX_STREAM_DATA */ 0x3, - /* MAX_STREAMS */ 0x3, - /* MAX_STREAMS2 */ 0x3, - /* DATA_BLOCKED */ 0x3, - /* STREAM_DATA_BLOCKED */ 0x3, - /* STREAMS_BLOCKED */ 0x3, - /* STREAMS_BLOCKED2 */ 0x3, - /* NEW_CONNECTION_ID */ 0x3, - /* RETIRE_CONNECTION_ID */ 0x3, - /* PATH_CHALLENGE */ 0x3, - /* PATH_RESPONSE */ 0x1, - /* CONNECTION_CLOSE */ 0xF, - /* CONNECTION_CLOSE2 */ 0x3, - /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + /* PADDING */ 0xF | PKT_ANY, + /* PING */ 0xF | PKT_ANY, + /* ACK */ 0xD | PKT_ANY, + /* ACK_ECN */ 0xD | PKT_ANY, + /* RESET_STREAM */ 0x3 | PKT_ANY, + /* STOP_SENDING */ 0x3 | PKT_ANY, + /* CRYPTO */ 0xD | PKT_ANY, + /* NEW_TOKEN */ 0x0 | PKT_SRV, /* only sent by server */ + /* STREAM */ 0x3 | PKT_ANY, + /* STREAM1 */ 0x3 | PKT_ANY, + /* STREAM2 */ 0x3 | PKT_ANY, + /* STREAM3 */ 0x3 | PKT_ANY, + /* STREAM4 */ 0x3 | PKT_ANY, + /* STREAM5 */ 0x3 | PKT_ANY, + /* STREAM6 */ 0x3 | PKT_ANY, + /* STREAM7 */ 0x3 | PKT_ANY, + /* MAX_DATA */ 0x3 | PKT_ANY, + /* MAX_STREAM_DATA */ 0x3 | PKT_ANY, + /* MAX_STREAMS */ 0x3 | PKT_ANY, + /* MAX_STREAMS2 */ 0x3 | PKT_ANY, + /* DATA_BLOCKED */ 0x3 | PKT_ANY, + /* STREAM_DATA_BLOCKED */ 0x3 | PKT_ANY, + /* STREAMS_BLOCKED */ 0x3 | PKT_ANY, + /* STREAMS_BLOCKED2 */ 0x3 | PKT_ANY, + /* NEW_CONNECTION_ID */ 0x3 | PKT_ANY, + /* RETIRE_CONNECTION_ID */ 0x3 | PKT_ANY, + /* PATH_CHALLENGE */ 0x3 | PKT_ANY, + /* PATH_RESPONSE */ 0x1 | PKT_ANY, + /* CONNECTION_CLOSE */ 0xF | PKT_ANY, + /* CONNECTION_CLOSE2 */ 0x3 | PKT_ANY, + /* HANDSHAKE_DONE */ 0x0 | PKT_SRV, /* only sent by server */ }; if (ngx_quic_long_pkt(pkt->flags)) { @@ -1212,6 +1269,8 @@ ngx_quic_frame_allowed(ngx_quic_header_t ptype = 1; /* application data */ } + ptype |= (pkt->server ? PKT_SRV : PKT_CLN); + if (ptype & ngx_quic_frame_masks[frame_type]) { return NGX_OK; } @@ -1648,7 +1707,10 @@ ngx_quic_parse_transport_param(u_char *p } break; + case NGX_QUIC_TP_ORIGINAL_DCID: case NGX_QUIC_TP_INITIAL_SCID: + case NGX_QUIC_TP_SR_TOKEN: + case NGX_QUIC_TP_RETRY_SCID: str.len = end - p; str.data = p; @@ -1708,6 +1770,28 @@ ngx_quic_parse_transport_param(u_char *p dst->initial_scid = str; break; + case NGX_QUIC_TP_SR_TOKEN: + + if (str.len != NGX_QUIC_SR_TOKEN_LEN) { + return NGX_ERROR; + } + ngx_memcpy(dst->sr_token, str.data, NGX_QUIC_SR_TOKEN_LEN); + break; + + case NGX_QUIC_TP_ORIGINAL_DCID: + if (str.len > NGX_QUIC_CID_LEN_MAX) { + return NGX_ERROR; + } + dst->original_dcid = str; + break; + + case NGX_QUIC_TP_RETRY_SCID: + if (str.len > NGX_QUIC_CID_LEN_MAX) { + return NGX_ERROR; + } + dst->retry_scid = str; + break; + default: return NGX_ERROR; } @@ -1718,7 +1802,7 @@ ngx_quic_parse_transport_param(u_char *p ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, - ngx_log_t *log) + ngx_log_t *log, ngx_uint_t client) { uint64_t id, len; ngx_int_t rc; @@ -1736,10 +1820,12 @@ ngx_quic_parse_transport_params(u_char * case NGX_QUIC_TP_PREFERRED_ADDRESS: case NGX_QUIC_TP_RETRY_SCID: case NGX_QUIC_TP_SR_TOKEN: - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic client sent forbidden transport param" - " id:0x%xL", id); - return NGX_ERROR; + if (!client) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id:0x%xL", id); + return NGX_ERROR; + } } p = ngx_quic_parse_int(p, end, &len); @@ -1828,6 +1914,24 @@ ngx_quic_parse_transport_params(u_char * "quic tp initial source_connection_id len:%uz %xV", tp->initial_scid.len, &tp->initial_scid); + if (client) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp stateless reset token %*xs", + NGX_QUIC_SR_TOKEN_LEN, tp->sr_token); + + if (tp->original_dcid.len) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp original_dcid len:%uz %xV", + tp->original_dcid.len, &tp->original_dcid); + } + + if (tp->retry_scid.len) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp retry_scid len:%uz %xV", + tp->retry_scid.len, &tp->retry_scid); + } + } + return NGX_OK; } @@ -2015,7 +2119,7 @@ ngx_quic_init_transport_params(ngx_quic_ ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, - size_t *clen) + size_t *clen, ngx_uint_t client) { u_char *p; size_t len; @@ -2086,16 +2190,21 @@ ngx_quic_create_transport_params(u_char len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, tp->ack_delay_exponent); - len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + if (!client) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } + len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); if (tp->retry_scid.len) { len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); } - len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); - len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); - len += NGX_QUIC_SR_TOKEN_LEN; + if (!client) { + len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); + len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); + len += NGX_QUIC_SR_TOKEN_LEN; + } if (pos == NULL) { return len; @@ -2141,16 +2250,21 @@ ngx_quic_create_transport_params(u_char ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, tp->ack_delay_exponent); - ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + if (!client) { + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } + ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); if (tp->retry_scid.len) { ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); } - ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); - ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); - p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + if (!client) { + ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); + ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); + p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + } return p - pos; } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Nginx, Inc. */ @@ -167,6 +168,7 @@ typedef struct { typedef struct { uint64_t length; + u_char *data; } ngx_quic_new_token_frame_t; /* @@ -337,6 +339,7 @@ typedef struct { unsigned first:1; unsigned rebound:1; unsigned path_challenged:1; + unsigned server:1; /* is from server */ } ngx_quic_header_t; @@ -388,9 +391,9 @@ size_t ngx_quic_create_ack_range(u_char ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf); ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, - ngx_quic_tp_t *tp, ngx_log_t *log); + ngx_quic_tp_t *tp, ngx_log_t *log, ngx_uint_t client); ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, - ngx_quic_tp_t *tp, size_t *clen); + ngx_quic_tp_t *tp, size_t *clen, ngx_uint_t client); void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
_______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel