From 4f1b4c4115f068faf1961e8053153259b55b0ef0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 1 Jul 2025 15:37:43 +0400 Subject: [PATCH 01/13] Events: ignoring handling events for shared connections. --- src/event/ngx_event.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index ef525d93b..1fd94ae20 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -267,18 +267,14 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle) ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { -#if (NGX_QUIC) - ngx_connection_t *c; c = rev->data; - if (c->quic) { + if (c->shared) { return NGX_OK; } -#endif - if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -351,11 +347,9 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) c = wev->data; -#if (NGX_QUIC) - if (c->quic) { + if (c->shared) { return NGX_OK; } -#endif if (lowat) { if (ngx_send_lowat(c, lowat) == NGX_ERROR) { From f1e48a2152120bd104d69376b5513638acf10f6e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 Feb 2025 16:59:35 +0400 Subject: [PATCH 02/13] QUIC: renamed client/server keys to read/write. This is a preparation for introducing QUIC client support. --- src/event/quic/ngx_event_quic_protection.c | 124 ++++++++++----------- src/event/quic/ngx_event_quic_protection.h | 4 +- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 2f28737a2..abd4a9518 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -136,8 +136,8 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a }; - client = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].client; - server = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].server; + client = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].read; + server = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].write; /* * RFC 9001, section 5. Packet Protection @@ -673,8 +673,8 @@ ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, 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 = is_write ? &keys->secrets[level].write + : &keys->secrets[level].read; keys->cipher = SSL_CIPHER_get_id(cipher); @@ -732,35 +732,35 @@ ngx_quic_keys_available(ngx_quic_keys_t *keys, ngx_uint_t level, ngx_uint_t is_write) { if (is_write == 0) { - return keys->secrets[level].client.ctx != NULL; + return keys->secrets[level].read.ctx != NULL; } - return keys->secrets[level].server.ctx != NULL; + return keys->secrets[level].write.ctx != NULL; } void ngx_quic_keys_discard(ngx_quic_keys_t *keys, ngx_uint_t level) { - ngx_quic_secret_t *client, *server; + ngx_quic_secret_t *read, *write; - client = &keys->secrets[level].client; - server = &keys->secrets[level].server; + read = &keys->secrets[level].read; + write = &keys->secrets[level].write; - ngx_quic_crypto_cleanup(client); - ngx_quic_crypto_cleanup(server); + ngx_quic_crypto_cleanup(read); + ngx_quic_crypto_cleanup(write); - ngx_quic_crypto_hp_cleanup(client); - ngx_quic_crypto_hp_cleanup(server); + ngx_quic_crypto_hp_cleanup(read); + ngx_quic_crypto_hp_cleanup(write); - if (client->secret.len) { - ngx_explicit_memzero(client->secret.data, client->secret.len); - client->secret.len = 0; + if (read->secret.len) { + ngx_explicit_memzero(read->secret.data, read->secret.len); + read->secret.len = 0; } - if (server->secret.len) { - ngx_explicit_memzero(server->secret.data, server->secret.len); - server->secret.len = 0; + if (write->secret.len) { + ngx_explicit_memzero(write->secret.data, write->secret.len); + write->secret.len = 0; } } @@ -773,8 +773,8 @@ ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) current = &keys->secrets[NGX_QUIC_ENCRYPTION_APPLICATION]; next = &keys->next_key; - ngx_quic_crypto_cleanup(¤t->client); - ngx_quic_crypto_cleanup(¤t->server); + ngx_quic_crypto_cleanup(¤t->read); + ngx_quic_crypto_cleanup(¤t->write); tmp = *current; *current = *next; @@ -787,7 +787,7 @@ ngx_quic_keys_update(ngx_event_t *ev) { ngx_int_t key_len; ngx_uint_t i; - ngx_quic_md_t client_key, server_key; + ngx_quic_md_t read_key, write_key; ngx_quic_hkdf_t seq[6]; ngx_quic_keys_t *keys; ngx_connection_t *c; @@ -812,31 +812,31 @@ ngx_quic_keys_update(ngx_event_t *ev) goto failed; } - client_key.len = key_len; - server_key.len = key_len; + read_key.len = key_len; + write_key.len = key_len; - next->client.secret.len = current->client.secret.len; - next->client.iv.len = NGX_QUIC_IV_LEN; - next->client.hp = current->client.hp; - next->client.hp_ctx = current->client.hp_ctx; + next->read.secret.len = current->read.secret.len; + next->read.iv.len = NGX_QUIC_IV_LEN; + next->read.hp = current->read.hp; + next->read.hp_ctx = current->read.hp_ctx; - next->server.secret.len = current->server.secret.len; - next->server.iv.len = NGX_QUIC_IV_LEN; - next->server.hp = current->server.hp; - next->server.hp_ctx = current->server.hp_ctx; + next->write.secret.len = current->write.secret.len; + next->write.iv.len = NGX_QUIC_IV_LEN; + next->write.hp = current->write.hp; + next->write.hp_ctx = current->write.hp_ctx; ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", - &next->client.secret, ¤t->client.secret); + &next->read.secret, ¤t->read.secret); ngx_quic_hkdf_set(&seq[1], "tls13 quic key", - &client_key, &next->client.secret); + &read_key, &next->read.secret); ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", - &next->client.iv, &next->client.secret); + &next->read.iv, &next->read.secret); ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", - &next->server.secret, ¤t->server.secret); + &next->write.secret, ¤t->write.secret); ngx_quic_hkdf_set(&seq[4], "tls13 quic key", - &server_key, &next->server.secret); + &write_key, &next->write.secret); ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", - &next->server.iv, &next->server.secret); + &next->write.iv, &next->write.secret); for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { @@ -844,28 +844,28 @@ 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->read, &read_key, 0, 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->write, &write_key, 1, c->log) == NGX_ERROR) { goto failed; } - ngx_explicit_memzero(current->client.secret.data, - current->client.secret.len); - ngx_explicit_memzero(current->server.secret.data, - current->server.secret.len); + ngx_explicit_memzero(current->read.secret.data, + current->read.secret.len); + ngx_explicit_memzero(current->write.secret.data, + current->write.secret.len); - current->client.secret.len = 0; - current->server.secret.len = 0; + current->read.secret.len = 0; + current->write.secret.len = 0; - ngx_explicit_memzero(client_key.data, client_key.len); - ngx_explicit_memzero(server_key.data, server_key.len); + ngx_explicit_memzero(read_key.data, read_key.len); + ngx_explicit_memzero(write_key.data, write_key.len); return; @@ -887,19 +887,19 @@ ngx_quic_keys_cleanup(ngx_quic_keys_t *keys) next = &keys->next_key; - ngx_quic_crypto_cleanup(&next->client); - ngx_quic_crypto_cleanup(&next->server); + ngx_quic_crypto_cleanup(&next->read); + ngx_quic_crypto_cleanup(&next->write); - if (next->client.secret.len) { - ngx_explicit_memzero(next->client.secret.data, - next->client.secret.len); - next->client.secret.len = 0; + if (next->read.secret.len) { + ngx_explicit_memzero(next->read.secret.data, + next->read.secret.len); + next->read.secret.len = 0; } - if (next->server.secret.len) { - ngx_explicit_memzero(next->server.secret.data, - next->server.secret.len); - next->server.secret.len = 0; + if (next->write.secret.len) { + ngx_explicit_memzero(next->write.secret.data, + next->write.secret.len); + next->write.secret.len = 0; } } @@ -924,7 +924,7 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) "quic ad len:%uz %xV", ad.len, &ad); #endif - secret = &pkt->keys->secrets[pkt->level].server; + secret = &pkt->keys->secrets[pkt->level].write; ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); @@ -1137,7 +1137,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ngx_quic_secret_t *secret; uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; - secret = &pkt->keys->secrets[pkt->level].client; + secret = &pkt->keys->secrets[pkt->level].read; p = pkt->raw->pos; len = pkt->data + pkt->len - p; @@ -1169,8 +1169,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; if (key_phase != pkt->key_phase) { - if (pkt->keys->next_key.client.ctx != NULL) { - secret = &pkt->keys->next_key.client; + if (pkt->keys->next_key.read.ctx != NULL) { + secret = &pkt->keys->next_key.read; pkt->key_update = 1; } else { diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 7c5cf3153..bfd5d1446 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -55,8 +55,8 @@ typedef struct { typedef struct { - ngx_quic_secret_t client; - ngx_quic_secret_t server; + ngx_quic_secret_t read; + ngx_quic_secret_t write; } ngx_quic_secrets_t; From 355b39d7c78e049276454ebc8a043c80fb144e56 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 Feb 2025 17:45:28 +0400 Subject: [PATCH 03/13] QUIC: renamed client/server streams to local/remote. This is a preparation for introducing QUIC client support. --- src/event/quic/ngx_event_quic.c | 8 +-- src/event/quic/ngx_event_quic_ack.c | 4 +- src/event/quic/ngx_event_quic_connection.h | 18 +++--- src/event/quic/ngx_event_quic_streams.c | 68 +++++++++++----------- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 6a59aaf93..e742d13bc 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -187,8 +187,8 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) qc->tp.max_idle_timeout = ctp->max_idle_timeout; } - qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; - qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; + qc->streams.local_max_streams_bidi = ctp->initial_max_streams_bidi; + qc->streams.local_max_streams_uni = ctp->initial_max_streams_uni; ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); @@ -308,8 +308,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->streams.recv_max_data = qc->tp.initial_max_data; qc->streams.recv_window = qc->streams.recv_max_data; - qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; - qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; + qc->streams.remote_max_streams_uni = qc->tp.initial_max_streams_uni; + qc->streams.remote_max_streams_bidi = qc->tp.initial_max_streams_bidi; qc->congestion.window = ngx_min(10 * NGX_QUIC_MIN_INITIAL_SIZE, ngx_max(2 * NGX_QUIC_MIN_INITIAL_SIZE, diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index abd3f7ade..cdf485dcb 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -810,8 +810,8 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) case NGX_QUIC_FT_MAX_STREAMS: case NGX_QUIC_FT_MAX_STREAMS2: f->u.max_streams.limit = f->u.max_streams.bidi - ? qc->streams.client_max_streams_bidi - : qc->streams.client_max_streams_uni; + ? qc->streams.remote_max_streams_bidi + : qc->streams.remote_max_streams_uni; ngx_quic_queue_frame(qc, f); break; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 33922cf80..b295b4534 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -156,15 +156,15 @@ typedef struct { uint64_t send_offset; uint64_t send_max_data; - uint64_t server_max_streams_uni; - uint64_t server_max_streams_bidi; - uint64_t server_streams_uni; - uint64_t server_streams_bidi; - - uint64_t client_max_streams_uni; - uint64_t client_max_streams_bidi; - uint64_t client_streams_uni; - uint64_t client_streams_bidi; + uint64_t local_max_streams_uni; + uint64_t local_max_streams_bidi; + uint64_t local_streams_uni; + uint64_t local_streams_bidi; + + uint64_t remote_max_streams_uni; + uint64_t remote_max_streams_bidi; + uint64_t remote_streams_uni; + uint64_t remote_streams_bidi; ngx_uint_t initialized; /* unsigned initialized:1; */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 18fffeabe..785c15564 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -58,47 +58,47 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) } if (bidi) { - if (qc->streams.server_streams_bidi - >= qc->streams.server_max_streams_bidi) + if (qc->streams.local_streams_bidi + >= qc->streams.local_max_streams_bidi) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server bidi streams:%uL", - qc->streams.server_streams_bidi); + "quic too many local bidi streams:%uL", + qc->streams.local_streams_bidi); return NULL; } - id = (qc->streams.server_streams_bidi << 2) + id = (qc->streams.local_streams_bidi << 2) | NGX_QUIC_STREAM_SERVER_INITIATED; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream" + "quic creating local bidi stream" " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_bidi, - qc->streams.server_max_streams_bidi, id); + qc->streams.local_streams_bidi, + qc->streams.local_max_streams_bidi, id); - qc->streams.server_streams_bidi++; + qc->streams.local_streams_bidi++; } else { - if (qc->streams.server_streams_uni - >= qc->streams.server_max_streams_uni) + if (qc->streams.local_streams_uni + >= qc->streams.local_max_streams_uni) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server uni streams:%uL", - qc->streams.server_streams_uni); + "quic too many local uni streams:%uL", + qc->streams.local_streams_uni); return NULL; } - id = (qc->streams.server_streams_uni << 2) + id = (qc->streams.local_streams_uni << 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" + "quic creating local uni stream" " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_uni, - qc->streams.server_max_streams_uni, id); + qc->streams.local_streams_uni, + qc->streams.local_max_streams_uni, id); - qc->streams.server_streams_uni++; + qc->streams.local_streams_uni++; } qs = ngx_quic_create_stream(pc, id); @@ -404,7 +404,7 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_uni) { + if ((id >> 2) < qc->streams.local_streams_uni) { return NGX_QUIC_STREAM_GONE; } @@ -412,23 +412,23 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) return NULL; } - if ((id >> 2) < qc->streams.client_streams_uni) { + if ((id >> 2) < qc->streams.remote_streams_uni) { return NGX_QUIC_STREAM_GONE; } - if ((id >> 2) >= qc->streams.client_max_streams_uni) { + if ((id >> 2) >= qc->streams.remote_max_streams_uni) { qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; return NULL; } - min_id = (qc->streams.client_streams_uni << 2) + min_id = (qc->streams.remote_streams_uni << 2) | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client_streams_uni = (id >> 2) + 1; + qc->streams.remote_streams_uni = (id >> 2) + 1; } else { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_bidi) { + if ((id >> 2) < qc->streams.local_streams_bidi) { return NGX_QUIC_STREAM_GONE; } @@ -436,17 +436,17 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) return NULL; } - if ((id >> 2) < qc->streams.client_streams_bidi) { + if ((id >> 2) < qc->streams.remote_streams_bidi) { return NGX_QUIC_STREAM_GONE; } - if ((id >> 2) >= qc->streams.client_max_streams_bidi) { + if ((id >> 2) >= qc->streams.remote_max_streams_bidi) { qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; return NULL; } - min_id = (qc->streams.client_streams_bidi << 2); - qc->streams.client_streams_bidi = (id >> 2) + 1; + min_id = (qc->streams.remote_streams_bidi << 2); + qc->streams.remote_streams_bidi = (id >> 2) + 1; } /* @@ -1184,11 +1184,11 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) frame->type = NGX_QUIC_FT_MAX_STREAMS; if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; + frame->u.max_streams.limit = ++qc->streams.remote_max_streams_uni; frame->u.max_streams.bidi = 0; } else { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; + frame->u.max_streams.limit = ++qc->streams.remote_max_streams_bidi; frame->u.max_streams.bidi = 1; } @@ -1567,16 +1567,16 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); if (f->bidi) { - if (qc->streams.server_max_streams_bidi < f->limit) { - qc->streams.server_max_streams_bidi = f->limit; + if (qc->streams.local_max_streams_bidi < f->limit) { + qc->streams.local_max_streams_bidi = f->limit; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic max_streams_bidi:%uL", f->limit); } } else { - if (qc->streams.server_max_streams_uni < f->limit) { - qc->streams.server_max_streams_uni = f->limit; + if (qc->streams.local_max_streams_uni < f->limit) { + qc->streams.local_max_streams_uni = f->limit; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic max_streams_uni:%uL", f->limit); From b4210a2de64783a0a7d254b1812771a0ea1bc8fb Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 14 Feb 2025 15:17:48 +0400 Subject: [PATCH 04/13] QUIC: stream recv_chain support. The function is called by event pipe which is used when proxying an HTTP connection. --- src/event/quic/ngx_event_quic_streams.c | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 785c15564..904c82688 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -29,6 +29,8 @@ static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size); +static ssize_t ngx_quic_stream_recv_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); @@ -761,6 +763,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->recv = ngx_quic_stream_recv; sc->send = ngx_quic_stream_send; + sc->recv_chain = ngx_quic_stream_recv_chain; sc->send_chain = ngx_quic_stream_send_chain; sc->read->log = log; @@ -948,6 +951,48 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) } +static ssize_t +ngx_quic_stream_recv_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + size_t len, size; + ssize_t n; + + /* TODO optimize */ + + len = 0; + + while (in && limit) { + size = in->buf->last - in->buf->pos; + if ((off_t) size > limit) { + size = limit; + } + + n = ngx_quic_stream_recv(c, in->buf->pos, size); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + break; + } + + in->buf->pos += n; + limit -= n; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + } + + if (len == 0 && in) { + return NGX_AGAIN; + } + + return len; +} + + static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { From de41f573b0087f5cff930ee81910dad9755e7633 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 May 2025 17:14:05 +0400 Subject: [PATCH 05/13] QUIC: detailed logging of transmitted packets. --- src/event/quic/ngx_event_quic_output.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 8c3350504..6564f9211 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -710,7 +710,24 @@ ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt->keys = qc->keys; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx %s", ngx_quic_level_name(pkt->level)); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx dcid len:%uz %*xs", + pkt->dcid.len, pkt->dcid.len, pkt->dcid.data); + + if (pkt->level != NGX_QUIC_ENCRYPTION_APPLICATION) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx scid len:%uz %*xs", + pkt->scid.len, pkt->scid.len, pkt->scid.data); + } + ngx_quic_set_packet_number(pkt, ctx); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx number:%uL len:%d", + pkt->number, pkt->num_len); } From d3c19e26b3065e88d495234e181e6e79def14c2f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 14 Feb 2025 22:12:59 +0400 Subject: [PATCH 06/13] QUIC: ngx_quic_is_stream_local() function. This is a preparation for adding QUIC client support. --- src/event/quic/ngx_event_quic_streams.c | 37 ++++++++++++------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 904c82688..2c1ba6fff 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -13,6 +13,7 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +static ngx_uint_t ngx_quic_is_stream_local(ngx_connection_t *c, uint64_t id); static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err); static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); @@ -44,6 +45,13 @@ static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); static void ngx_quic_set_event(ngx_event_t *ev); +static ngx_uint_t +ngx_quic_is_stream_local(ngx_connection_t *c, uint64_t id) +{ + return (id & NGX_QUIC_STREAM_SERVER_INITIATED) ? 1 : 0; +} + + ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { @@ -405,7 +413,7 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if (ngx_quic_is_stream_local(c, id)) { if ((id >> 2) < qc->streams.local_streams_uni) { return NGX_QUIC_STREAM_GONE; } @@ -429,7 +437,7 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) } else { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if (ngx_quic_is_stream_local(c, id)) { if ((id >> 2) < qc->streams.local_streams_bidi) { return NGX_QUIC_STREAM_GONE; } @@ -775,7 +783,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) log->connection = sc->number; if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if (ngx_quic_is_stream_local(c, id)) { qs->send_max_data = qc->ctp.initial_max_stream_data_uni; qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; qs->send_state = NGX_QUIC_STREAM_SEND_READY; @@ -787,7 +795,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) } } else { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if (ngx_quic_is_stream_local(c, id)) { qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; @@ -1210,16 +1218,7 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) return NGX_OK; } - if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { - ngx_reusable_connection(pc, 1); - } - - if (qc->shutdown) { - ngx_quic_shutdown_quic(pc); - return NGX_OK; - } - - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + if (!ngx_quic_is_stream_local(pc, qs->id)) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_ERROR; @@ -1286,7 +1285,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, f = &frame->u.stream; if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + && ngx_quic_is_stream_local(c, f->stream_id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; @@ -1431,7 +1430,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + && ngx_quic_is_stream_local(c, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; @@ -1461,7 +1460,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + && !ngx_quic_is_stream_local(c, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; @@ -1504,7 +1503,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + && ngx_quic_is_stream_local(c, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; @@ -1573,7 +1572,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + && !ngx_quic_is_stream_local(c, f->id)) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; From dc416c3014b470ce9020e7fae497bb45af05f498 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 19 Apr 2025 22:28:12 +0400 Subject: [PATCH 07/13] QUIC: application-centered API. The change removes ngx_quic_run() and adds the following functions: - ngx_quic_handshake() - ngx_quic_shutdown() - ngx_quic_get_error() - ngx_quic_accept_stream() - ngx_quic_reject_streams() The first two resemble similar SSL functions. A typical QUIC workflow is now application-centered. After initiating the handshake, the application sets c->ssl->handshake which is first called once the handshake is complete. After that it's called after processing each QUIC event. Inside the handler the application can accept new streams, check QUIC errors and shut down the connection using the above mentioned functions. The change also introduces QUIC connection lingering. When closing a QUIC connection with ngx_quic_shutdown(), first the connections switches to the lingering mode. In this mode all unsent stream data is sent and acked. The maximum timeout for lingering is 3 seconds. The same valus is currently used by ngx_ssl_shutdown(). --- src/event/quic/ngx_event_quic.c | 228 ++++++++++++--------- src/event/quic/ngx_event_quic.h | 16 +- src/event/quic/ngx_event_quic_ack.c | 22 +- src/event/quic/ngx_event_quic_connection.h | 12 +- src/event/quic/ngx_event_quic_migration.c | 16 +- src/event/quic/ngx_event_quic_output.c | 4 +- src/event/quic/ngx_event_quic_protection.c | 18 +- src/event/quic/ngx_event_quic_ssl.c | 27 ++- src/event/quic/ngx_event_quic_streams.c | 226 ++++++-------------- src/event/quic/ngx_event_quic_streams.h | 5 +- src/event/quic/ngx_event_quic_transport.h | 3 + src/http/v3/ngx_http_v3.c | 23 --- src/http/v3/ngx_http_v3.h | 12 +- src/http/v3/ngx_http_v3_module.c | 9 +- src/http/v3/ngx_http_v3_request.c | 190 +++++++++++++---- src/http/v3/ngx_http_v3_uni.c | 4 - 16 files changed, 408 insertions(+), 407 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index e742d13bc..775d98e74 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -82,6 +82,7 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) } p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); + p = ngx_slprintf(p, last, "%s", qc->lingering ? " lingering" : ""); p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); @@ -196,34 +197,34 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) } -void -ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) +ngx_int_t +ngx_quic_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf) { ngx_int_t rc; ngx_quic_connection_t *qc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); rc = ngx_quic_handle_datagram(c, c->buffer, conf); + if (rc != NGX_OK) { - ngx_quic_close_connection(c, rc); - return; + if (c->ssl) { + c->ssl->no_wait_shutdown = 1; + } + + return NGX_ERROR; } /* quic connection is now created */ qc = ngx_quic_get_connection(c); - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - - if (!qc->streams.initialized) { - ngx_add_timer(&qc->close, qc->conf->handshake_timeout); - } + ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); ngx_quic_connstate_dbg(c); c->read->handler = ngx_quic_input_handler; - return; + return NGX_AGAIN; } @@ -344,7 +345,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } c->idle = 1; - ngx_reusable_connection(c, 1); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic connection created"); @@ -415,74 +415,88 @@ ngx_quic_input_handler(ngx_event_t *rev) c->log->action = "handling quic input"; - if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, - "quic client timed out"); - ngx_quic_close_connection(c, NGX_DONE); - return; - } - - if (c->close) { - c->close = 0; - - if (!ngx_exiting || !qc->streams.initialized) { - 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 (c->close || rev->timedout) { + goto done; } b = c->udp->buffer; if (b == NULL) { - return; + goto done; } rc = ngx_quic_handle_datagram(c, b, NULL); if (rc == NGX_ERROR) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "datagram handling error"); + goto done; } if (rc == NGX_DONE) { - return; + goto done; } /* rc == NGX_OK */ qc->send_timer_set = 0; - ngx_add_timer(rev, qc->tp.max_idle_timeout); + ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); - ngx_quic_connstate_dbg(c); +done: + + ngx_quic_end_handler(c); } void -ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) +ngx_quic_end_handler(ngx_connection_t *c) { - ngx_uint_t i; - ngx_pool_t *pool; + ngx_quic_connection_t *qc; + + ngx_quic_connstate_dbg(c); + + if (c->ssl == NULL || c->ssl->handler == NULL) { + return; + } + + qc = ngx_quic_get_connection(c); + + if (!c->ssl->handshaked && qc->error == 0) { + return; + } + + c->ssl->handler(c); +} + + +ngx_int_t +ngx_quic_shutdown(ngx_connection_t *c) +{ + ngx_uint_t i, no_wait; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); if (qc == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet rejected rc:%i, cleanup connection", rc); goto quic_done; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close %s rc:%i", - qc->closing ? "resumed": "initiated", rc); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close %s", + qc->lingering || qc->closing ? "resumed": "initiated"); + + no_wait = (c->ssl == NULL || c->ssl->no_wait_shutdown) ? 1 : 0; + + if (!no_wait && !qc->closing) { + if (ngx_quic_linger_streams(c) == NGX_AGAIN) { + + if (!qc->lingering) { + ngx_add_timer(&qc->close, 3000); + qc->lingering = 1; + } + + return NGX_AGAIN; + } + } if (!qc->closing) { @@ -496,7 +510,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_del_timer(&qc->close); } - if (rc == NGX_DONE) { + if (qc->error == NGX_QUIC_ERR_CLOSE) { /* * RFC 9000, 10.1. Idle Timeout @@ -506,7 +520,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) * closed and its state is discarded when it remains idle */ - /* this case also handles some errors from ngx_quic_run() */ + /* this case also handles some errors from ngx_quic_handshake() */ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close silent drain:%d timedout:%d", @@ -520,15 +534,15 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) * to terminate the connection immediately. */ - if (qc->error == 0 && rc == NGX_ERROR) { - qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; - qc->error_app = 0; + if (qc->error == 0) { + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "internal error"); } ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close immediate term:%d drain:%d " + "quic close immediate term:%ui drain:%d " "%serror:%ui \"%s\"", - rc == NGX_ERROR ? 1 : 0, qc->draining, + no_wait, qc->draining, qc->error_app ? "app " : "", qc->error, qc->error_reason ? qc->error_reason : ""); @@ -542,22 +556,22 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) qc->error_level = ctx->level; (void) ngx_quic_send_cc(c); - if (rc == NGX_OK) { + if (!no_wait) { ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); } } } + qc->lingering = 0; qc->closing = 1; } - if (rc == NGX_ERROR && qc->close.timer_set) { - /* do not wait for timer in case of fatal error */ + if (no_wait && qc->close.timer_set) { ngx_del_timer(&qc->close); } - if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { - return; + if (ngx_quic_close_streams(c) == NGX_AGAIN) { + return NGX_AGAIN; } if (qc->push.timer_set) { @@ -581,7 +595,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) } if (qc->close.timer_set) { - return; + return NGX_AGAIN; } if (qc->close.posted) { @@ -600,6 +614,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) quic_done: if (c->ssl) { + c->ssl->no_wait_shutdown = 1; (void) ngx_ssl_shutdown(c); } @@ -607,66 +622,93 @@ quic_done: ngx_del_timer(c->read); } -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, -1); -#endif + return NGX_OK; +} - c->destroyed = 1; - pool = c->pool; +void +ngx_quic_set_error(ngx_connection_t *c, ngx_uint_t err, const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->error) { + return; + } - ngx_close_connection(c); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic error %ui \"%s\"", + err, reason); - ngx_destroy_pool(pool); + qc->error = err; + qc->error_reason = reason; + qc->error_ftype = 0; } void -ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, - const char *reason) +ngx_quic_set_app_error(ngx_connection_t *c, ngx_uint_t err, const char *reason) { ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - if (qc->closing) { + if (qc->error) { return; } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic app error %ui \"%s\"", + err, reason); + qc->error = err; qc->error_reason = reason; qc->error_app = 1; qc->error_ftype = 0; +} + + +ngx_uint_t +ngx_quic_get_error(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; - ngx_post_event(&qc->close, &ngx_posted_events); + qc = ngx_quic_get_connection(c); + + return qc->error; } void -ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, - const char *reason) +ngx_quic_reject_streams(ngx_connection_t *c) { ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); qc->shutdown = 1; - qc->shutdown_code = err; - qc->shutdown_reason = reason; - - ngx_quic_shutdown_quic(c); } static void ngx_quic_close_handler(ngx_event_t *ev) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_connection_t *qc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); c = ev->data; - ngx_quic_close_connection(c, NGX_OK); + if (ev->timedout) { + ev->timedout = 0; + ngx_quic_set_error(c, NGX_QUIC_ERR_CLOSE, ""); + + qc = ngx_quic_get_connection(c); + if (qc->lingering) { + c->ssl->no_wait_shutdown = 1; + } + } + + ngx_quic_end_handler(c); } @@ -765,7 +807,7 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); - qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; qc->error_reason = "QUIC flood detected"; return NGX_ERROR; } @@ -861,7 +903,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, "quic stateless reset packet detected"); qc->draining = 1; - ngx_post_event(&qc->close, &ngx_posted_events); + qc->error = NGX_QUIC_ERR_CLOSE; return NGX_OK; } @@ -963,9 +1005,6 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); - qc->error = 0; - qc->error_reason = NULL; - c->log->action = "decrypting packet"; if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { @@ -1417,7 +1456,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) if (do_close) { qc->draining = 1; - ngx_post_event(&qc->close, &ngx_posted_events); + qc->error = NGX_QUIC_ERR_CLOSE; } if (pkt->path != qc->path && nonprobing) { @@ -1452,21 +1491,8 @@ ngx_quic_push_handler(ngx_event_t *ev) c = ev->data; if (ngx_quic_output(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "output error"); } - ngx_quic_connstate_dbg(c); -} - - -void -ngx_quic_shutdown_quic(ngx_connection_t *c) -{ - ngx_quic_connection_t *qc; - - if (c->reusable) { - qc = ngx_quic_get_connection(c); - ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); - } + ngx_quic_end_handler(c); } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 4f899ec0a..deafce046 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -83,7 +83,6 @@ typedef struct { ngx_flag_t retry; ngx_flag_t gso_enabled; ngx_flag_t disable_active_migration; - ngx_msec_t handshake_timeout; ngx_msec_t idle_timeout; ngx_str_t host_key; size_t stream_buffer_size; @@ -94,9 +93,6 @@ typedef struct { ngx_int_t stream_reject_code_uni; ngx_int_t stream_reject_code_bidi; - ngx_quic_init_pt init; - ngx_quic_shutdown_pt shutdown; - u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t; @@ -122,21 +118,21 @@ struct ngx_quic_stream_s { ngx_quic_buffer_t recv; ngx_quic_stream_send_state_e send_state; ngx_quic_stream_recv_state_e recv_state; - unsigned cancelable:1; unsigned fin_acked:1; }; 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_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf); +ngx_connection_t *ngx_quic_accept_stream(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); -void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, +void ngx_quic_set_app_error(ngx_connection_t *c, ngx_uint_t err, const char *reason); +ngx_uint_t ngx_quic_get_error(ngx_connection_t *c); +ngx_int_t ngx_quic_shutdown(ngx_connection_t *c); +void ngx_quic_reject_streams(ngx_connection_t *c); ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how); -void ngx_quic_cancelable_stream(ngx_connection_t *c); ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, ngx_str_t *dcid); ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index cdf485dcb..80639951b 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -1091,11 +1091,10 @@ void ngx_quic_lost_handler(ngx_event_t *ev) c = ev->data; if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "lost detect error"); } - ngx_quic_connstate_dbg(c); + ngx_quic_end_handler(c); } @@ -1148,7 +1147,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) f = ngx_quic_alloc_frame(c); if (f == NULL) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "memory error"); + goto done; } f->level = ctx->level; @@ -1156,7 +1157,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) f->ignore_congestion = 1; if (ngx_quic_frame_sendto(c, f, 0, qc->path) == NGX_ERROR) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "send error"); + goto done; } } } @@ -1165,14 +1168,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_quic_set_lost_timer(c); - ngx_quic_connstate_dbg(c); - - return; - -failed: +done: - ngx_quic_close_connection(c, NGX_ERROR); - return; + ngx_quic_end_handler(c); } diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index b295b4534..4012f0464 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -165,9 +165,6 @@ typedef struct { uint64_t remote_max_streams_bidi; uint64_t remote_streams_uni; uint64_t remote_streams_bidi; - - ngx_uint_t initialized; - /* unsigned initialized:1; */ } ngx_quic_streams_t; @@ -290,11 +287,9 @@ struct ngx_quic_connection_s { ngx_uint_t error_ftype; const char *error_reason; - ngx_uint_t shutdown_code; - const char *shutdown_reason; - unsigned error_app:1; unsigned send_timer_set:1; + unsigned lingering:1; unsigned closing:1; unsigned shutdown:1; unsigned draining:1; @@ -312,8 +307,9 @@ struct ngx_quic_connection_s { ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp); void ngx_quic_discard_ctx(ngx_connection_t *c, ngx_uint_t level); -void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); -void ngx_quic_shutdown_quic(ngx_connection_t *c); +void ngx_quic_end_handler(ngx_connection_t *c); +void ngx_quic_set_error(ngx_connection_t *c, ngx_uint_t err, + const char *reason); #if (NGX_DEBUG) void ngx_quic_connstate_dbg(ngx_connection_t *c); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 42354ca66..bb7e87f75 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -724,21 +724,25 @@ ngx_quic_path_handler(ngx_event_t *ev) switch (path->state) { case NGX_QUIC_PATH_VALIDATING: if (ngx_quic_expire_path_validation(c, path) != NGX_OK) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_NO_VIABLE_PATH, + "path error"); + goto done; } break; case NGX_QUIC_PATH_WAITING: if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "mtu error"); + goto done; } break; case NGX_QUIC_PATH_MTUD: if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "mtu error"); + goto done; } break; @@ -750,11 +754,9 @@ ngx_quic_path_handler(ngx_event_t *ev) ngx_quic_set_path_timer(c); - return; +done: -failed: - - ngx_quic_close_connection(c, NGX_ERROR); + ngx_quic_end_handler(c); } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 6564f9211..22a554ad2 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -101,7 +101,7 @@ ngx_quic_output(ngx_connection_t *c) if (!qc->send_timer_set) { qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); } ngx_quic_set_lost_timer(c); @@ -1339,7 +1339,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, if (!qc->send_timer_set) { qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); } ngx_quic_set_lost_timer(c); diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index abd4a9518..7fb6c3f7e 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -809,7 +809,8 @@ ngx_quic_keys_update(ngx_event_t *ev) key_len = ngx_quic_ciphers(keys->cipher, &ciphers); if (key_len == NGX_ERROR) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_KEY_UPDATE_ERROR, "key error"); + goto done; } read_key.len = key_len; @@ -840,20 +841,23 @@ ngx_quic_keys_update(ngx_event_t *ev) for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_KEY_UPDATE_ERROR, "key error"); + goto done; } } if (ngx_quic_crypto_init(ciphers.c, &next->read, &read_key, 0, c->log) == NGX_ERROR) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_KEY_UPDATE_ERROR, "key error"); + goto done; } if (ngx_quic_crypto_init(ciphers.c, &next->write, &write_key, 1, c->log) == NGX_ERROR) { - goto failed; + ngx_quic_set_error(c, NGX_QUIC_ERR_KEY_UPDATE_ERROR, "key error"); + goto done; } ngx_explicit_memzero(current->read.secret.data, @@ -867,11 +871,9 @@ ngx_quic_keys_update(ngx_event_t *ev) ngx_explicit_memzero(read_key.data, read_key.len); ngx_explicit_memzero(write_key.data, write_key.len); - return; - -failed: +done: - ngx_quic_close_connection(c, NGX_ERROR); + ngx_quic_end_handler(c); } diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 18992ae1b..c58571a36 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -59,7 +59,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, #endif -static ngx_int_t ngx_quic_handshake(ngx_connection_t *c); +static ngx_int_t ngx_quic_do_handshake(ngx_connection_t *c); static ngx_int_t ngx_quic_crypto_provide(ngx_connection_t *c, ngx_uint_t level); @@ -681,14 +681,15 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - return ngx_quic_handshake(c); + return ngx_quic_do_handshake(c); } static ngx_int_t -ngx_quic_handshake(ngx_connection_t *c) +ngx_quic_do_handshake(ngx_connection_t *c) { int n, sslerr; + ngx_int_t rc; ngx_ssl_conn_t *ssl_conn; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -734,9 +735,7 @@ ngx_quic_handshake(ngx_connection_t *c) if (ngx_quic_keys_available(qc->keys, NGX_QUIC_ENCRYPTION_EARLY_DATA, 0) && qc->client_tp_done) { - if (ngx_quic_init_streams(c) != NGX_OK) { - return NGX_ERROR; - } + goto done; } return NGX_OK; @@ -746,8 +745,6 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_ssl_handshake_log(c); #endif - c->ssl->handshaked = 1; - frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; @@ -786,10 +783,20 @@ ngx_quic_handshake(ngx_connection_t *c) return NGX_ERROR; } - if (ngx_quic_init_streams(c) != NGX_OK) { +done: + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { return NGX_ERROR; } + if (rc == NGX_AGAIN) { + return NGX_OK; + } + + c->ssl->handshaked = 1; + return NGX_OK; } @@ -892,8 +899,6 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } - c->ssl->no_wait_shutdown = 1; - ssl_conn = c->ssl->connection; #if (NGX_QUIC_OPENSSL_API) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 2c1ba6fff..085b207e2 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -20,9 +20,6 @@ static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); -static void ngx_quic_init_stream_handler(ngx_event_t *ev); -static void ngx_quic_init_streams_handler(ngx_connection_t *c); -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_empty_handler(ngx_event_t *ev); @@ -37,7 +34,6 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, 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); -static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c); static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last); static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last); static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs); @@ -179,16 +175,51 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) return NULL; } +ngx_int_t +ngx_quic_linger_streams(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 NGX_OK; + } + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + if (qs->sent != qs->acked) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL lingering unacked:%uL", + qs->id, qs->sent - qs->acked); + return NGX_AGAIN; + } + } + + return NGX_OK; +} + ngx_int_t -ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +ngx_quic_close_streams(ngx_connection_t *c) { - ngx_pool_t *pool; - ngx_queue_t *q, posted_events; - ngx_rbtree_t *tree; - ngx_connection_t *sc; - ngx_rbtree_node_t *node; - ngx_quic_stream_t *qs; + ngx_pool_t *pool; + ngx_queue_t *q, posted_events; + 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); while (!ngx_queue_empty(&qc->streams.uninitialized)) { q = ngx_queue_head(&qc->streams.uninitialized); @@ -392,7 +423,6 @@ 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; @@ -484,24 +514,6 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) } ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); - - rev = qs->connection->read; - rev->handler = ngx_quic_init_stream_handler; - - if (qc->streams.initialized) { - ngx_post_event(rev, &ngx_posted_events); - - if (qc->push.posted) { - /* - * The posted stream can produce output immediately. - * By postponing the push event, we coalesce the stream - * output with queued frames in one UDP datagram. - */ - - ngx_delete_posted_event(&qc->push); - ngx_post_event(&qc->push, &ngx_posted_events); - } - } } if (qs == NULL) { @@ -561,98 +573,39 @@ ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) } -static void -ngx_quic_init_stream_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - ngx_quic_stream_t *qs; - - c = ev->data; - qs = c->quic; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); - - if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - c->write->active = 1; - c->write->ready = 1; - } - - c->read->active = 1; - - ngx_queue_remove(&qs->queue); - - c->listening->handler(c); -} - - -ngx_int_t -ngx_quic_init_streams(ngx_connection_t *c) +ngx_connection_t * +ngx_quic_accept_stream(ngx_connection_t *c) { - ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *sc; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - if (qc->streams.initialized) { - return NGX_OK; - } - - rc = ngx_ssl_ocsp_validate(c); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_AGAIN) { - c->ssl->handler = ngx_quic_init_streams_handler; - return NGX_OK; - } - - return ngx_quic_do_init_streams(c); -} - - -static void -ngx_quic_init_streams_handler(ngx_connection_t *c) -{ - if (ngx_quic_do_init_streams(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); + if (ngx_queue_empty(&qc->streams.uninitialized)) { + return NULL; } -} + q = ngx_queue_head(&qc->streams.uninitialized); + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); -static ngx_int_t -ngx_quic_do_init_streams(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); + ngx_queue_remove(&qs->queue); - qc = ngx_quic_get_connection(c); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic accept stream id:%uL", qs->id); - if (qc->conf->init) { - if (qc->conf->init(c) != NGX_OK) { - return NGX_ERROR; - } - } + sc = qs->connection; - for (q = ngx_queue_head(&qc->streams.uninitialized); - q != ngx_queue_sentinel(&qc->streams.uninitialized); - q = ngx_queue_next(q)) - { - qs = ngx_queue_data(q, ngx_quic_stream_t, queue); - ngx_post_event(qs->connection->read, &ngx_posted_events); + if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + sc->write->active = 1; + sc->write->ready = 1; } - qc->streams.initialized = 1; - - if (!qc->closing && qc->close.timer_set) { - ngx_del_timer(&qc->close); - } + sc->read->active = 1; + sc->read->ready = 1; - return NGX_OK; + return sc; } @@ -752,6 +705,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) return NULL; } + ngx_reusable_connection(c, reusable); + qs->connection = sc; sc->quic = qs; @@ -815,7 +770,6 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) ngx_close_connection(sc); ngx_destroy_pool(pool); ngx_queue_insert_tail(&qc->streams.free, &qs->queue); - ngx_reusable_connection(c, reusable); return NULL; } @@ -828,31 +782,6 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) } -void -ngx_quic_cancelable_stream(ngx_connection_t *c) -{ - ngx_connection_t *pc; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - - if (!qs->cancelable) { - qs->cancelable = 1; - - if (ngx_quic_can_shutdown(pc) == NGX_OK) { - ngx_reusable_connection(pc, 1); - - if (qc->shutdown) { - ngx_quic_shutdown_quic(pc); - } - } - } -} - - static void ngx_quic_empty_handler(ngx_event_t *ev) { @@ -1243,35 +1172,6 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) } -static ngx_int_t -ngx_quic_can_shutdown(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) { - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - - if (!qs->cancelable) { - return NGX_DECLINED; - } - } - } - - return NGX_OK; -} - - ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h index fb6dbbd8f..4b837db10 100644 --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -33,12 +33,11 @@ ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, 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_int_t ngx_quic_init_streams(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, uint64_t id); -ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, - ngx_quic_connection_t *qc); +ngx_int_t ngx_quic_linger_streams(ngx_connection_t *c); +ngx_int_t ngx_quic_close_streams(ngx_connection_t *c); #endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 656cb09fb..12a39c698 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -112,6 +112,9 @@ #define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) +/* The special code to close connection without any response */ +#define NGX_QUIC_ERR_CLOSE 0x10000000 + /* 22.3. QUIC Transport Parameters Registry */ #define NGX_QUIC_TP_ORIGINAL_DCID 0x00 diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index d8597ec5c..c99c6cdfe 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -10,7 +10,6 @@ #include -static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); static void ngx_http_v3_cleanup_session(void *data); @@ -34,10 +33,6 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_queue_init(&h3c->blocked); - h3c->keepalive.log = c->log; - h3c->keepalive.data = c; - h3c->keepalive.handler = ngx_http_v3_keepalive_handler; - h3c->table.send_insert_count.log = c->log; h3c->table.send_insert_count.data = c; h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; @@ -61,20 +56,6 @@ failed: } -static void -ngx_http_v3_keepalive_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - c = ev->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); - - ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "keepalive timeout"); -} - - static void ngx_http_v3_cleanup_session(void *data) { @@ -82,10 +63,6 @@ ngx_http_v3_cleanup_session(void *data) ngx_http_v3_cleanup_table(h3c); - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); - } - if (h3c->table.send_insert_count.posted) { ngx_delete_posted_event(&h3c->table.send_insert_count); } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 8fd212c1f..fe9fae734 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -94,12 +94,9 @@ module) #define ngx_http_v3_finalize_connection(c, code, reason) \ - ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \ - code, reason) - -#define ngx_http_v3_shutdown_connection(c, code, reason) \ - ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ - code, reason) + ngx_quic_set_app_error((c)->quic ? (c)->quic->parent : (c), code, reason);\ + ngx_post_event((c)->quic ? (c)->quic->parent->read : (c)->read, \ + &ngx_posted_events) typedef struct { @@ -125,7 +122,6 @@ struct ngx_http_v3_session_s { ngx_http_v3_dynamic_table_t table; - ngx_event_t keepalive; ngx_uint_t nrequests; ngx_queue_t blocked; @@ -147,8 +143,6 @@ void ngx_http_v3_init_stream(ngx_connection_t *c); void ngx_http_v3_reset_stream(ngx_connection_t *c); ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); -ngx_int_t ngx_http_v3_init(ngx_connection_t *c); -void ngx_http_v3_shutdown(ngx_connection_t *c); ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 139bd65f3..ea2587435 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -210,9 +210,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; - h3scf->quic.init = ngx_http_v3_init; - h3scf->quic.shutdown = ngx_http_v3_shutdown; - return h3scf; } @@ -223,8 +220,7 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_v3_srv_conf_t *prev = parent; ngx_http_v3_srv_conf_t *conf = child; - ngx_http_ssl_srv_conf_t *sscf; - ngx_http_core_srv_conf_t *cscf; + ngx_http_ssl_srv_conf_t *sscf; ngx_conf_merge_value(conf->enable, prev->enable, 1); @@ -282,9 +278,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } - cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); - conf->quic.handshake_timeout = cscf->client_header_timeout; - sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->quic.ssl = &sscf->ssl; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6865e1466..c228f57d5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,10 @@ #include +static void ngx_http_v3_handshake_handler(ngx_connection_t *c); +static void ngx_http_v3_close_connection(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_init(ngx_connection_t *c); +static void ngx_http_v3_handler(ngx_connection_t *c); static void ngx_http_v3_init_request_stream(ngx_connection_t *c); static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_connection(void *data); @@ -58,9 +62,11 @@ static const struct { void ngx_http_v3_init_stream(ngx_connection_t *c) { + ngx_int_t rc; ngx_http_connection_t *hc, *phc; ngx_http_v3_srv_conf_t *h3scf; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; hc = c->data; @@ -72,7 +78,20 @@ ngx_http_v3_init_stream(ngx_connection_t *c) h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; - ngx_quic_run(c, &h3scf->quic); + rc = ngx_quic_handshake(c, &h3scf->quic); + + if (rc == NGX_AGAIN) { + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_core_module); + + ngx_add_timer(c->read, cscf->client_header_timeout); + + c->ssl->handler = ngx_http_v3_handshake_handler; + return; + } + + ngx_http_v3_handshake_handler(c); + return; } @@ -97,7 +116,118 @@ ngx_http_v3_init_stream(ngx_connection_t *c) } -ngx_int_t +static void +ngx_http_v3_handshake_handler(ngx_connection_t *c) +{ + if (c->ssl && c->ssl->handshaked) { + + if (ngx_http_v3_init(c) != NGX_OK) { + ngx_http_v3_close_connection(c); + return; + } + + /*TODO read ALPN */ + + ngx_reusable_connection(c, 1); + + c->ssl->handler = ngx_http_v3_handler; + + ngx_http_v3_handler(c); + + return; + } + + if (c->read->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + } + + ngx_http_v3_close_connection(c); +} + + +static void +ngx_http_v3_handler(ngx_connection_t *c) +{ + ngx_connection_t *sc; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (c->close) { + c->close = 0; + + if (!ngx_exiting) { + c->ssl->no_wait_shutdown = 1; + ngx_http_v3_close_connection(c); + return; + } + + if (!h3c->goaway) { + h3c->goaway = 1; + + if (!h3c->hq) { + (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); + } + + ngx_quic_reject_streams(c); + } + } + + if (c->read->timedout) { + c->read->timedout = 0; + + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + + ngx_quic_set_app_error(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); + ngx_http_v3_close_connection(c); + + return; + } + + while (!ngx_quic_get_error(c)) { + + sc = ngx_quic_accept_stream(c); + if (sc == NULL) { + break; + } + + ngx_http_init_connection(sc); + } + + if (ngx_quic_get_error(c)) { + ngx_http_v3_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + ngx_http_v3_session_t *h3c; + + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->ssl->handler = ngx_http_v3_close_connection; + return; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +static ngx_int_t ngx_http_v3_init(ngx_connection_t *c) { unsigned int len; @@ -114,7 +244,7 @@ ngx_http_v3_init(ngx_connection_t *c) h3c = ngx_http_v3_get_session(c); clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); - ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + ngx_add_timer(c->read, clcf->keepalive_timeout); h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); @@ -148,34 +278,6 @@ ngx_http_v3_init(ngx_connection_t *c) } -void -ngx_http_v3_shutdown(ngx_connection_t *c) -{ - ngx_http_v3_session_t *h3c; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); - - h3c = ngx_http_v3_get_session(c); - - if (h3c == NULL) { - ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "connection shutdown"); - return; - } - - if (!h3c->goaway) { - h3c->goaway = 1; - - if (!h3c->hq) { - (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); - } - - ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "connection shutdown"); - } -} - - static void ngx_http_v3_init_request_stream(ngx_connection_t *c) { @@ -228,8 +330,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) } } - ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "reached maximum number of requests"); + ngx_quic_reject_streams(c->quic->parent); } cln = ngx_pool_cleanup_add(c->pool, 0); @@ -243,8 +344,8 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) h3c->nrequests++; - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); + if (c->quic->parent->read->timer_set) { + ngx_del_timer(c->quic->parent->read); } rev = c->read; @@ -370,6 +471,7 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) c->log->action = "reading client request"; ngx_reusable_connection(c, 0); + ngx_reusable_connection(c->quic->parent, 0); r = ngx_http_create_request(c); if (r == NULL) { @@ -436,15 +538,27 @@ ngx_http_v3_cleanup_connection(void *data) { ngx_connection_t *c = data; + ngx_connection_t *pc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; h3c = ngx_http_v3_get_session(c); - if (--h3c->nrequests == 0) { - clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); - ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + if (--h3c->nrequests) { + return; } + + if (h3c->goaway) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive shutdown"); + return; + } + + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + + pc = c->quic->parent; + ngx_add_timer(pc->read, clcf->keepalive_timeout); + ngx_reusable_connection(pc, 1); } diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index 302064b8b..688b66dff 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -52,8 +52,6 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) return; } - ngx_quic_cancelable_stream(c); - us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { ngx_http_v3_finalize_connection(c, @@ -342,8 +340,6 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) goto failed; } - ngx_quic_cancelable_stream(sc); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create uni stream, type:%ui", type); From 513098064e62e5f1230ca7dcd8abdebdb5c63b2a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 28 May 2025 09:55:56 +0400 Subject: [PATCH 08/13] QUIC: simplified connection access. Now c->quic is used for both QUIC connections and streams. It allows to avoid using c->udp to access QUIC connection object. Also, it allows for early allocation of QUIC connection, which can hold QUIC configuration before the handshake. QUIC handshake function is now split into ngx_quic_create_connection() and ngx_quic_handshake() in preparation to adding QUIC client support. QUIC configuration is passed only to ngx_quic_create_connection(). This brings QUIC API in line with SSL API. --- src/core/ngx_connection.h | 2 +- src/core/ngx_core.h | 2 +- src/event/quic/ngx_event_quic.c | 92 ++++++++++++++-------- src/event/quic/ngx_event_quic.h | 13 ++- src/event/quic/ngx_event_quic_connection.h | 5 +- src/event/quic/ngx_event_quic_streams.c | 27 +++++-- src/http/ngx_http_request.c | 2 +- src/http/v3/ngx_http_v3.h | 11 ++- src/http/v3/ngx_http_v3_parse.c | 2 +- src/http/v3/ngx_http_v3_request.c | 29 ++++--- src/http/v3/ngx_http_v3_uni.c | 2 +- 11 files changed, 120 insertions(+), 67 deletions(-) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 84dd80442..04294224e 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -150,7 +150,7 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; #if (NGX_QUIC || NGX_COMPAT) - ngx_quic_stream_t *quic; + ngx_quic_t *quic; #endif #if (NGX_SSL || NGX_COMPAT) diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 02890b843..f6b944849 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -28,9 +28,9 @@ typedef struct ngx_thread_task_s ngx_thread_task_t; typedef struct ngx_ssl_s ngx_ssl_t; typedef struct ngx_ssl_cache_s ngx_ssl_cache_t; typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; -typedef struct ngx_quic_stream_s ngx_quic_stream_t; typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; typedef struct ngx_udp_connection_s ngx_udp_connection_t; +typedef struct ngx_quic_s ngx_quic_t; typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 775d98e74..322477def 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -11,16 +11,15 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_close_handler(ngx_event_t *ev); -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_datagram(ngx_connection_t *c, ngx_buf_t *b); static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, @@ -198,14 +197,42 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) ngx_int_t -ngx_quic_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf) +ngx_quic_create_connection(ngx_quic_conf_t *conf, ngx_connection_t *c, + ngx_uint_t flags) +{ + ngx_quic_t *quic; + ngx_quic_connection_t *qc; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; + } + + qc->conf = conf; + + quic = ngx_palloc(c->pool, sizeof(ngx_quic_t)); + if (quic == NULL) { + return NGX_ERROR; + } + + quic->connection = qc; + quic->stream = NULL; + + c->quic = quic; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handshake(ngx_connection_t *c) { ngx_int_t rc; ngx_quic_connection_t *qc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); - rc = ngx_quic_handle_datagram(c, c->buffer, conf); + rc = ngx_quic_handle_datagram(c, c->buffer); if (rc != NGX_OK) { if (c->ssl) { @@ -215,7 +242,7 @@ ngx_quic_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf) return NGX_ERROR; } - /* quic connection is now created */ + /* quic connection is now initialized */ qc = ngx_quic_get_connection(c); ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); @@ -229,17 +256,15 @@ ngx_quic_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf) static ngx_quic_connection_t * -ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_uint_t i; ngx_quic_tp_t *ctp; + ngx_quic_conf_t *conf; ngx_quic_connection_t *qc; - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { - return NULL; - } + qc = c->quic->connection; + conf = qc->conf; qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); if (qc->keys == NULL) { @@ -346,6 +371,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, c->idle = 1; + qc->initialized = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic connection created"); @@ -424,7 +451,7 @@ ngx_quic_input_handler(ngx_event_t *rev) goto done; } - rc = ngx_quic_handle_datagram(c, b, NULL); + rc = ngx_quic_handle_datagram(c, b); if (rc == NGX_ERROR) { ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, @@ -477,7 +504,7 @@ ngx_quic_shutdown(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - if (qc == NULL) { + if (!qc->initialized) { goto quic_done; } @@ -713,8 +740,7 @@ ngx_quic_close_handler(ngx_event_t *ev) static ngx_int_t -ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, - ngx_quic_conf_t *conf) +ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b) { size_t size; u_char *p, *start; @@ -743,7 +769,7 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, pkt.flags = p[0]; pkt.raw->pos++; - rc = ngx_quic_handle_packet(c, conf, &pkt); + rc = ngx_quic_handle_packet(c, &pkt); #if (NGX_DEBUG) if (pkt.parsed) { @@ -799,18 +825,16 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, qc = ngx_quic_get_connection(c); - if (qc) { - qc->received += size; + qc->received += size; - if ((uint64_t) (c->sent + qc->received) / 8 > - (qc->streams.sent + qc->streams.recv_last) + 1048576) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + if ((uint64_t) (c->sent + qc->received) / 8 > + (qc->streams.sent + qc->streams.recv_last) + 1048576) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); - qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; - qc->error_reason = "QUIC flood detected"; - return NGX_ERROR; - } + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + qc->error_reason = "QUIC flood detected"; + return NGX_ERROR; } return NGX_OK; @@ -818,10 +842,10 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, static ngx_int_t -ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) +ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_int_t rc; + ngx_quic_conf_t *conf; ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; @@ -857,7 +881,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc = ngx_quic_get_connection(c); - if (qc) { + if (qc->initialized) { if (rc == NGX_ABORT) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -912,7 +936,9 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return rc; } - /* packet does not belong to a connection */ + /* connection has not been initialized yet */ + + conf = c->quic->connection->conf; if (rc == NGX_ABORT) { return ngx_quic_negotiate_version(c, pkt); @@ -986,7 +1012,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, c->log->action = "creating quic connection"; - qc = ngx_quic_new_connection(c, conf, pkt); + qc = ngx_quic_new_connection(c, pkt); if (qc == NULL) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index deafce046..4451bb1ba 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -44,6 +44,7 @@ #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 +typedef struct ngx_quic_connection_s ngx_quic_connection_t; typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); @@ -98,7 +99,7 @@ typedef struct { } ngx_quic_conf_t; -struct ngx_quic_stream_s { +typedef struct { ngx_rbtree_node_t node; ngx_queue_t queue; ngx_connection_t *parent; @@ -119,11 +120,19 @@ struct ngx_quic_stream_s { ngx_quic_stream_send_state_e send_state; ngx_quic_stream_recv_state_e recv_state; unsigned fin_acked:1; +} ngx_quic_stream_t; + + +struct ngx_quic_s { + ngx_quic_connection_t *connection; + ngx_quic_stream_t *stream; }; void ngx_quic_recvmsg(ngx_event_t *ev); -ngx_int_t ngx_quic_handshake(ngx_connection_t *c, ngx_quic_conf_t *conf); +ngx_int_t ngx_quic_create_connection(ngx_quic_conf_t *conf, ngx_connection_t *c, + ngx_uint_t flags); +ngx_int_t ngx_quic_handshake(ngx_connection_t *c); ngx_connection_t *ngx_quic_accept_stream(ngx_connection_t *c); ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); void ngx_quic_set_app_error(ngx_connection_t *c, ngx_uint_t err, diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 4012f0464..e3782a8de 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -26,7 +26,6 @@ #define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) -typedef struct ngx_quic_connection_s ngx_quic_connection_t; typedef struct ngx_quic_server_id_s ngx_quic_server_id_t; typedef struct ngx_quic_client_id_s ngx_quic_client_id_t; typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; @@ -67,8 +66,7 @@ typedef struct ngx_quic_keys_s ngx_quic_keys_t; : (((level) == NGX_QUIC_ENCRYPTION_HANDSHAKE) ? &((qc)->send_ctx[1]) \ : &((qc)->send_ctx[2])) -#define ngx_quic_get_connection(c) \ - (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) +#define ngx_quic_get_connection(c) ((c)->quic->connection) #define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) @@ -287,6 +285,7 @@ struct ngx_quic_connection_s { ngx_uint_t error_ftype; const char *error_reason; + unsigned initialized:1; unsigned error_app:1; unsigned send_timer_set:1; unsigned lingering:1; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 085b207e2..ea50662b1 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -56,7 +56,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - pc = c->quic ? c->quic->parent : c; + pc = c->quic->stream ? c->quic->stream->parent : c; qc = ngx_quic_get_connection(pc); if (qc->closing) { @@ -285,7 +285,7 @@ ngx_quic_close_streams(ngx_connection_t *c) ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) { - return ngx_quic_do_reset_stream(c->quic, err); + return ngx_quic_do_reset_stream(c->quic->stream, err); } @@ -359,7 +359,7 @@ ngx_quic_shutdown_stream_send(ngx_connection_t *c) { ngx_quic_stream_t *qs; - qs = c->quic; + qs = c->quic->stream; if (qs->send_state != NGX_QUIC_STREAM_SEND_READY && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) @@ -385,7 +385,7 @@ ngx_quic_shutdown_stream_recv(ngx_connection_t *c) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; + qs = c->quic->stream; if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) @@ -616,6 +616,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) ngx_log_t *log; ngx_pool_t *pool; ngx_uint_t reusable; + ngx_quic_t *quic; ngx_queue_t *q; struct sockaddr *sockaddr; ngx_connection_t *sc; @@ -669,6 +670,16 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) *log = *c->log; pool->log = log; + quic = ngx_palloc(pool, sizeof(ngx_quic_t)); + if (quic == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + quic->connection = qc; + quic->stream = qs; + sockaddr = ngx_palloc(pool, c->socklen); if (sockaddr == NULL) { ngx_destroy_pool(pool); @@ -709,7 +720,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qs->connection = sc; - sc->quic = qs; + sc->quic = quic; sc->shared = 1; sc->type = SOCK_STREAM; sc->pool = pool; @@ -798,7 +809,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_connection_t *pc; ngx_quic_stream_t *qs; - qs = c->quic; + qs = c->quic->stream; pc = qs->parent; rev = c->read; @@ -939,7 +950,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; + qs = c->quic->stream; pc = qs->parent; qc = ngx_quic_get_connection(pc); wev = c->write; @@ -1081,7 +1092,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; + qs = c->quic->stream; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, "quic stream id:0x%xL cleanup", qs->id); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 41c1cda04..35117e30d 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -4002,7 +4002,7 @@ ngx_http_close_connection(ngx_connection_t *c) #endif #if (NGX_HTTP_V3) - if (c->quic) { + if (c->quic && c->quic->stream) { ngx_http_v3_reset_stream(c); } #endif diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index fe9fae734..b24e637fe 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -79,8 +79,9 @@ #define ngx_http_v3_get_session(c) \ - ((ngx_http_v3_session_t *) ((c)->quic ? (c)->quic->parent->data \ - : (c)->data)) + ((ngx_http_v3_session_t *) ((c)->quic->stream \ + ? (c)->quic->stream->parent->data \ + : (c)->data)) #define ngx_http_quic_get_connection(c) \ (ngx_http_v3_get_session(c)->http_connection) @@ -94,8 +95,10 @@ module) #define ngx_http_v3_finalize_connection(c, code, reason) \ - ngx_quic_set_app_error((c)->quic ? (c)->quic->parent : (c), code, reason);\ - ngx_post_event((c)->quic ? (c)->quic->parent->read : (c)->read, \ + ngx_quic_set_app_error((c)->quic->stream ? (c)->quic->stream->parent \ + : (c), code, reason); \ + ngx_post_event((c)->quic->stream ? (c)->quic->stream->parent->read \ + : (c)->read, \ &ngx_posted_events) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bcbf0dbe1..e0ea77ecb 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -392,7 +392,7 @@ done: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); if (st->prefix.insert_count > 0) { - if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { + if (ngx_http_v3_send_ack_section(c, c->quic->stream->id) != NGX_OK) { return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c228f57d5..46cf1ba30 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -78,7 +78,12 @@ ngx_http_v3_init_stream(ngx_connection_t *c) h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); h3scf->quic.idle_timeout = clcf->keepalive_timeout; - rc = ngx_quic_handshake(c, &h3scf->quic); + if (ngx_quic_create_connection(&h3scf->quic, c, 0) != NGX_OK) { + ngx_close_connection(c); + return; + } + + rc = ngx_quic_handshake(c); if (rc == NGX_AGAIN) { cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, @@ -107,7 +112,7 @@ ngx_http_v3_init_stream(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (c->quic->stream->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ngx_http_v3_init_uni_stream(c); } else { @@ -299,7 +304,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - n = c->quic->id >> 2; + n = c->quic->stream->id >> 2; if (n >= clcf->keepalive_requests * 2) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, @@ -316,7 +321,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) return; } - h3c->next_request_id = c->quic->id + 0x04; + h3c->next_request_id = c->quic->stream->id + 0x04; if (n + 1 == clcf->keepalive_requests || ngx_current_msec - c->start_time > clcf->keepalive_time) @@ -330,7 +335,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) } } - ngx_quic_reject_streams(c->quic->parent); + ngx_quic_reject_streams(c->quic->stream->parent); } cln = ngx_pool_cleanup_add(c->pool, 0); @@ -344,8 +349,8 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) h3c->nrequests++; - if (c->quic->parent->read->timer_set) { - ngx_del_timer(c->quic->parent->read); + if (c->quic->stream->parent->read->timer_set) { + ngx_del_timer(c->quic->stream->parent->read); } rev = c->read; @@ -471,7 +476,7 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) c->log->action = "reading client request"; ngx_reusable_connection(c, 0); - ngx_reusable_connection(c->quic->parent, 0); + ngx_reusable_connection(c->quic->stream->parent, 0); r = ngx_http_create_request(c); if (r == NULL) { @@ -491,7 +496,7 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) * cscf->large_client_header_buffers.num; c->data = r; - c->requests = (c->quic->id >> 2) + 1; + c->requests = (c->quic->stream->id >> 2) + 1; cln = ngx_pool_cleanup_add(r->pool, 0); if (cln == NULL) { @@ -516,9 +521,9 @@ ngx_http_v3_reset_stream(ngx_connection_t *c) if (!c->read->eof && !h3c->hq && h3c->known_streams[NGX_HTTP_V3_STREAM_SERVER_DECODER] - && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + && (c->quic->stream->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + (void) ngx_http_v3_send_cancel_stream(c, c->quic->stream->id); } if (c->timedout) { @@ -556,7 +561,7 @@ ngx_http_v3_cleanup_connection(void *data) clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); - pc = c->quic->parent; + pc = c->quic->stream->parent; ngx_add_timer(pc->read, clcf->keepalive_timeout); ngx_reusable_connection(pc, 1); } diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index 688b66dff..bc5f13624 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -41,7 +41,7 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); - n = c->quic->id >> 2; + n = c->quic->stream->id >> 2; if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { ngx_http_v3_finalize_connection(c, From eedab6dc6ba5440cfbd81f606a8792528544ca1a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 28 Feb 2025 12:41:45 +0400 Subject: [PATCH 09/13] QUIC: client support. Similar to SSL, client mode is enabled by passing NGX_SSL_CLIENT flag to ngx_quic_create_connection(). --- src/core/ngx_connection.c | 4 + src/event/quic/ngx_event_quic.c | 226 +++++++++++++----- src/event/quic/ngx_event_quic_connection.h | 6 +- src/event/quic/ngx_event_quic_connid.c | 4 + src/event/quic/ngx_event_quic_migration.c | 6 - .../quic/ngx_event_quic_openssl_compat.c | 11 +- src/event/quic/ngx_event_quic_output.c | 2 +- src/event/quic/ngx_event_quic_protection.c | 16 +- src/event/quic/ngx_event_quic_protection.h | 2 +- src/event/quic/ngx_event_quic_socket.c | 88 ++++--- src/event/quic/ngx_event_quic_socket.h | 2 +- src/event/quic/ngx_event_quic_ssl.c | 200 ++++++++++------ src/event/quic/ngx_event_quic_ssl.h | 4 +- src/event/quic/ngx_event_quic_streams.c | 33 ++- src/event/quic/ngx_event_quic_tokens.c | 22 ++ src/event/quic/ngx_event_quic_tokens.h | 2 + src/event/quic/ngx_event_quic_transport.c | 153 +++++++----- src/event/quic/ngx_event_quic_transport.h | 6 +- src/event/quic/ngx_event_quic_udp.c | 20 +- 19 files changed, 528 insertions(+), 279 deletions(-) diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 7cae295eb..2674f18e6 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1527,6 +1527,10 @@ ngx_tcp_nodelay(ngx_connection_t *c) { int tcp_nodelay; + if (c->type != SOCK_STREAM) { + return NGX_OK; + } + if (c->tcp_nodelay != NGX_TCP_NODELAY_UNSET) { return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 322477def..15ab8042d 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -14,10 +14,10 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); -static void ngx_quic_input_handler(ngx_event_t *rev); +static void ngx_quic_read_handler(ngx_event_t *rev); +static void ngx_quic_write_handler(ngx_event_t *wev); static void ngx_quic_close_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b); static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, @@ -209,6 +209,7 @@ ngx_quic_create_connection(ngx_quic_conf_t *conf, ngx_connection_t *c, } qc->conf = conf; + qc->is_server = (flags & NGX_SSL_CLIENT) ? 0 : 1; quic = ngx_palloc(c->pool, sizeof(ngx_quic_t)); if (quic == NULL) { @@ -219,8 +220,18 @@ ngx_quic_create_connection(ngx_quic_conf_t *conf, ngx_connection_t *c, quic->stream = NULL; c->quic = quic; + c->start_time = ngx_current_msec; - return NGX_OK; + if (qc->is_server) { + /* initialize server during handshake to save resources */ + return NGX_OK; + } + + if (ngx_quic_new_connection(c, NULL) == NULL) { + return NGX_ERROR; + } + + return ngx_quic_init_connection(c, flags); } @@ -232,7 +243,12 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); - rc = ngx_quic_handle_datagram(c, c->buffer); + if (c->buffer) { + rc = ngx_quic_handle_datagram(c, c->buffer); + + } else { + rc = ngx_quic_do_handshake(c); + } if (rc != NGX_OK) { if (c->ssl) { @@ -249,19 +265,26 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_quic_connstate_dbg(c); - c->read->handler = ngx_quic_input_handler; + c->read->handler = ngx_quic_read_handler; + c->write->handler = ngx_quic_write_handler; - return NGX_AGAIN; + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + return c->ssl->handshaked ? NGX_OK : NGX_AGAIN; } static ngx_quic_connection_t * ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_str_t *secret, scid; ngx_uint_t i; ngx_quic_tp_t *ctp; ngx_quic_conf_t *conf; ngx_quic_connection_t *qc; + u_char scid_buf[NGX_QUIC_SERVER_CID_LEN]; qc = c->quic->connection; conf = qc->conf; @@ -271,7 +294,45 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) return NULL; } - qc->version = pkt->version; + if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { + return NULL; + } + + if (pkt == NULL) { + /* client */ + + if (RAND_bytes(scid_buf, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NULL; + } + + scid.data = scid_buf; + scid.len = NGX_QUIC_SERVER_CID_LEN; + secret = &scid; + + qc->validated = 1; + qc->version = 0x01; + + } else { + secret = &pkt->dcid; + scid = pkt->scid; + + qc->validated = pkt->validated; + qc->version = pkt->version; + + 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; + } + } + + 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 NULL; + } + } ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -316,10 +377,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) qc->conf = conf; - if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { - return NULL; - } - ctp = &qc->ctp; /* defaults to be used before actual client parameters are received */ @@ -348,27 +405,23 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) + conf->max_concurrent_streams_bidi) * conf->stream_buffer_size / 2000; - 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 (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + if (ngx_quic_keys_set_initial_secret(qc->keys, secret, qc->is_server, + c->log) != NGX_OK) { return NULL; } - qc->validated = pkt->validated; - - if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { + if (ngx_quic_open_sockets(c, qc, &scid, pkt ? &pkt->dcid : NULL) != NGX_OK) + { ngx_quic_keys_cleanup(qc->keys); return NULL; } + if (qc->validated) { + qc->path->validated = 1; + } + c->idle = 1; qc->initialized = 1; @@ -428,49 +481,58 @@ ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) static void -ngx_quic_input_handler(ngx_event_t *rev) +ngx_quic_read_handler(ngx_event_t *rev) { - ngx_int_t rc; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_quic_connection_t *qc; + ssize_t n; + ngx_buf_t buf; + ngx_connection_t *c; + static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic read handler"); c = rev->data; - qc = ngx_quic_get_connection(c); c->log->action = "handling quic input"; - if (c->close || rev->timedout) { - goto done; - } + while (rev->ready) { + n = c->recv(c, buffer, sizeof(buffer)); - b = c->udp->buffer; - if (b == NULL) { - goto done; - } + if (n <= 0) { + break; + } - rc = ngx_quic_handle_datagram(c, b); + ngx_memzero(&buf, sizeof(ngx_buf_t)); - if (rc == NGX_ERROR) { - ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, - "datagram handling error"); - goto done; + buf.pos = buffer; + buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); + + if (ngx_quic_handle_datagram(c, &buf) == NGX_ERROR) { + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "datagram handling error"); + break; + } } - if (rc == NGX_DONE) { - goto done; + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "socket error"); } - /* rc == NGX_OK */ + ngx_quic_end_handler(c); +} - qc->send_timer_set = 0; - ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); -done: +static void +ngx_quic_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; - ngx_quic_end_handler(c); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic write handler"); + + ngx_del_event(c->write, NGX_WRITE_EVENT, 0); } @@ -502,6 +564,11 @@ ngx_quic_shutdown(ngx_connection_t *c) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; + if (c->quic->stream) { + /* QUIC stream */ + return NGX_OK; + } + qc = ngx_quic_get_connection(c); if (!qc->initialized) { @@ -739,7 +806,7 @@ ngx_quic_close_handler(ngx_event_t *ev) } -static ngx_int_t +ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b) { size_t size; @@ -754,9 +821,11 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b) path = NULL; size = b->last - b->pos; - p = start = b->pos; + qc = ngx_quic_get_connection(c); + qc->received += size; + while (p < b->last) { ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -823,10 +892,6 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b) return NGX_DONE; } - qc = ngx_quic_get_connection(c); - - qc->received += size; - if ((uint64_t) (c->sent + qc->received) / 8 > (qc->streams.sent + qc->streams.recv_last) + 1048576) { @@ -837,6 +902,9 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b) return NGX_ERROR; } + qc->send_timer_set = 0; + ngx_add_timer(&qc->close, qc->tp.max_idle_timeout); + return NGX_OK; } @@ -847,6 +915,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_int_t rc; ngx_quic_conf_t *conf; ngx_quic_socket_t *qsock; + ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; c->log->action = "parsing quic packet"; @@ -911,14 +980,35 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) } } - if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { - return NGX_DECLINED; + if (qc->is_server) { + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } } - } rc = ngx_quic_handle_payload(c, pkt); + if (rc == NGX_OK + && !qc->is_server + && !qc->scid_set + && pkt->level == NGX_QUIC_ENCRYPTION_INITIAL) + { + /* RFC 9000, 7.2. Negotiating Connection IDs + * + * After processing the first Initial packet, each endpoint sets + * the Destination Connection ID field in subsequent packets it + * sends to the value of the Source Connection ID field that it + * received. + */ + + qc->scid_set = 1; + + cid = qc->path->cid; + ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); + cid->len = pkt->scid.len; + } + if (rc == NGX_DECLINED && pkt->level == NGX_QUIC_ENCRYPTION_APPLICATION) { @@ -1076,7 +1166,7 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (c->ssl == NULL) { - if (ngx_quic_init_connection(c) != NGX_OK) { + if (ngx_quic_init_connection(c, 0) != NGX_OK) { return NGX_ERROR; } } @@ -1266,7 +1356,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) chain.next = NULL; frame.data = &chain; - len = ngx_quic_parse_frame(pkt, p, end, &frame); + len = ngx_quic_parse_frame(pkt, p, end, &frame, qc->is_server); if (len < 0) { qc->error = pkt->error; @@ -1328,6 +1418,14 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PING: break; + case NGX_QUIC_FT_NEW_TOKEN: + + if (ngx_quic_handle_new_token_frame(c, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_STREAM: if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { @@ -1465,6 +1563,14 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) break; + case NGX_QUIC_FT_HANDSHAKE_DONE: + + if (ngx_quic_handle_handshake_done_frame(c) != 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_connection.h b/src/event/quic/ngx_event_quic_connection.h index e3782a8de..879052bf4 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -286,6 +286,9 @@ struct ngx_quic_connection_s { const char *error_reason; unsigned initialized:1; + unsigned handshaked:1; + unsigned is_server:1; + unsigned scid_set:1; unsigned error_app:1; unsigned send_timer_set:1; unsigned lingering:1; @@ -294,7 +297,7 @@ struct ngx_quic_connection_s { unsigned draining:1; unsigned key_phase:1; unsigned validated:1; - unsigned client_tp_done:1; + unsigned peer_tp_done:1; #if (NGX_QUIC_OPENSSL_API) unsigned read_level:2; @@ -303,6 +306,7 @@ struct ngx_quic_connection_s { }; +ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b); ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp); void ngx_quic_discard_ctx(ngx_connection_t *c, ngx_uint_t level); diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 4e7b8dc22..340b736e7 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -51,6 +51,10 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) uint64_t cookie; socklen_t optlen; + if (c->listening == NULL) { + return NGX_OK; + } + fd = c->listening->fd; optlen = sizeof(cookie); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index bb7e87f75..568020488 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -313,12 +313,6 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) len = pkt->raw->last - pkt->raw->start; - if (c->udp->buffer == NULL) { - /* first ever packet in connection, path already exists */ - path = qc->path; - goto update; - } - probe = NULL; for (q = ngx_queue_head(&qc->paths); diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c index 58298dcb8..fb6345b4d 100644 --- a/src/event/quic/ngx_event_quic_openssl_compat.c +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -116,6 +116,8 @@ ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) return; } + qc = ngx_quic_get_connection(c); + p = (u_char *) line; for (start = p; *p && *p != ' '; p++); @@ -129,27 +131,27 @@ ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) { level = ssl_encryption_handshake; - write = 0; + write = !qc->is_server; } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0) { level = ssl_encryption_handshake; - write = 1; + write = qc->is_server; } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) == 0) { level = ssl_encryption_application; - write = 0; + write = !qc->is_server; } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) == 0) { level = ssl_encryption_application; - write = 1; + write = qc->is_server; } else { return; @@ -201,7 +203,6 @@ ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) } } - qc = ngx_quic_get_connection(c); com = qc->compat; cipher = SSL_get_current_cipher(ssl); diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 22a554ad2..a1a51d036 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -969,7 +969,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, pkt.keys = &keys; - if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) + if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, 1, c->log) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 7fb6c3f7e..8c2fd764c 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -119,7 +119,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, - ngx_log_t *log) + ngx_uint_t is_server, ngx_log_t *log) { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -136,8 +136,14 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a }; - client = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].read; - server = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].write; + if (is_server) { + client = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].read; + server = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].write; + + } else { + client = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].write; + server = &keys->secrets[NGX_QUIC_ENCRYPTION_INITIAL].read; + } /* * RFC 9001, section 5. Packet Protection @@ -200,13 +206,13 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, 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, !is_server, 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, is_server, log) == NGX_ERROR) { goto failed; diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index bfd5d1446..f5f8cf1de 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -92,7 +92,7 @@ typedef struct { ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, - ngx_str_t *secret, ngx_log_t *log); + ngx_str_t *secret, ngx_uint_t is_server, ngx_log_t *log); ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, ngx_quic_keys_t *keys, ngx_uint_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index c2bc822a5..b08ba47e4 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -12,8 +12,10 @@ ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_quic_header_t *pkt) + ngx_str_t *scid, ngx_str_t *dcid) { + u_char *p; + ngx_str_t sid; ngx_quic_socket_t *qsock, *tmp; ngx_quic_client_id_t *cid; @@ -35,44 +37,45 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, 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; - } - - /* socket to use for further processing (id auto-generated) */ qsock = ngx_quic_create_socket(c, qc); if (qsock == NULL) { return NGX_ERROR; } - /* socket is listening at new server id */ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { return NGX_ERROR; } qsock->used = 1; - qc->tp.initial_scid.len = qsock->sid.len; - qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len); - if (qc->tp.initial_scid.data == NULL) { + ngx_memcpy(&qsock->sockaddr, c->sockaddr, c->socklen); + qsock->socklen = c->socklen; + + c->udp = &qsock->udp; + + sid.data = qsock->sid.id; + sid.len = qsock->sid.len; + + if (ngx_quic_new_sr_token(c, &sid, qc->conf->sr_token_key, qc->tp.sr_token) + != NGX_OK) + { goto failed; } - ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len); - /* for all packets except first, this is set at udp layer */ - c->udp = &qsock->udp; + p = ngx_pnalloc(c->pool, qsock->sid.len); + if (p == NULL) { + goto failed; + } - /* ngx_quic_get_connection(c) macro is now usable */ + ngx_memcpy(p, qsock->sid.id, qsock->sid.len); + qc->tp.initial_scid.data = p; + qc->tp.initial_scid.len = qsock->sid.len; - /* we have a client identified by scid */ - cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); + cid = ngx_quic_create_client_id(c, scid, 0, NULL); if (cid == NULL) { goto failed; } - /* path of the first packet is our initial active path */ qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); if (qc->path == NULL) { goto failed; @@ -80,31 +83,32 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, qc->path->tag = NGX_QUIC_PATH_ACTIVE; - if (pkt->validated) { - qc->path->validated = 1; - } - ngx_quic_path_dbg(c, "set active", qc->path); - tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); - if (tmp == NULL) { - goto failed; - } + if (dcid) { + tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (tmp == NULL) { + goto failed; + } - tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ + tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ - ngx_memcpy(tmp->sid.id, pkt->dcid.data, pkt->dcid.len); - tmp->sid.len = pkt->dcid.len; + ngx_memcpy(tmp->sid.id, dcid->data, dcid->len); + tmp->sid.len = dcid->len; - if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { - goto failed; + if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { + goto failed; + } } return NGX_OK; failed: - ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (c->listening) { + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } + c->udp = NULL; return NGX_ERROR; @@ -155,7 +159,10 @@ ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_queue_remove(&qsock->queue); ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); - ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (c->listening) { + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } + qc->nsockets--; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -179,17 +186,20 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, qsock->udp.connection = c; qsock->udp.node.key = ngx_crc32_long(id.data, id.len); qsock->udp.key = id; + qsock->quic = qc; - ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + if (c->listening) { + ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + } ngx_queue_insert_tail(&qc->sockets, &qsock->queue); qc->nsockets++; - qsock->quic = qc; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket seq:%L listening at sid:%xV nsock:%ui", - (int64_t) sid->seqnum, &id, qc->nsockets); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L listening at sid:%*xs nsock:%ui", + (int64_t) qsock->sid.seqnum, qsock->sid.len, qsock->sid.id, + qc->nsockets); return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h index 68ecc063d..1afd170aa 100644 --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -13,7 +13,7 @@ ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); + ngx_quic_connection_t *qc, ngx_str_t *scid, ngx_str_t *dcid); void ngx_quic_close_sockets(ngx_connection_t *c); ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index c58571a36..880a90637 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -59,7 +59,6 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, #endif -static ngx_int_t ngx_quic_do_handshake(ngx_connection_t *c); static ngx_int_t ngx_quic_crypto_provide(ngx_connection_t *c, ngx_uint_t level); @@ -85,18 +84,20 @@ ngx_quic_cbs_send(ngx_ssl_conn_t *ssl_conn, *consumed = 0; - SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + if (qc->is_server) { + 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 = "missing ALPN extension"; + if (alpn_len == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "missing ALPN extension"; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic missing ALPN extension"); - return 1; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic missing ALPN extension"); + return 1; + } } - if (!qc->client_tp_done) { + if (!qc->peer_tp_done) { /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); qc->error_reason = "missing transport parameters"; @@ -295,7 +296,7 @@ ngx_quic_cbs_got_transport_params(ngx_ssl_conn_t *ssl_conn, return 1; } - qc->client_tp_done = 1; + qc->peer_tp_done = 1; return 1; } @@ -491,68 +492,75 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_add_handshake_data"); - if (!qc->client_tp_done) { - /* - * things to do once during handshake: check ALPN and transport - * parameters; we want to break handshake if something is wrong - * here; - */ + if (!qc->peer_tp_done) { - SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + if (qc->is_server) { + /* + * things to do once during handshake: check ALPN and transport + * parameters; we want to break handshake if something is wrong + * here; + */ - if (alpn_len == 0) { - if (qc->error == 0) { - qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); - qc->error_reason = "missing ALPN extension"; + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic missing ALPN extension"); - } + if (alpn_len == 0) { + if (qc->error == 0) { + qc->error = + NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "missing ALPN extension"; - return 1; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic missing ALPN extension"); + } + + return 1; + } } - SSL_get_peer_quic_transport_params(ssl_conn, &client_params, - &client_params_len); + if (qc->received > 0) { + SSL_get_peer_quic_transport_params(ssl_conn, &client_params, + &client_params_len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_get_peer_quic_transport_params():" + " params_len:%ui", client_params_len); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_get_peer_quic_transport_params():" - " params_len:%ui", client_params_len); + if (client_params_len == 0) { + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ - if (client_params_len == 0) { - /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ + if (qc->error == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); + qc->error_reason = "missing transport parameters"; - if (qc->error == 0) { - qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); - qc->error_reason = "missing transport parameters"; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "missing transport parameters"); + } - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "missing transport parameters"); + return 1; } - return 1; - } + p = (u_char *) client_params; + end = p + client_params_len; - p = (u_char *) client_params; - end = p + client_params_len; + /* defaults for parameters not sent by client */ + ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); - /* defaults for parameters not sent by client */ - ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); + if (ngx_quic_parse_transport_params(p, end, &ctp, qc->is_server, + c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; - if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) - != NGX_OK) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "failed to process transport parameters"; + return 1; + } - return 1; - } + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { + return 1; + } - if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { - return 1; + qc->peer_tp_done = 1; } - - qc->client_tp_done = 1; } ctx = ngx_quic_get_send_ctx(qc, level); @@ -685,7 +693,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } -static ngx_int_t +ngx_int_t ngx_quic_do_handshake(ngx_connection_t *c) { int n, sslerr; @@ -733,7 +741,7 @@ ngx_quic_do_handshake(ngx_connection_t *c) if (!SSL_is_init_finished(ssl_conn)) { if (ngx_quic_keys_available(qc->keys, NGX_QUIC_ENCRYPTION_EARLY_DATA, 0) - && qc->client_tp_done) + && qc->peer_tp_done) { goto done; } @@ -745,19 +753,37 @@ ngx_quic_do_handshake(ngx_connection_t *c) ngx_ssl_handshake_log(c); #endif - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; + if (qc->handshaked) { + return NGX_OK; } - frame->level = NGX_QUIC_ENCRYPTION_APPLICATION; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(qc, frame); + qc->handshaked = 1; - if (qc->conf->retry) { - if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + if (qc->is_server) { + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { return NGX_ERROR; } + + frame->level = NGX_QUIC_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; + } + } + + /* + * RFC 9001, 4.9.2. Discarding Handshake Keys + * + * An endpoint MUST discard its Handshake keys + * when the TLS handshake is confirmed. + */ + ngx_quic_discard_ctx(c, NGX_QUIC_ENCRYPTION_HANDSHAKE); + + ngx_quic_discover_path_mtu(c, qc->path); } /* @@ -768,16 +794,6 @@ ngx_quic_do_handshake(ngx_connection_t *c) ngx_post_event(&qc->key_update, &ngx_posted_events); - /* - * RFC 9001, 4.9.2. Discarding Handshake Keys - * - * An endpoint MUST discard its Handshake keys - * when the TLS handshake is confirmed. - */ - ngx_quic_discard_ctx(c, NGX_QUIC_ENCRYPTION_HANDSHAKE); - - ngx_quic_discover_path_mtu(c, qc->path); - /* start accepting clients on negotiated number of server ids */ if (ngx_quic_create_sockets(c) != NGX_OK) { return NGX_ERROR; @@ -856,7 +872,33 @@ ngx_quic_crypto_provide(ngx_connection_t *c, ngx_uint_t level) ngx_int_t -ngx_quic_init_connection(ngx_connection_t *c) +ngx_quic_handle_handshake_done_frame(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->is_server) { + return NGX_ERROR; + } + + /* + * RFC 9001, 4.1.2 Handshake Confirmed. + * + * At the client, the handshake is considered confirmed + * when a HANDSHAKE_DONE frame is received. + */ + + ngx_quic_discard_ctx(c, NGX_QUIC_ENCRYPTION_HANDSHAKE); + + ngx_quic_discover_path_mtu(c, qc->path); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c, ngx_uint_t flags) { u_char *p; size_t clen; @@ -895,7 +937,7 @@ ngx_quic_init_connection(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { + if (ngx_ssl_create_connection(qc->conf->ssl, c, flags) != NGX_OK) { return NGX_ERROR; } @@ -952,7 +994,8 @@ ngx_quic_init_connection(ngx_connection_t *c) 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->is_server); /* always succeeds */ p = ngx_pnalloc(c->pool, len); @@ -960,7 +1003,8 @@ ngx_quic_init_connection(ngx_connection_t *c) 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->is_server); if (len < 0) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h index ee0aa07c9..09026f7ef 100644 --- a/src/event/quic/ngx_event_quic_ssl.h +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -11,9 +11,11 @@ #include #include -ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); +ngx_int_t ngx_quic_init_connection(ngx_connection_t *c, ngx_uint_t flags); +ngx_int_t ngx_quic_do_handshake(ngx_connection_t *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_handle_handshake_done_frame(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 index ea50662b1..2319f9cf0 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -44,7 +44,16 @@ static void ngx_quic_set_event(ngx_event_t *ev); static ngx_uint_t ngx_quic_is_stream_local(ngx_connection_t *c, uint64_t id) { - return (id & NGX_QUIC_STREAM_SERVER_INITIATED) ? 1 : 0; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->is_server) { + return (id & NGX_QUIC_STREAM_SERVER_INITIATED) ? 1 : 0; + + } else { + return (id & NGX_QUIC_STREAM_SERVER_INITIATED) ? 0 : 1; + } } @@ -73,8 +82,11 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) return NULL; } - id = (qc->streams.local_streams_bidi << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED; + id = (qc->streams.local_streams_bidi << 2); + + if (qc->is_server) { + id |= NGX_QUIC_STREAM_SERVER_INITIATED; + } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic creating local bidi stream" @@ -95,9 +107,12 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) } id = (qc->streams.local_streams_uni << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED | NGX_QUIC_STREAM_UNIDIRECTIONAL; + if (qc->is_server) { + id |= NGX_QUIC_STREAM_SERVER_INITIATED; + } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic creating local uni stream" " streams:%uL max:%uL id:0x%xL", @@ -463,6 +478,11 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) min_id = (qc->streams.remote_streams_uni << 2) | NGX_QUIC_STREAM_UNIDIRECTIONAL; + + if (!qc->is_server) { + min_id |= NGX_QUIC_STREAM_SERVER_INITIATED; + } + qc->streams.remote_streams_uni = (id >> 2) + 1; } else { @@ -486,6 +506,11 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) } min_id = (qc->streams.remote_streams_bidi << 2); + + if (!qc->is_server) { + min_id |= NGX_QUIC_STREAM_SERVER_INITIATED; + } + qc->streams.remote_streams_bidi = (id >> 2) + 1; } diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index c1da0d472..7edbfae23 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -307,3 +307,25 @@ bad_token: return NGX_DECLINED; } + + +ngx_int_t +ngx_quic_handle_new_token_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->is_server) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic received token len:%uz %*xs", frame->u.token.length, + frame->data->buf->last - frame->data->buf->pos, + frame->data->buf->pos); +#endif + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h index ee3fe5b9a..428767ef0 100644 --- a/src/event/quic/ngx_event_quic_tokens.h +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -30,5 +30,7 @@ ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, 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_handle_new_token_frame(ngx_connection_t *c, + ngx_quic_frame_t *frame); #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 index ba6211c33..72b545615 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -97,7 +97,7 @@ static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp); static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, - ngx_uint_t frame_type); + ngx_uint_t frame_type, ngx_uint_t is_server); static size_t ngx_quic_create_ping(u_char *p); static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges); @@ -742,7 +742,7 @@ ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, - ngx_quic_frame_t *f) + ngx_quic_frame_t *f, ngx_uint_t is_server) { u_char *p; uint64_t varint; @@ -770,7 +770,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->type = varint; - if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { + if (ngx_quic_frame_allowed(pkt, f->type, is_server) != NGX_OK) { pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; return NGX_ERROR; } @@ -952,6 +952,22 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; + case NGX_QUIC_FT_NEW_TOKEN: + + p = ngx_quic_parse_int(p, end, &f->u.token.length); + if (p == NULL) { + goto error; + } + + p = ngx_quic_read_bytes(p, end, f->u.token.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + break; + case NGX_QUIC_FT_STREAM: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: @@ -1133,6 +1149,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; + case NGX_QUIC_FT_HANDSHAKE_DONE: + + break; + default: ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unknown frame type 0x%xi", f->type); @@ -1158,7 +1178,8 @@ error: static ngx_int_t -ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type, + ngx_uint_t is_server) { uint8_t ptype; @@ -1168,37 +1189,37 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) * Frame permissions per packet: 4 bits: IH01 */ 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 */ 0xFF, + /* PING */ 0xFF, + /* ACK */ 0xDD, + /* ACK_ECN */ 0xDD, + /* RESET_STREAM */ 0x33, + /* STOP_SENDING */ 0x33, + /* CRYPTO */ 0xDD, + /* NEW_TOKEN */ 0x01, /* only sent by server */ + /* STREAM */ 0x33, + /* STREAM1 */ 0x33, + /* STREAM2 */ 0x33, + /* STREAM3 */ 0x33, + /* STREAM4 */ 0x33, + /* STREAM5 */ 0x33, + /* STREAM6 */ 0x33, + /* STREAM7 */ 0x33, + /* MAX_DATA */ 0x33, + /* MAX_STREAM_DATA */ 0x33, + /* MAX_STREAMS */ 0x33, + /* MAX_STREAMS2 */ 0x33, + /* DATA_BLOCKED */ 0x33, + /* STREAM_DATA_BLOCKED */ 0x33, + /* STREAMS_BLOCKED */ 0x33, + /* STREAMS_BLOCKED2 */ 0x33, + /* NEW_CONNECTION_ID */ 0x33, + /* RETIRE_CONNECTION_ID */ 0x33, + /* PATH_CHALLENGE */ 0x33, + /* PATH_RESPONSE */ 0x11, + /* CONNECTION_CLOSE */ 0xFF, + /* CONNECTION_CLOSE2 */ 0x33, + /* HANDSHAKE_DONE */ 0x01, /* only sent by server */ }; if (ngx_quic_long_pkt(pkt->flags)) { @@ -1217,6 +1238,10 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) ptype = 1; /* application data */ } + if (is_server) { + ptype <<= 4; + } + if (ptype & ngx_quic_frame_masks[frame_type]) { return NGX_OK; } @@ -1723,7 +1748,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, - ngx_log_t *log) + ngx_uint_t is_server, ngx_log_t *log) { uint64_t id, len; ngx_int_t rc; @@ -1736,15 +1761,17 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, return NGX_ERROR; } - switch (id) { - case NGX_QUIC_TP_ORIGINAL_DCID: - 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 (is_server) { + switch (id) { + case NGX_QUIC_TP_ORIGINAL_DCID: + 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; + } } p = ngx_quic_parse_int(p, end, &len); @@ -2028,7 +2055,7 @@ ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) 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 is_server) { u_char *p; size_t len; @@ -2099,16 +2126,19 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, 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); 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); - } + if (is_server) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); - 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 (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 (pos == NULL) { return len; @@ -2154,16 +2184,19 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, 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); 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); - } + if (is_server) { + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + + 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); + 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 index 12a39c698..2319d6bdf 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -382,7 +382,7 @@ size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, u_char **start); ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, - ngx_quic_frame_t *frame); + ngx_quic_frame_t *frame, ngx_uint_t is_server); ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, @@ -392,9 +392,9 @@ size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); 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_uint_t is_server, ngx_log_t *log); 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 is_server); void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c index 15b54bc82..95933359c 100644 --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -174,6 +174,8 @@ ngx_quic_recvmsg(ngx_event_t *ev) } #endif + c->log->action = "handling quic input"; + ngx_memzero(&buf, sizeof(ngx_buf_t)); buf.pos = buffer; @@ -186,20 +188,12 @@ ngx_quic_recvmsg(ngx_event_t *ev) ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); qsock->socklen = socklen; - c->udp->buffer = &buf; - - rev = c->read; - rev->ready = 1; - rev->active = 0; - - rev->handler(rev); - - if (c->udp) { - c->udp->buffer = NULL; + if (ngx_quic_handle_datagram(c, &buf) == NGX_ERROR) { + ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, + "datagram handling error"); } - rev->ready = 0; - rev->active = 1; + ngx_quic_end_handler(c); goto next; } @@ -291,8 +285,6 @@ ngx_quic_recvmsg(ngx_event_t *ev) c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - c->start_time = ngx_current_msec; - #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); #endif From 5366f39ff2c004ff2e0181e0b3ebcfb662ccf165 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 1 Jul 2025 14:43:23 +0400 Subject: [PATCH 10/13] QUIC: allow changing main connection log. --- src/event/quic/ngx_event_quic.c | 18 +++++++++--------- src/event/quic/ngx_event_quic_ack.c | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 15ab8042d..45aa1e877 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -355,23 +355,23 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_init_rtt(qc); - qc->pto.log = c->log; + qc->pto.log = ngx_cycle->log; qc->pto.data = c; qc->pto.handler = ngx_quic_pto_handler; - qc->push.log = c->log; + qc->push.log = ngx_cycle->log; qc->push.data = c; qc->push.handler = ngx_quic_push_handler; - qc->close.log = c->log; + qc->close.log = ngx_cycle->log; qc->close.data = c; qc->close.handler = ngx_quic_close_handler; - qc->path_validation.log = c->log; + qc->path_validation.log = ngx_cycle->log; qc->path_validation.data = c; qc->path_validation.handler = ngx_quic_path_handler; - qc->key_update.log = c->log; + qc->key_update.log = ngx_cycle->log; qc->key_update.data = c; qc->key_update.handler = ngx_quic_keys_update; @@ -788,10 +788,10 @@ ngx_quic_close_handler(ngx_event_t *ev) ngx_connection_t *c; ngx_quic_connection_t *qc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); - c = ev->data; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close handler"); + if (ev->timedout) { ev->timedout = 0; ngx_quic_set_error(c, NGX_QUIC_ERR_CLOSE, ""); @@ -1618,10 +1618,10 @@ ngx_quic_push_handler(ngx_event_t *ev) { ngx_connection_t *c; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); - c = ev->data; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic push handler"); + if (ngx_quic_output(c) != NGX_OK) { ngx_quic_set_error(c, NGX_QUIC_ERR_INTERNAL_ERROR, "output error"); } diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 80639951b..b72acafa3 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -1110,12 +1110,12 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); - c = ev->data; qc = ngx_quic_get_connection(c); now = ngx_current_msec; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic pto timer"); + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; From 56699cc3ff571ccb1791ed2fc8f5ddd0d418e49c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 28 May 2025 17:00:26 +0400 Subject: [PATCH 11/13] HTTP/3: modified ngx_http_v3_encode_field_l() prototype. --- src/http/v3/ngx_http_v3_encode.c | 25 ++++++++++++----------- src/http/v3/ngx_http_v3_encode.h | 4 ++-- src/http/v3/ngx_http_v3_filter_module.c | 27 ++++++++++++++----------- src/http/v3/ngx_http_v3_request.c | 4 ++-- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c index fb089c413..763792914 100644 --- a/src/http/v3/ngx_http_v3_encode.c +++ b/src/http/v3/ngx_http_v3_encode.c @@ -180,7 +180,8 @@ ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, uintptr_t -ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +ngx_http_v3_encode_field_l(u_char *p, u_char *name, size_t name_len, + u_char *value, size_t value_len) { size_t hlen; u_char *p1, *p2; @@ -188,18 +189,18 @@ ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) /* Literal Field Line With Literal Name */ if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) - + name->len - + ngx_http_v3_encode_prefix_int(NULL, value->len, 7) - + value->len; + return ngx_http_v3_encode_prefix_int(NULL, name_len, 3) + + name_len + + ngx_http_v3_encode_prefix_int(NULL, value_len, 7) + + value_len; } p1 = p; *p = 0x20; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name_len, 3); p2 = p; - hlen = ngx_http_huff_encode(name->data, name->len, p, 1); + hlen = ngx_http_huff_encode(name, name_len, p, 1); if (hlen) { p = p1; @@ -213,16 +214,16 @@ ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) p += hlen; } else { - ngx_strlow(p, name->data, name->len); - p += name->len; + ngx_strlow(p, name, name_len); + p += name_len; } p1 = p; *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value_len, 7); p2 = p; - hlen = ngx_http_huff_encode(value->data, value->len, p, 0); + hlen = ngx_http_huff_encode(value, value_len, p, 0); if (hlen) { p = p1; @@ -236,7 +237,7 @@ ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) p += hlen; } else { - p = ngx_cpymem(p, value->data, value->len); + p = ngx_cpymem(p, value, value_len); } return (uintptr_t) p; diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h index fca376da5..e227b6f77 100644 --- a/src/http/v3/ngx_http_v3_encode.h +++ b/src/http/v3/ngx_http_v3_encode.h @@ -24,8 +24,8 @@ uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index); uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, u_char *data, size_t len); -uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, - ngx_str_t *value); +uintptr_t ngx_http_v3_encode_field_l(u_char *p, u_char *name, size_t name_len, + u_char *data, size_t value_len); uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index); uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, size_t len); diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index e3f15368f..2eaba1bc3 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -313,8 +313,9 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) continue; } - len += ngx_http_v3_encode_field_l(NULL, &header[i].key, - &header[i].value); + len += ngx_http_v3_encode_field_l(NULL, + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); @@ -500,8 +501,8 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, - &header[i].key, - &header[i].value); + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } if (r->header_only) { @@ -631,8 +632,9 @@ ngx_http_v3_early_hints_filter(ngx_http_request_t *r) continue; } - len += ngx_http_v3_encode_field_l(NULL, &header[i].key, - &header[i].value); + len += ngx_http_v3_encode_field_l(NULL, + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } if (len == 0) { @@ -685,8 +687,8 @@ ngx_http_v3_early_hints_filter(ngx_http_request_t *r) &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, - &header[i].key, - &header[i].value); + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } b->flush = 1; @@ -888,8 +890,9 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r, continue; } - len += ngx_http_v3_encode_field_l(NULL, &header[i].key, - &header[i].value); + len += ngx_http_v3_encode_field_l(NULL, + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } cl = ngx_chain_get_free_buf(r->pool, &ctx->free); @@ -945,8 +948,8 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r, &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, - &header[i].key, - &header[i].value); + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); } n = b->last - b->pos; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 46cf1ba30..f8a89403b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -702,8 +702,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) /* rc == NGX_OK || rc == NGX_DONE */ h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, - &st->field_rep.field.name, - &st->field_rep.field.value); + st->field_rep.field.name.data, st->field_rep.field.name.len, + st->field_rep.field.value.data, st->field_rep.field.value.len); if (ngx_http_v3_process_header(r, &st->field_rep.field.name, &st->field_rep.field.value) From aaa7e239a73a7baa5cb6ea0a78ecc48f78a519e7 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 2 Jun 2025 14:07:04 +0400 Subject: [PATCH 12/13] HTTP/3: client preparation. A number of changes in HTTP/3 server code in preparation for adding HTTP/3 client support: - parse, table and uni function no longer access server configurations - parse functions no longer call action functions and use callbacks instead - added session "data" field, which stores ngx_http_connection_t reference - added session fields "max_table_capacity" and "max_blocked_streams" - session field "blocked" is renamed to "queue" - ngx_http_v3_close_connection() is moved to ngx_http_v3.c and made public - made QPACK header constants public --- src/http/v3/ngx_http_v3.c | 31 ++- src/http/v3/ngx_http_v3.h | 14 +- src/http/v3/ngx_http_v3_filter_module.c | 20 -- src/http/v3/ngx_http_v3_parse.c | 352 +++++++++--------------- src/http/v3/ngx_http_v3_parse.h | 81 +++++- src/http/v3/ngx_http_v3_request.c | 196 ++++++++----- src/http/v3/ngx_http_v3_table.c | 20 +- src/http/v3/ngx_http_v3_table.h | 20 ++ src/http/v3/ngx_http_v3_uni.c | 165 +++++++++-- src/http/v3/ngx_http_v3_uni.h | 3 - 10 files changed, 534 insertions(+), 368 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index c99c6cdfe..43cd1f39d 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -17,11 +17,8 @@ ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c) { ngx_pool_cleanup_t *cln; - ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; - hc = c->data; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); @@ -29,9 +26,9 @@ ngx_http_v3_init_session(ngx_connection_t *c) goto failed; } - h3c->http_connection = hc; + h3c->connection = c; - ngx_queue_init(&h3c->blocked); + ngx_queue_init(&h3c->queue); h3c->table.send_insert_count.log = c->log; h3c->table.send_insert_count.data = c; @@ -86,3 +83,27 @@ ngx_http_v3_check_flood(ngx_connection_t *c) return NGX_OK; } + + +void +ngx_http_v3_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + if (ngx_quic_shutdown(c) == NGX_AGAIN) { + c->ssl->handler = ngx_http_v3_close_connection; + return; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index b24e637fe..b43ad7253 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -84,7 +84,7 @@ : (c)->data)) #define ngx_http_quic_get_connection(c) \ - (ngx_http_v3_get_session(c)->http_connection) + ((ngx_http_connection_t *) ngx_http_v3_get_session(c)->data) #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ @@ -121,14 +121,19 @@ struct ngx_http_v3_parse_s { struct ngx_http_v3_session_s { - ngx_http_connection_t *http_connection; + ngx_connection_t *connection; + + void *data; ngx_http_v3_dynamic_table_t table; - ngx_uint_t nrequests; + ngx_queue_t queue; - ngx_queue_t blocked; + size_t max_table_capacity; + ngx_uint_t max_blocked_streams; + ngx_uint_t nrequests; ngx_uint_t nblocked; + ngx_uint_t max_literal; uint64_t next_request_id; @@ -146,6 +151,7 @@ void ngx_http_v3_init_stream(ngx_connection_t *c); void ngx_http_v3_reset_stream(ngx_connection_t *c); ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); +void ngx_http_v3_close_connection(ngx_connection_t *c); ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 2eaba1bc3..cb1938b7d 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -10,26 +10,6 @@ #include -/* static table indices */ -#define NGX_HTTP_V3_HEADER_AUTHORITY 0 -#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 -#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 -#define NGX_HTTP_V3_HEADER_DATE 6 -#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 -#define NGX_HTTP_V3_HEADER_LOCATION 12 -#define NGX_HTTP_V3_HEADER_METHOD_GET 17 -#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 -#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 -#define NGX_HTTP_V3_HEADER_STATUS_103 24 -#define NGX_HTTP_V3_HEADER_STATUS_200 25 -#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 -#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 -#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 -#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 -#define NGX_HTTP_V3_HEADER_SERVER 92 -#define NGX_HTTP_V3_HEADER_USER_AGENT 95 - - typedef struct { ngx_chain_t *free; ngx_chain_t *busy; diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index e0ea77ecb..e37a7de5e 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -20,8 +20,6 @@ static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *n); static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length); -static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, - ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); @@ -42,24 +40,14 @@ static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, ngx_buf_t *b); -static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, - ngx_http_v3_parse_control_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, ngx_http_v3_parse_settings_t *st, ngx_buf_t *b); -static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, - ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, ngx_buf_t *b); -static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, - ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); - -static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c, - ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); - static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n) @@ -94,7 +82,7 @@ ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) } -static ngx_int_t +ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) { @@ -220,8 +208,6 @@ ngx_http_v3_parse_prefix_int(ngx_connection_t *c, if (st->shift == 56 && ((ch & 0x80) || (st->value & 0xc000000000000000))) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client exceeded integer size limit"); return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; } @@ -248,8 +234,10 @@ ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_buf_t *b) { - ngx_buf_t loc; - ngx_int_t rc; + ngx_buf_t loc; + ngx_int_t rc; + ngx_http_v3_parse_field_t *f; + enum { sw_start = 0, sw_type, @@ -348,14 +336,32 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return rc; } + st->field_rep.max_literal = st->max_literal; + st->state = sw_verify; break; case sw_verify: - rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); - if (rc != NGX_OK) { - return rc; + if (st->prefix.insert_count > 0) { + rc = st->process_insert_count(st->data, + &st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } + + st->insert_count = st->prefix.insert_count; + + if (st->prefix.sign) { + if (st->insert_count <= st->prefix.delta_base) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + st->base = st->insert_count - st->prefix.delta_base - 1; + + } else { + st->base = st->insert_count + st->prefix.delta_base; + } } st->state = sw_field_rep; @@ -366,8 +372,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_http_v3_parse_start_local(b, &loc, st->length); - rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, - &loc); + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->base, &loc); ngx_http_v3_parse_end_local(b, &loc, &st->length); @@ -379,11 +384,20 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return rc; } + f = &st->field_rep.field; + + rc = st->process_header(st->data, f->has_name ? &f->name : NULL, + f->has_value ? &f->value : NULL, + f->index, f->dynamic); + if (rc != NGX_OK) { + return rc; + } + if (st->length == 0) { goto done; } - return NGX_OK; + break; } } @@ -391,14 +405,6 @@ done: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); - if (st->prefix.insert_count > 0) { - if (ngx_http_v3_send_ack_section(c, c->quic->stream->id) != NGX_OK) { - return NGX_ERROR; - } - - ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); - } - st->state = sw_start; return NGX_DONE; } @@ -468,27 +474,10 @@ ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, done: - rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); - if (rc != NGX_OK) { - return rc; - } - - if (st->sign) { - if (st->insert_count <= st->delta_base) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base"); - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - st->base = st->insert_count - st->delta_base - 1; - - } else { - st->base = st->insert_count + st->delta_base; - } - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field section prefix done " - "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", - st->insert_count, st->sign, st->delta_base, st->base); + "insert_count:%ui, sign:%ui, delta_base:%ui", + st->insert_count, st->sign, st->delta_base); st->state = sw_start; return NGX_DONE; @@ -521,9 +510,8 @@ ngx_http_v3_parse_field_rep(ngx_connection_t *c, ch = *b->pos; - ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); - st->field.base = base; + st->field.max_literal = st->max_literal; if (ch & 0x80) { /* Indexed Field Line */ @@ -594,9 +582,8 @@ static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, ngx_buf_t *b) { - u_char ch; - ngx_uint_t n; - ngx_http_core_srv_conf_t *cscf; + u_char ch; + ngx_uint_t n; enum { sw_start = 0, sw_value @@ -614,14 +601,6 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, n = st->length; - cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); - - if (n > cscf->large_client_header_buffers.size) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent too large field line"); - return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; - } - if (st->huffman) { if (n > NGX_MAX_INT_T_VALUE / 8) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -633,6 +612,7 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, st->huffstate = 0; } + /* XXX this allocation is bad in uni streams */ st->last = ngx_pnalloc(c->pool, n + 1); if (st->last == NULL) { return NGX_ERROR; @@ -656,8 +636,6 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, st->length == 1, c->log) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid encoded field line"); return NGX_ERROR; } @@ -734,16 +712,13 @@ done: "http3 parse field ri done %s%ui]", st->dynamic ? "dynamic[-" : "static[", st->index); + st->has_name = 0; + st->has_value = 0; + if (st->dynamic) { st->index = st->base - st->index - 1; } - rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, - &st->value); - if (rc != NGX_OK) { - return rc; - } - st->state = sw_start; return NGX_DONE; } @@ -820,6 +795,11 @@ ngx_http_v3_parse_field_lri(ngx_connection_t *c, goto done; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field value too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_value; break; @@ -842,15 +822,13 @@ done: st->dynamic ? "dynamic[-" : "static[", st->index, &st->value); + st->has_name = 0; + st->has_value = 1; + if (st->dynamic) { st->index = st->base - st->index - 1; } - rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); - if (rc != NGX_OK) { - return rc; - } - st->state = sw_start; return NGX_DONE; } @@ -903,6 +881,11 @@ ngx_http_v3_parse_field_l(ngx_connection_t *c, return NGX_ERROR; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field name too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_name; break; @@ -943,6 +926,11 @@ ngx_http_v3_parse_field_l(ngx_connection_t *c, goto done; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field value too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_value; break; @@ -964,6 +952,11 @@ done: "http3 parse field l done \"%V\" \"%V\"", &st->name, &st->value); + st->has_name = 1; + st->has_value = 1; + st->index = 0; + st->dynamic = 0; + st->state = sw_start; return NGX_DONE; } @@ -1009,11 +1002,10 @@ done: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field pbi done dynamic[+%ui]", st->index); - rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, - &st->value); - if (rc != NGX_OK) { - return rc; - } + st->has_name = 0; + st->has_value = 0; + st->index += st->base; + st->dynamic = 1; st->state = sw_start; return NGX_DONE; @@ -1084,6 +1076,11 @@ ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, goto done; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field value too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_value; break; @@ -1105,61 +1102,17 @@ done: "http3 parse field lpbi done dynamic[+%ui] \"%V\"", st->index, &st->value); - rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); - if (rc != NGX_OK) { - return rc; - } + st->has_name = 0; + st->has_value = 1; + st->index += st->base; + st->dynamic = 1; st->state = sw_start; return NGX_DONE; } -static ngx_int_t -ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) -{ - u_char *p; - - if (!dynamic) { - if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) { - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - return NGX_OK; - } - - if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - if (name) { - p = ngx_pnalloc(c->pool, name->len + 1); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, name->data, name->len); - p[name->len] = '\0'; - name->data = p; - } - - if (value) { - p = ngx_pnalloc(c->pool, value->len + 1); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, value->data, value->len); - p[value->len] = '\0'; - value->data = p; - } - - return NGX_OK; -} - - -static ngx_int_t +ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, ngx_buf_t *b) { @@ -1246,6 +1199,9 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, switch (st->type) { case NGX_HTTP_V3_FRAME_SETTINGS: + st->settings.set_param = st->set_param; + st->settings.data = st->data; + st->state = sw_settings; break; @@ -1335,7 +1291,7 @@ ngx_http_v3_parse_settings(ngx_connection_t *c, return rc; } - if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + if (st->set_param(st->data, st->id, st->vlint.value) != NGX_OK) { return NGX_HTTP_V3_ERR_SETTINGS_ERROR; } @@ -1352,7 +1308,7 @@ done: } -static ngx_int_t +ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b) { @@ -1377,6 +1333,8 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, return NGX_AGAIN; } + st->field.max_literal = st->max_literal; + ch = *b->pos; if (ch & 0x80) { @@ -1410,6 +1368,12 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, return rc; } + rc = st->ref_insert(st->data, st->field.dynamic, st->field.index, + &st->field.value); + if (rc != NGX_OK) { + return rc; + } + st->state = sw_start; break; @@ -1420,6 +1384,11 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, return rc; } + rc = st->insert(st->data, &st->field.name, &st->field.value); + if (rc != NGX_OK) { + return rc; + } + st->state = sw_start; break; @@ -1430,7 +1399,7 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, return rc; } - rc = ngx_http_v3_set_capacity(c, st->pint.value); + rc = st->set_capacity(st->data, st->pint.value); if (rc != NGX_OK) { return rc; } @@ -1445,7 +1414,7 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, return rc; } - rc = ngx_http_v3_duplicate(c, st->pint.value); + rc = st->duplicate(st->data, st->pint.value); if (rc != NGX_OK) { return rc; } @@ -1528,6 +1497,11 @@ ngx_http_v3_parse_field_inr(ngx_connection_t *c, goto done; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field value too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_value; break; @@ -1550,11 +1524,6 @@ done: st->dynamic ? "dynamic" : "static", st->index, &st->value); - rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); - if (rc != NGX_OK) { - return rc; - } - st->state = sw_start; return NGX_DONE; } @@ -1607,6 +1576,11 @@ ngx_http_v3_parse_field_iln(ngx_connection_t *c, return NGX_ERROR; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field name too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_name; break; @@ -1647,6 +1621,11 @@ ngx_http_v3_parse_field_iln(ngx_connection_t *c, goto done; } + if (st->literal.length > st->max_literal) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "field value too large"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + st->state = sw_value; break; @@ -1668,17 +1647,12 @@ done: "http3 parse field iln done \"%V\":\"%V\"", &st->name, &st->value); - rc = ngx_http_v3_insert(c, &st->name, &st->value); - if (rc != NGX_OK) { - return rc; - } - st->state = sw_start; return NGX_DONE; } -static ngx_int_t +ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b) { @@ -1730,7 +1704,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, return rc; } - rc = ngx_http_v3_ack_section(c, st->pint.value); + rc = st->ack_section(st->data, st->pint.value); if (rc != NGX_OK) { return rc; } @@ -1745,7 +1719,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, return rc; } - rc = ngx_http_v3_cancel_stream(c, st->pint.value); + rc = st->cancel_stream(st->data, st->pint.value); if (rc != NGX_OK) { return rc; } @@ -1760,7 +1734,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, return rc; } - rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + rc = st->inc_insert_count(st->data, st->pint.value); if (rc != NGX_OK) { return rc; } @@ -1806,7 +1780,8 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, st->type = st->vlint.value; if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { - /* trailers */ + /* parse trailers later */ + b->pos--; goto done; } @@ -1863,80 +1838,3 @@ done: st->state = sw_start; return NGX_DONE; } - - -ngx_int_t -ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, - ngx_buf_t *b) -{ - ngx_int_t rc; - enum { - sw_start = 0, - sw_type, - sw_control, - sw_encoder, - sw_decoder, - sw_unknown - }; - - for ( ;; ) { - - switch (st->state) { - case sw_start: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); - - st->state = sw_type; - - /* fall through */ - - case sw_type: - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - switch (st->vlint.value) { - case NGX_HTTP_V3_STREAM_CONTROL: - st->state = sw_control; - break; - - case NGX_HTTP_V3_STREAM_ENCODER: - st->state = sw_encoder; - break; - - case NGX_HTTP_V3_STREAM_DECODER: - st->state = sw_decoder; - break; - - default: - st->state = sw_unknown; - } - - break; - - case sw_control: - - return ngx_http_v3_parse_control(c, &st->u.control, b); - - case sw_encoder: - - return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); - - case sw_decoder: - - return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); - - case sw_unknown: - - b->pos = b->last; - return NGX_AGAIN; - } - } -} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index ba004db5d..af484093e 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -14,6 +14,31 @@ #include +typedef ngx_int_t (*ngx_http_v3_process_insert_count_pt)(void *data, + ngx_uint_t *insert_count); +typedef ngx_int_t (*ngx_http_v3_process_header_pt)(void *data, ngx_str_t *name, + ngx_str_t *value, ngx_uint_t index, ngx_uint_t dynamic); + +typedef ngx_int_t (*ngx_http_v3_set_param_pt)(void *data, uint64_t id, + uint64_t value); + +typedef ngx_int_t (*ngx_http_v3_ref_insert_pt)(void *data, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +typedef ngx_int_t (*ngx_http_v3_insert_pt)(void *data, ngx_str_t *name, + ngx_str_t *value); +typedef ngx_int_t (*ngx_http_v3_duplicate_pt)(void *data, ngx_uint_t index); +typedef ngx_int_t (*ngx_http_v3_set_capacity_pt)(void *data, + ngx_uint_t capacity); + + +typedef ngx_int_t (*ngx_http_v3_ack_section_pt)(void *data, + ngx_uint_t stream_id); +typedef ngx_int_t (*ngx_http_v3_cancel_stream_pt)(void *data, + ngx_uint_t stream_id); +typedef ngx_int_t (*ngx_http_v3_inc_insert_count_pt)(void *data, + ngx_uint_t inc); + + typedef struct { ngx_uint_t state; uint64_t value; @@ -31,6 +56,9 @@ typedef struct { ngx_uint_t state; uint64_t id; ngx_http_v3_parse_varlen_int_t vlint; + + ngx_http_v3_set_param_pt set_param; + void *data; } ngx_http_v3_parse_settings_t; @@ -59,18 +87,23 @@ typedef struct { ngx_uint_t index; ngx_uint_t base; ngx_uint_t dynamic; + ngx_uint_t max_literal; ngx_str_t name; ngx_str_t value; ngx_http_v3_parse_prefix_int_t pint; ngx_http_v3_parse_literal_t literal; + + unsigned has_name:1; + unsigned has_value:1; } ngx_http_v3_parse_field_t; typedef struct { ngx_uint_t state; ngx_http_v3_parse_field_t field; + ngx_uint_t max_literal; } ngx_http_v3_parse_field_rep_t; @@ -78,22 +111,44 @@ typedef struct { ngx_uint_t state; ngx_uint_t type; ngx_uint_t length; + ngx_uint_t base; + ngx_uint_t insert_count; ngx_http_v3_parse_varlen_int_t vlint; ngx_http_v3_parse_field_section_prefix_t prefix; ngx_http_v3_parse_field_rep_t field_rep; + ngx_uint_t max_literal; + + ngx_http_v3_process_insert_count_pt + process_insert_count; + ngx_http_v3_process_header_pt process_header; + void *data; } ngx_http_v3_parse_headers_t; typedef struct { ngx_uint_t state; + ngx_uint_t max_literal; ngx_http_v3_parse_field_t field; ngx_http_v3_parse_prefix_int_t pint; + + ngx_http_v3_ref_insert_pt ref_insert; + ngx_http_v3_insert_pt insert; + ngx_http_v3_duplicate_pt duplicate; + ngx_http_v3_set_capacity_pt set_capacity; + void *data; } ngx_http_v3_parse_encoder_t; typedef struct { ngx_uint_t state; + ngx_uint_t max_literal; ngx_http_v3_parse_prefix_int_t pint; + + ngx_http_v3_ack_section_pt ack_section; + ngx_http_v3_cancel_stream_pt cancel_stream; + ngx_http_v3_inc_insert_count_pt + inc_insert_count; + void *data; } ngx_http_v3_parse_decoder_t; @@ -101,20 +156,13 @@ typedef struct { ngx_uint_t state; ngx_uint_t type; ngx_uint_t length; + ngx_uint_t max_literal; ngx_http_v3_parse_varlen_int_t vlint; ngx_http_v3_parse_settings_t settings; -} ngx_http_v3_parse_control_t; - -typedef struct { - ngx_uint_t state; - ngx_http_v3_parse_varlen_int_t vlint; - union { - ngx_http_v3_parse_encoder_t encoder; - ngx_http_v3_parse_decoder_t decoder; - ngx_http_v3_parse_control_t control; - } u; -} ngx_http_v3_parse_uni_t; + ngx_http_v3_set_param_pt set_param; + void *data; +} ngx_http_v3_parse_control_t; typedef struct { @@ -139,8 +187,15 @@ ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_buf_t *b); ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, ngx_buf_t *b); -ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, - ngx_http_v3_parse_uni_t *st, ngx_buf_t *b); + +ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, + ngx_http_v3_parse_control_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, + ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, + ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); #endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f8a89403b..325eeb2a2 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -11,7 +11,6 @@ static void ngx_http_v3_handshake_handler(ngx_connection_t *c); -static void ngx_http_v3_close_connection(ngx_connection_t *c); static ngx_int_t ngx_http_v3_init(ngx_connection_t *c); static void ngx_http_v3_handler(ngx_connection_t *c); static void ngx_http_v3_init_request_stream(ngx_connection_t *c); @@ -19,8 +18,10 @@ static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_connection(void *data); static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); -static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, - ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_process_header(void *data, + ngx_str_t *name, ngx_str_t *value, ngx_uint_t index, ngx_uint_t dynamic); +static ngx_int_t ngx_http_v3_process_insert_count(void *data, + ngx_uint_t *insert_count); static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, @@ -207,52 +208,38 @@ ngx_http_v3_handler(ngx_connection_t *c) } -static void -ngx_http_v3_close_connection(ngx_connection_t *c) -{ - ngx_pool_t *pool; - ngx_http_v3_session_t *h3c; - - if (ngx_quic_shutdown(c) == NGX_AGAIN) { - c->ssl->handler = ngx_http_v3_close_connection; - return; - } - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, -1); -#endif - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); -} - - static ngx_int_t ngx_http_v3_init(ngx_connection_t *c) { unsigned int len; const unsigned char *data; + ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_srv_conf_t *cscf; ngx_http_core_loc_conf_t *clcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + hc = c->data; + if (ngx_http_v3_init_session(c) != NGX_OK) { return NGX_ERROR; } h3c = ngx_http_v3_get_session(c); + h3c->data = hc; + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ngx_add_timer(c->read, clcf->keepalive_timeout); + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + h3c->max_literal = cscf->large_client_header_buffers.size; + h3c->max_table_capacity = h3scf->max_table_capacity; + h3c->max_blocked_streams = h3scf->max_blocked_streams; + if (h3scf->enable_hq) { if (!h3scf->enable) { h3c->hq = 1; @@ -380,14 +367,15 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) static void ngx_http_v3_wait_request_handler(ngx_event_t *rev) { - size_t size; - ssize_t n; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_pool_cleanup_t *cln; - ngx_http_request_t *r; - ngx_http_connection_t *hc; - ngx_http_core_srv_conf_t *cscf; + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_headers_t *st; c = rev->data; @@ -495,6 +483,13 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) r->v3_parse->header_limit = cscf->large_client_header_buffers.size * cscf->large_client_header_buffers.num; + st = &r->v3_parse->headers; + + st->max_literal = cscf->large_client_header_buffers.size; + st->process_insert_count = ngx_http_v3_process_insert_count; + st->process_header = ngx_http_v3_process_header; + st->data = r; + c->data = r; c->requests = (c->quic->stream->id >> 2) + 1; @@ -603,10 +598,10 @@ ngx_http_v3_process_request(ngx_event_t *rev) return; } - h3c = ngx_http_v3_get_session(c); - st = &r->v3_parse->headers; + h3c = ngx_http_v3_get_session(c); + b = r->header_in; for ( ;; ) { @@ -668,6 +663,10 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } + if (rc == NGX_ABORT) { + break; + } + r->request_length += b->pos - p; h3c->total_bytes += b->pos - p; @@ -695,24 +694,19 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } - if (rc == NGX_AGAIN) { - continue; - } - - /* rc == NGX_OK || rc == NGX_DONE */ + if (rc == NGX_DONE) { - h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, - st->field_rep.field.name.data, st->field_rep.field.name.len, - st->field_rep.field.value.data, st->field_rep.field.value.len); + if (st->insert_count > 0) { + if (ngx_http_v3_send_ack_section(c, c->quic->stream->id) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; + } - if (ngx_http_v3_process_header(r, &st->field_rep.field.name, - &st->field_rep.field.value) - != NGX_OK) - { - break; - } + ngx_http_v3_ack_insert_count(c, st->insert_count); + } - if (rc == NGX_DONE) { if (ngx_http_v3_process_request_header(r) != NGX_OK) { break; } @@ -720,6 +714,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) ngx_http_process_request(r); break; } + + /* rc == NGX_AGAIN */ } ngx_http_run_posted_requests(c); @@ -729,31 +725,83 @@ ngx_http_v3_process_request(ngx_event_t *rev) static ngx_int_t -ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, - ngx_str_t *value) +ngx_http_v3_process_header(void *data, ngx_str_t *name, ngx_str_t *value, + ngx_uint_t index, ngx_uint_t dynamic) { + ngx_http_request_t *r = data; + + u_char *p; size_t len; + ngx_str_t namet, valuet; ngx_table_elt_t *h; + ngx_connection_t *c; ngx_http_header_t *hh; + ngx_http_v3_session_t *h3c; ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; static ngx_str_t cookie = ngx_string("cookie"); + c = r->connection; + h3c = ngx_http_v3_get_session(c); + + if (name == NULL) { + + if (dynamic) { + + if (ngx_http_v3_lookup(c, index, &namet, &valuet) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + p = ngx_pstrdup(c->pool, &namet); + if (p == NULL) { + return NGX_ERROR; + } + + namet.data = p; + name = &namet; + + if (value == NULL) { + p = ngx_pstrdup(r->pool, &valuet); + if (p == NULL) { + return NGX_ERROR; + } + + valuet.data = p; + value = &valuet; + } + + } else { + if (ngx_http_v3_lookup_static(c, index, &namet, &valuet) != NGX_OK) + { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + name = &namet; + + if (value == NULL) { + value = &valuet; + } + } + } + + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + name->data, name->len, value->data, value->len); + len = name->len + value->len; if (len > r->v3_parse->header_limit) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent too large header"); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); - return NGX_ERROR; + return NGX_ABORT; } r->v3_parse->header_limit -= len; if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; + return NGX_ABORT; } if (r->invalid_header) { @@ -772,21 +820,19 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { - return NGX_ERROR; + return NGX_ABORT; } if (name->len == cookie.len && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) { if (ngx_http_v3_cookie(r, value) != NGX_OK) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } } else { h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } @@ -801,7 +847,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, h->lowcase_key, h->key.len); if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + return NGX_ABORT; } } @@ -811,6 +857,32 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } +static ngx_int_t +ngx_http_v3_process_insert_count(void *data, ngx_uint_t *insert_count) +{ + ngx_http_request_t *r = data; + + ngx_int_t rc; + ngx_uint_t n; + + n = *insert_count; + + rc = ngx_http_v3_decode_insert_count(r->connection, &n); + if (rc != NGX_OK) { + return rc; + } + + rc = ngx_http_v3_check_insert_count(r->connection, n); + if (rc != NGX_OK) { + return rc; + } + + *insert_count = n; + + return NGX_OK; +} + + static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) @@ -1012,7 +1084,7 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, failed: ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; + return NGX_ABORT; } diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index 428e7326b..7cf33c755 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -284,16 +284,14 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_uint_t max, prev_max; ngx_http_v3_field_t **elts; ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_dynamic_table_t *dt; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 set capacity %ui", capacity); h3c = ngx_http_v3_get_session(c); - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (capacity > h3scf->max_table_capacity) { + if (capacity > h3c->max_table_capacity) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_table_capacity limit"); return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; @@ -508,7 +506,6 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) { ngx_uint_t max_entries, full_range, max_value, max_wrapped, req_insert_count; - ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -521,9 +518,7 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) h3c = ngx_http_v3_get_session(c); dt = &h3c->table; - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - - max_entries = h3scf->max_table_capacity / 32; + max_entries = h3c->max_table_capacity / 32; full_range = 2 * max_entries; if (*insert_count > full_range) { @@ -563,7 +558,6 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) ngx_pool_cleanup_t *cln; ngx_http_v3_block_t *block; ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_dynamic_table_t *dt; h3c = ngx_http_v3_get_session(c); @@ -605,9 +599,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) } if (block->queue.prev == NULL) { - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - - if (h3c->nblocked == h3scf->max_blocked_streams) { + if (h3c->nblocked == h3c->max_blocked_streams) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_blocked_streams limit"); @@ -618,7 +610,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) } h3c->nblocked++; - ngx_queue_insert_tail(&h3c->blocked, &block->queue); + ngx_queue_insert_tail(&h3c->queue, &block->queue); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -669,8 +661,8 @@ ngx_http_v3_new_entry(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic entry, blocked:%ui", h3c->nblocked); - while (!ngx_queue_empty(&h3c->blocked)) { - q = ngx_queue_head(&h3c->blocked); + while (!ngx_queue_empty(&h3c->queue)) { + q = ngx_queue_head(&h3c->queue); block = (ngx_http_v3_block_t *) q; bc = block->connection; diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h index 1c2fb17b9..251480438 100644 --- a/src/http/v3/ngx_http_v3_table.h +++ b/src/http/v3/ngx_http_v3_table.h @@ -14,6 +14,26 @@ #include +/* static table indices */ +#define NGX_HTTP_V3_HEADER_AUTHORITY 0 +#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NGX_HTTP_V3_HEADER_DATE 6 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NGX_HTTP_V3_HEADER_LOCATION 12 +#define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_103 24 +#define NGX_HTTP_V3_HEADER_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NGX_HTTP_V3_HEADER_SERVER 92 +#define NGX_HTTP_V3_HEADER_USER_AGENT 95 + + typedef struct { ngx_str_t name; ngx_str_t value; diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index bc5f13624..a05e3e706 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -10,24 +10,38 @@ #include +typedef ngx_int_t (*ngx_http_v3_uni_parse_pt)(ngx_connection_t *c, + void *data, ngx_buf_t *b); + + typedef struct { - ngx_http_v3_parse_uni_t parse; - ngx_int_t index; + ngx_http_v3_uni_parse_pt parse; + void *data; + ngx_int_t index; } ngx_http_v3_uni_stream_t; +static ngx_int_t ngx_http_v3_uni_parse_type(ngx_connection_t *c, void *data, + ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_unknown(ngx_connection_t *c, void *data, + ngx_buf_t *b); static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, + uint64_t type); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); +static ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { - uint64_t n; - ngx_http_v3_session_t *h3c; - ngx_http_v3_uni_stream_t *us; + uint64_t n; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + ngx_http_v3_parse_varlen_int_t *st; h3c = ngx_http_v3_get_session(c); if (h3c->hq) { @@ -64,8 +78,20 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) us->index = -1; - c->data = us; + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_varlen_int_t)); + if (st == NULL) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "memory allocation error"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + us->parse = ngx_http_v3_uni_parse_type; + us->data = st; + + c->data = us; c->read->handler = ngx_http_v3_uni_read_handler; c->write->handler = ngx_http_v3_uni_dummy_write_handler; @@ -73,6 +99,107 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) } +static ngx_int_t +ngx_http_v3_uni_parse_type(ngx_connection_t *c, void *data, ngx_buf_t *b) +{ + ngx_http_v3_parse_varlen_int_t *st = data; + + ngx_int_t rc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + ngx_http_v3_parse_control_t *stc; + ngx_http_v3_parse_encoder_t *ste; + ngx_http_v3_parse_decoder_t *std; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); + + us = c->data; + + rc = ngx_http_v3_parse_varlen_int(c, st, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_register_uni_stream(c, st->value); + if (rc != NGX_OK) { + return rc; + } + + h3c = ngx_http_v3_get_session(c); + + switch (st->value) { + + case NGX_HTTP_V3_STREAM_CONTROL: + stc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_control_t)); + if (stc == NULL) { + return NGX_ERROR; + } + + stc->set_param = (ngx_http_v3_set_param_pt) ngx_http_v3_set_param; + stc->data = c; + stc->max_literal = h3c->max_literal; + + us->parse = (ngx_http_v3_uni_parse_pt) ngx_http_v3_parse_control; + us->data = stc; + + break; + + case NGX_HTTP_V3_STREAM_ENCODER: + ste = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_encoder_t)); + if (ste == NULL) { + return NGX_ERROR; + } + + ste->ref_insert = (ngx_http_v3_ref_insert_pt) ngx_http_v3_ref_insert; + ste->insert = (ngx_http_v3_insert_pt) ngx_http_v3_insert; + ste->duplicate = (ngx_http_v3_duplicate_pt) ngx_http_v3_duplicate; + ste->set_capacity = + (ngx_http_v3_set_capacity_pt) ngx_http_v3_set_capacity; + ste->data = c; + ste->max_literal = h3c->max_literal; + + us->parse = (ngx_http_v3_uni_parse_pt) ngx_http_v3_parse_encoder; + us->data = ste; + + break; + + case NGX_HTTP_V3_STREAM_DECODER: + std = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_decoder_t)); + if (std == NULL) { + return NGX_ERROR; + } + + std->ack_section = (ngx_http_v3_ack_section_pt) ngx_http_v3_ack_section; + std->cancel_stream = + (ngx_http_v3_cancel_stream_pt) ngx_http_v3_cancel_stream; + std->inc_insert_count = + (ngx_http_v3_inc_insert_count_pt) ngx_http_v3_inc_insert_count; + std->data = c; + std->max_literal = h3c->max_literal; + + us->parse = (ngx_http_v3_uni_parse_pt) ngx_http_v3_parse_decoder; + us->data = std; + + break; + + default: + us->parse = (ngx_http_v3_uni_parse_pt) ngx_http_v3_parse_unknown; + us->data = NULL; + } + + return us->parse(c, us->data, b); +} + + +static ngx_int_t +ngx_http_v3_parse_unknown(ngx_connection_t *c, void *data, ngx_buf_t *b) +{ + b->pos = b->last; + + return NGX_AGAIN; +} + + static void ngx_http_v3_close_uni_stream(ngx_connection_t *c) { @@ -99,7 +226,7 @@ ngx_http_v3_close_uni_stream(ngx_connection_t *c) } -ngx_int_t +static ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) { ngx_int_t index; @@ -222,7 +349,7 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) return; } - rc = ngx_http_v3_parse_uni(c, &us->parse, &b); + rc = us->parse(c, us->data, &b); if (rc == NGX_DONE) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -389,11 +516,10 @@ failed: ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c) { - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; - size_t n; - ngx_connection_t *cc; - ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -402,26 +528,25 @@ ngx_http_v3_send_settings(ngx_connection_t *c) return NGX_ERROR; } - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + h3c = ngx_http_v3_get_session(c); n = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); - n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); + n += ngx_http_v3_encode_varlen_int(NULL, h3c->max_table_capacity); n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); - n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); + n += ngx_http_v3_encode_varlen_int(NULL, h3c->max_blocked_streams); p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_SETTINGS); p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3c->max_table_capacity); p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3c->max_blocked_streams); n = p - buf; - h3c = ngx_http_v3_get_session(c); h3c->total_bytes += n; if (cc->send(cc, buf, n) != (ssize_t) n) { @@ -606,7 +731,7 @@ failed: } -ngx_int_t +static ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h index 280589469..b424509c6 100644 --- a/src/http/v3/ngx_http_v3_uni.h +++ b/src/http/v3/ngx_http_v3_uni.h @@ -15,9 +15,6 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c); -ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); - -ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); From 4fb5375866a846ff0e227bd7e0827d49bd4cb8bf Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 27 May 2025 13:15:10 +0400 Subject: [PATCH 13/13] HTTP/3: ngx_http_v3_proxy_module. The module allows to use HTTP/3 protocol for proxying. HTTP/3 proxying is enabled by specifying "proxy_http_version 3". Example: server { listen 8000; location / { proxy_http_version 3; proxy_pass https://127.0.0.1:8443; } } server { listen 8443 quic reuseport; ssl_certificate certs/example.com.crt; ssl_certificate_key certs/example.com.key; location / { return 200 foo; } } --- auto/modules | 11 + src/core/ngx_core.h | 1 + src/event/quic/ngx_event_quic.h | 6 +- src/event/quic/ngx_event_quic_streams.c | 113 +- src/http/modules/ngx_http_proxy_module.c | 64 + src/http/modules/ngx_http_proxy_module.h | 9 + src/http/ngx_http_upstream.c | 76 +- src/http/ngx_http_upstream.h | 6 + src/http/v3/ngx_http_v3_proxy_module.c | 1773 ++++++++++++++++++++++ src/http/v3/ngx_http_v3_table.h | 3 + 10 files changed, 2030 insertions(+), 32 deletions(-) create mode 100644 src/http/v3/ngx_http_v3_proxy_module.c diff --git a/auto/modules b/auto/modules index c199d89bf..0c40991f4 100644 --- a/auto/modules +++ b/auto/modules @@ -792,6 +792,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_PROXY = YES -a $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_proxy_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_proxy_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if [ $HTTP_PERL != NO ]; then ngx_module_name=ngx_http_perl_module ngx_module_incs=src/http/modules/perl diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index f6b944849..eeed2a260 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -31,6 +31,7 @@ typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; typedef struct ngx_udp_connection_s ngx_udp_connection_t; typedef struct ngx_quic_s ngx_quic_t; +typedef struct ngx_quic_conf_s ngx_quic_conf_t; typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 4451bb1ba..ed2f55c76 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -78,7 +78,7 @@ typedef struct { } ngx_quic_buffer_t; -typedef struct { +struct ngx_quic_conf_s { ngx_ssl_t *ssl; ngx_flag_t retry; @@ -96,7 +96,7 @@ typedef struct { u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; -} ngx_quic_conf_t; +}; typedef struct { @@ -134,6 +134,8 @@ ngx_int_t ngx_quic_create_connection(ngx_quic_conf_t *conf, ngx_connection_t *c, ngx_uint_t flags); ngx_int_t ngx_quic_handshake(ngx_connection_t *c); ngx_connection_t *ngx_quic_accept_stream(ngx_connection_t *c); +ngx_int_t ngx_quic_has_streams(ngx_connection_t *c, ngx_uint_t local, + ngx_uint_t bidi); ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); void ngx_quic_set_app_error(ngx_connection_t *c, ngx_uint_t err, const char *reason); diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 2319f9cf0..da3cdb07f 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -57,6 +57,50 @@ ngx_quic_is_stream_local(ngx_connection_t *c, uint64_t id) } +ngx_int_t +ngx_quic_has_streams(ngx_connection_t *c, ngx_uint_t local, ngx_uint_t bidi) +{ + uint64_t type; + 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); + + /* TODO optimize */ + + type = 0; + + if ((qc->is_server && local) || (!qc->is_server && !local)) { + type |= NGX_QUIC_STREAM_SERVER_INITIATED; + } + + if (!bidi) { + type |= NGX_QUIC_STREAM_UNIDIRECTIONAL; + } + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_DECLINED; + } + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + if ((qs->id & 0x03) == type) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { @@ -925,44 +969,67 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ssize_t -ngx_quic_stream_recv_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +ngx_quic_stream_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit) { - size_t len, size; - ssize_t n; + u_char *last; + ssize_t n, bytes, size; + ngx_buf_t *b; /* TODO optimize */ - len = 0; + bytes = 0; - while (in && limit) { - size = in->buf->last - in->buf->pos; - if ((off_t) size > limit) { - size = limit; - } + b = cl->buf; + last = b->last; - n = ngx_quic_stream_recv(c, in->buf->pos, size); + for ( ;; ) { + size = b->end - last; - if (n == NGX_ERROR) { - return NGX_ERROR; + if (limit) { + if (bytes >= limit) { + return bytes; + } + + if (bytes + size > limit) { + size = (ssize_t) (limit - bytes); + } } - if (n == NGX_AGAIN) { - break; + n = ngx_quic_stream_recv(c, last, size); + + if (n > 0) { + last += n; + bytes += n; + + if (!c->read->ready) { + return bytes; + } + + if (last == b->end) { + cl = cl->next; + + if (cl == NULL) { + return bytes; + } + + b = cl->buf; + last = b->last; + } + + continue; } - in->buf->pos += n; - limit -= n; + if (bytes) { + + if (n == 0 || n == NGX_ERROR) { + c->read->ready = 1; + } - if (in->buf->pos == in->buf->last) { - in = in->next; + return bytes; } - } - if (len == 0 && in) { - return NGX_AGAIN; + return n; } - - return len; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 7897b3f4b..ea09fcf61 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -10,6 +10,10 @@ #include #include +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif + #define NGX_HTTP_PROXY_COOKIE_SECURE 0x0001 #define NGX_HTTP_PROXY_COOKIE_SECURE_ON 0x0002 @@ -200,6 +204,9 @@ static ngx_conf_enum_t ngx_http_proxy_http_version[] = { { ngx_string("1.1"), NGX_HTTP_VERSION_11 }, #if (NGX_HTTP_V2) { ngx_string("2"), NGX_HTTP_VERSION_20 }, +#endif +#if (NGX_HTTP_V3) + { ngx_string("3"), NGX_HTTP_VERSION_30 }, #endif { ngx_null_string, 0 } }; @@ -706,6 +713,24 @@ static ngx_command_t ngx_http_proxy_commands[] = { offsetof(ngx_http_proxy_loc_conf_t, ssl_conf_commands), &ngx_http_proxy_ssl_conf_command_post }, +#endif + +#if (NGX_QUIC) + + { ngx_string("proxy_quic_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, quic_idle_timeout), + NULL }, + + { ngx_string("proxy_quic_stream_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, quic_stream_buffer_size), + NULL }, + #endif ngx_null_command @@ -886,6 +911,12 @@ ngx_http_proxy_handler(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (plcf->http_version == NGX_HTTP_VERSION_30) { + return ngx_http_v3_proxy_handler(r); + } +#endif + if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -3605,6 +3636,11 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif +#if (NGX_QUIC) + conf->quic_idle_timeout = NGX_CONF_UNSET_MSEC; + conf->quic_stream_buffer_size = NGX_CONF_UNSET_SIZE; +#endif + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; conf->upstream.change_buffering = 1; @@ -3968,6 +4004,28 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } +#endif + +#if (NGX_QUIC) + + ngx_conf_merge_msec_value(conf->quic_idle_timeout, + prev->quic_idle_timeout, 60000); + ngx_conf_merge_size_value(conf->quic_stream_buffer_size, + prev->quic_stream_buffer_size, 65536); + + conf->upstream.quic = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); + if (conf->upstream.quic == NULL) { + return NGX_CONF_ERROR; + } + + conf->upstream.quic->max_concurrent_streams_bidi = 0; + conf->upstream.quic->max_concurrent_streams_uni = 3; + conf->upstream.quic->stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + conf->upstream.quic->active_connection_id_limit = 2; + conf->upstream.quic->stream_buffer_size = conf->quic_stream_buffer_size; + conf->upstream.quic->idle_timeout = conf->quic_idle_timeout; + conf->upstream.quic->ssl = conf->upstream.ssl; + #endif ngx_conf_merge_ptr_value(conf->method, prev->method, NULL); @@ -5374,6 +5432,12 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) return NGX_ERROR; } +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_quic_compat_init(cf, plcf->upstream.ssl->ctx) != NGX_OK) { + return NGX_ERROR; + } +#endif + return NGX_OK; } diff --git a/src/http/modules/ngx_http_proxy_module.h b/src/http/modules/ngx_http_proxy_module.h index a0f79bc13..77de83e0d 100644 --- a/src/http/modules/ngx_http_proxy_module.h +++ b/src/http/modules/ngx_http_proxy_module.h @@ -85,6 +85,11 @@ typedef struct { ngx_str_t ssl_crl; ngx_array_t *ssl_conf_commands; #endif + +#if (NGX_QUIC || NGX_COMPAT) + ngx_msec_t quic_idle_timeout; + size_t quic_stream_buffer_size; +#endif } ngx_http_proxy_loc_conf_t; @@ -120,6 +125,10 @@ ngx_int_t ngx_http_proxy_rewrite_cookie(ngx_http_request_t *r, ngx_int_t ngx_http_proxy_v2_handler(ngx_http_request_t *r); #endif +#if (NGX_HTTP_V3) +ngx_int_t ngx_http_v3_proxy_handler(ngx_http_request_t *r); +#endif + extern ngx_module_t ngx_http_proxy_module; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 2e4b2b48f..4023811f2 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -1658,6 +1658,16 @@ ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) } } + c->sockaddr = ngx_palloc(c->pool, u->peer.socklen); + if (c->sockaddr == NULL) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + ngx_memcpy(c->sockaddr, u->peer.sockaddr, u->peer.socklen); + c->socklen = u->peer.socklen; + c->log = r->connection->log; c->pool->log = c->log; c->read->log = c->log; @@ -1742,10 +1752,18 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, return; } - if (ngx_ssl_create_connection(u->conf->ssl, c, - NGX_SSL_BUFFER|NGX_SSL_CLIENT) - != NGX_OK) +#if (NGX_QUIC) + if (u->quic) { + rc = ngx_quic_create_connection(u->conf->quic, c, NGX_SSL_CLIENT); + + } else +#endif { + rc = ngx_ssl_create_connection(u->conf->ssl, c, + NGX_SSL_BUFFER|NGX_SSL_CLIENT); + } + + if (rc != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); return; @@ -1808,9 +1826,19 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, } } - r->connection->log->action = "SSL handshaking to upstream"; +#if (NGX_QUIC) + if (u->quic) { + r->connection->log->action = "QUIC handshaking to upstream"; + + rc = ngx_quic_handshake(c); + + } else +#endif + { + r->connection->log->action = "SSL handshaking to upstream"; - rc = ngx_ssl_handshake(c); + rc = ngx_ssl_handshake(c); + } if (rc == NGX_AGAIN) { @@ -1875,6 +1903,14 @@ ngx_http_upstream_ssl_handshake(ngx_http_request_t *r, ngx_http_upstream_t *u, } } + if (u->create_stream) { + if (u->create_stream(r) != NGX_OK) { + goto failed; + } + + c = u->peer.connection; + } + if (!c->ssl->sendfile) { c->sendfile = 0; u->output.sendfile = 0; @@ -2232,6 +2268,16 @@ ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, u->write_event_handler = ngx_http_upstream_dummy_handler; } +#if (NGX_QUIC) + if (u->quic) { + if (ngx_quic_shutdown_stream(c, NGX_WRITE_SHUTDOWN) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } +#endif + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -4694,7 +4740,15 @@ ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, u->peer.connection->ssl->no_wait_shutdown = 1; u->peer.connection->ssl->no_send_shutdown = 1; - (void) ngx_ssl_shutdown(u->peer.connection); +#if (NGX_QUIC) + if (u->quic) { + (void) ngx_quic_shutdown(u->peer.connection); + + } else +#endif + { + (void) ngx_ssl_shutdown(u->peer.connection); + } } #endif @@ -4782,7 +4836,15 @@ ngx_http_upstream_finalize_request(ngx_http_request_t *r, u->peer.connection->ssl->no_wait_shutdown = 1; - (void) ngx_ssl_shutdown(u->peer.connection); +#if (NGX_QUIC) + if (u->quic) { + (void) ngx_quic_shutdown(u->peer.connection); + + } else +#endif + { + (void) ngx_ssl_shutdown(u->peer.connection); + } } #endif diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 3afe6e8f9..dc6b20e40 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -251,6 +251,10 @@ typedef struct { ngx_array_t *ssl_passwords; #endif +#if (NGX_QUIC || NGX_COMPAT) + ngx_quic_conf_t *quic; +#endif + ngx_str_t module; NGX_COMPAT_BEGIN(6) @@ -370,6 +374,7 @@ struct ngx_http_upstream_s { ngx_int_t (*create_key)(ngx_http_request_t *r); #endif ngx_int_t (*create_request)(ngx_http_request_t *r); + ngx_int_t (*create_stream)(ngx_http_request_t *r); ngx_int_t (*reinit_request)(ngx_http_request_t *r); ngx_int_t (*process_header)(ngx_http_request_t *r); void (*abort_request)(ngx_http_request_t *r); @@ -399,6 +404,7 @@ struct ngx_http_upstream_s { unsigned cacheable:1; unsigned accel:1; unsigned ssl:1; + unsigned quic:1; #if (NGX_HTTP_CACHE) unsigned cache_status:3; #endif diff --git a/src/http/v3/ngx_http_v3_proxy_module.c b/src/http/v3/ngx_http_v3_proxy_module.c new file mode 100644 index 000000000..71e5abc38 --- /dev/null +++ b/src/http/v3/ngx_http_v3_proxy_module.c @@ -0,0 +1,1773 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +typedef struct { + ngx_http_proxy_ctx_t ctx; + ngx_http_v3_parse_headers_t parse_headers; + ngx_http_v3_parse_data_t parse_data; + off_t body_received; + ngx_uint_t pseudo_done; /* unsigned pseudo_done:1; */ +} ngx_http_v3_proxy_ctx_t; + + +static ngx_int_t ngx_http_v3_proxy_create_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_proxy_create_stream(ngx_http_request_t *r); +static void ngx_http_v3_proxy_quic_handler(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_proxy_handle_quic_connection(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_proxy_reinit_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_proxy_body_output_filter(void *data, + ngx_chain_t *in); +static ngx_int_t ngx_http_v3_proxy_process_response(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_proxy_process_header(void *data, + ngx_str_t *name, ngx_str_t *value, ngx_uint_t index, ngx_uint_t dynamic); +static ngx_int_t ngx_http_v3_proxy_process_insert_count(void *data, + ngx_uint_t *insert_count); +static ngx_int_t ngx_http_v3_proxy_input_filter_init(void *data); +static ngx_int_t ngx_http_v3_proxy_body_filter(ngx_event_pipe_t *p, + ngx_buf_t *buf); +static ngx_int_t ngx_http_v3_proxy_non_buffered_body_filter(void *data, + ssize_t bytes); +static ngx_int_t ngx_http_v3_proxy_process_trailer(ngx_http_request_t *r, + ngx_buf_t *buf); +static void ngx_http_v3_proxy_abort_request(ngx_http_request_t *r); +static void ngx_http_v3_proxy_finalize_request(ngx_http_request_t *r, + ngx_int_t rc); + + +ngx_module_t ngx_http_v3_proxy_module; + + +static ngx_http_module_t ngx_http_v3_proxy_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_proxy_module = { + NGX_MODULE_V1, + &ngx_http_v3_proxy_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_int_t +ngx_http_v3_proxy_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; +#if (NGX_HTTP_CACHE) + ngx_http_proxy_main_conf_t *pmcf; +#endif + ngx_http_v3_parse_headers_t *st; + + if (ngx_http_upstream_create(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + if (!plcf->ssl) { + ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, + "unsupported \"http\" scheme"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_proxy_ctx_t)); + if (ctx == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + st = &ctx->parse_headers; + st->max_literal = plcf->upstream.buffer_size; + st->process_insert_count = ngx_http_v3_proxy_process_insert_count; + st->process_header = ngx_http_v3_proxy_process_header; + st->data = r; + + ngx_http_set_ctx(r, ctx, ngx_http_v3_proxy_module); + + ngx_http_set_ctx(r, &ctx->ctx, ngx_http_proxy_module); + + u = r->upstream; + + if (plcf->proxy_lengths == NULL) { + ctx->ctx.vars = plcf->vars; + u->schema = plcf->vars.schema; + u->ssl = 1; + + } else { + if (ngx_http_proxy_eval(r, &ctx->ctx, plcf) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!u->ssl) { + ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, + "unsupported \"http\" scheme"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + ngx_str_set(&u->ssl_alpn_protocol, NGX_HTTP_V3_ALPN_PROTO); + + u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module; + + u->conf = &plcf->upstream; + +#if (NGX_HTTP_CACHE) + pmcf = ngx_http_get_module_main_conf(r, ngx_http_proxy_module); + + u->caches = &pmcf->caches; + u->create_key = ngx_http_proxy_create_key; +#endif + + u->create_request = ngx_http_v3_proxy_create_request; + u->create_stream = ngx_http_v3_proxy_create_stream; + u->reinit_request = ngx_http_v3_proxy_reinit_request; + u->process_header = ngx_http_v3_proxy_process_response; + u->abort_request = ngx_http_v3_proxy_abort_request; + u->finalize_request = ngx_http_v3_proxy_finalize_request; + r->state = 0; + + if (plcf->redirects) { + u->rewrite_redirect = ngx_http_proxy_rewrite_redirect; + } + + if (plcf->cookie_domains || plcf->cookie_paths || plcf->cookie_flags) { + u->rewrite_cookie = ngx_http_proxy_rewrite_cookie; + } + + u->buffering = plcf->upstream.buffering; + + u->pipe = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t)); + if (u->pipe == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + u->pipe->input_filter = ngx_http_v3_proxy_body_filter; + u->pipe->input_ctx = r; + + u->input_filter_init = ngx_http_v3_proxy_input_filter_init; + u->input_filter = ngx_http_v3_proxy_non_buffered_body_filter; + u->input_filter_ctx = r; + + u->accel = 1; + u->quic = 1; + u->peer.type = SOCK_DGRAM; + + if (!plcf->upstream.request_buffering + && plcf->body_values == NULL && plcf->upstream.pass_request_body) + { + r->request_body_no_buffering = 1; + } + + rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_proxy_create_request(ngx_http_request_t *r) +{ + u_char *p, *key, *val; + size_t len, uri_len, loc_len, header_len, body_len, + key_len, val_len; + uintptr_t escape; + ngx_buf_t *b; + ngx_str_t method, *host; + ngx_uint_t i, unparsed_uri, internal_chunked; + ngx_chain_t *cl, *body; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_script_code_pt code; + ngx_http_proxy_headers_t *headers; + ngx_http_script_engine_t e, le; + ngx_http_proxy_loc_conf_t *plcf; + ngx_http_script_len_code_pt lcode; + + u = r->upstream; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + +#if (NGX_HTTP_CACHE) + headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; +#else + headers = &plcf->headers; +#endif + + if (u->method.len) { + /* HEAD was changed to GET to cache response */ + method = u->method; + + } else if (plcf->method) { + if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) { + return NGX_ERROR; + } + + } else { + method = r->method_name; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + /* :method header */ + + if (method.len == 4 + && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + ctx->ctx.head = 1; + + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_HEAD); + + } else if (method.len == sizeof("GET") - 1 + && ngx_strncasecmp(method.data, (u_char *) "GET", 3) == 0) + { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + } else if (method.len == sizeof("POST") - 1 + && ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0) + { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_POST); + + } else if (method.len == sizeof("PUT") - 1 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0) + { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_PUT); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET, + NULL, method.len); + } + + /* :scheme header */ + + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + /* :path header */ + + escape = 0; + loc_len = 0; + unparsed_uri = 0; + internal_chunked = 0; + + if (plcf->proxy_lengths && ctx->ctx.vars.uri.len) { + uri_len = ctx->ctx.vars.uri.len; + + } else if (ctx->ctx.vars.uri.len == 0 && r->valid_unparsed_uri) { + unparsed_uri = 1; + uri_len = r->unparsed_uri.len; + + } else { + loc_len = (r->valid_location && ctx->ctx.vars.uri.len) + ? ngx_min(plcf->location.len, r->uri.len) : 0; + + if (r->quoted_uri || r->internal) { + escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, + r->uri.len - loc_len, NGX_ESCAPE_URI); + } + + uri_len = ctx->ctx.vars.uri.len + r->uri.len - loc_len + escape + + sizeof("?") - 1 + r->args.len; + } + + if (uri_len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "zero length URI to proxy"); + return NGX_ERROR; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, uri_len); + + /* :authority header */ + + host = &ctx->ctx.vars.host_header; + + if (!plcf->host_set) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, host->len); + } + + /* other headers */ + + ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); + + ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); + ngx_http_script_flush_no_cacheable_variables(r, headers->flushes); + + body_len = 0; + + if (plcf->body_lengths) { + le.ip = plcf->body_lengths->elts; + le.request = r; + le.flushed = 1; + + while (*(uintptr_t *) le.ip) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + body_len += lcode(&le); + } + + ctx->ctx.internal_body_length = body_len; + + len += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA); + len += ngx_http_v3_encode_varlen_int(NULL, body_len); + + } else if (r->headers_in.chunked && r->reading_body) { + ctx->ctx.internal_body_length = -1; + internal_chunked = 1; + + } else { + ctx->ctx.internal_body_length = r->headers_in.content_length_n; + + len += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA); + len += ngx_http_v3_encode_varlen_int(NULL, + ctx->ctx.internal_body_length); + } + + + le.ip = headers->lengths->elts; + le.request = r; + le.flushed = 1; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, NULL, key_len, NULL, val_len) + * 2; + } + + if (plcf->upstream.pass_request_headers) { + part = &r->headers_in.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, NULL, header[i].key.len, + NULL, header[i].value.len); + } + } + + header_len = len; + + len += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, header_len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + + b->last += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, header_len); + + p = b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + if (method.len == 4 + && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_HEAD); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: HEAD\""); + + } else if (method.len == sizeof("GET") - 1 + && ngx_strncasecmp(method.data, (u_char *) "GET", 3) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: GET\""); + + } else if (method.len == sizeof("POST") - 1 + && ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_POST); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: POST\""); + + } else if (method.len == sizeof("PUT") - 1 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_PUT); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: PUT\""); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET, + method.data, method.len); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":method: %V\"", &method); + } + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":scheme: https\""); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, uri_len); + u->uri.data = b->last; + + if (plcf->proxy_lengths && ctx->ctx.vars.uri.len) { + b->last = ngx_copy(b->last, ctx->ctx.vars.uri.data, + ctx->ctx.vars.uri.len); + + } else if (unparsed_uri) { + b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len); + + } else { + if (r->valid_location) { + b->last = ngx_copy(b->last, ctx->ctx.vars.uri.data, + ctx->ctx.vars.uri.len); + } + + if (escape) { + ngx_escape_uri(b->last, r->uri.data + loc_len, + r->uri.len - loc_len, NGX_ESCAPE_URI); + b->last += r->uri.len - loc_len + escape; + + } else { + b->last = ngx_copy(b->last, r->uri.data + loc_len, + r->uri.len - loc_len); + } + + if (r->args.len > 0) { + *b->last++ = '?'; + b->last = ngx_copy(b->last, r->args.data, r->args.len); + } + } + + u->uri.len = b->last - u->uri.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":path: %V\"", &u->uri); + + if (!plcf->host_set) { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + host->data, host->len); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \":authority: %V\"", host); + } + + ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); + + e.ip = headers->values->elts; + e.pos = b->last; + e.request = r; + e.flushed = 1; + + le.ip = headers->lengths->elts; + + while (*(uintptr_t *) le.ip) { + + lcode = *(ngx_http_script_len_code_pt *) le.ip; + key_len = lcode(&le); + + for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { + lcode = *(ngx_http_script_len_code_pt *) le.ip; + } + le.ip += sizeof(uintptr_t); + + if (val_len == 0) { + e.skip = 1; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + e.skip = 0; + + continue; + } + + e.pos += ngx_http_v3_encode_field_l(NULL, NULL, key_len, NULL, val_len); + key = e.pos; + + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + + val = e.pos; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + e.ip += sizeof(uintptr_t); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, key, key_len, + val, val_len); + e.pos = b->last; + } + + b->last = e.pos; + + + if (plcf->upstream.pass_request_headers) { + part = &r->headers_in.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + header[i].key.data, header[i].key.len, + header[i].value.data, header[i].value.len); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy header: \"%V: %V\"", + &header[i].key, &header[i].value); + } + } + + len = b->last - p; + b->pos += (ngx_http_v3_encode_varlen_int(NULL, header_len) + - ngx_http_v3_encode_varlen_int(NULL, len)); + p = (u_char *) ngx_http_v3_encode_varlen_int(b->pos, + NGX_HTTP_V3_FRAME_HEADERS); + (void) ngx_http_v3_encode_varlen_int(p, len); + + if (plcf->body_values) { + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, body_len); + + e.ip = plcf->body_values->elts; + e.pos = b->last; + e.skip = 0; + + while (*(uintptr_t *) e.ip) { + code = *(ngx_http_script_code_pt *) e.ip; + code((ngx_http_script_engine_t *) &e); + } + + b->last = e.pos; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy header:%N\"%*xs\"", + (size_t) (b->last - b->pos), b->pos); + + if (r->request_body_no_buffering) { + + u->request_bufs = cl; + + if (internal_chunked) { + u->output.output_filter = ngx_http_v3_proxy_body_output_filter; + u->output.filter_ctx = r; + + } else { + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + ctx->ctx.internal_body_length); + } + + } else if (plcf->body_values == NULL && plcf->upstream.pass_request_body) { + + body = u->request_bufs; + body_len = 0; + + while (body) { + body_len += ngx_buf_size(body->buf); + body = body->next; + } + + body = u->request_bufs; + u->request_bufs = cl; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, body_len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, body_len); + + cl->next = ngx_alloc_chain_link(r->pool); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + cl->buf = b; + + while (body) { + b = ngx_alloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); + + cl->next = ngx_alloc_chain_link(r->pool); + if (cl->next == NULL) { + return NGX_ERROR; + } + + cl = cl->next; + cl->buf = b; + + body = body->next; + } + + } else { + u->request_bufs = cl; + } + + b->flush = 1; + cl->next = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_create_stream(ngx_http_request_t *r) +{ + ngx_log_t *log; + ngx_connection_t *c, *sc; + ngx_http_upstream_t *u; + ngx_http_v3_session_t *h3c; + + u = r->upstream; + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 proxy create stream"); + + c->ssl->handler = ngx_http_v3_proxy_quic_handler; + + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + h3c->max_literal = u->conf->buffer_size; + + /* + * h3c->max_table_capacity = 0; + * h3c->max_blocked_streams = 0; + */ + + if (ngx_http_v3_send_settings(c) != NGX_OK) { + return NGX_ERROR; + } + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NGX_ERROR; + } + + /* XXX */ + *log = r->connection->listening->log; + log->connection = c->number; + + /* XXX do not create log here */ + sc = ngx_quic_open_stream(c, 1); + if (sc == NULL) { + return NGX_ERROR; + } + + sc->log = c->log; + sc->pool->log = sc->log; + sc->read->log = sc->log; + sc->write->log = sc->log; + + /* QUIC connection may outlive client connection */ + + c->log = log; + c->pool->log = c->log; + c->read->log = c->log; + c->write->log = c->log; + + sc->data = r; + + sc->requests++; + c->requests++; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + u->peer.connection = sc; + u->writer.connection = sc; + + if (ngx_http_v3_proxy_handle_quic_connection(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_proxy_quic_handler(ngx_connection_t *c) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 proxy handler"); + + if (c->close) { + ngx_http_v3_close_connection(c); + return; + } + + if (ngx_http_v3_proxy_handle_quic_connection(c) != NGX_OK) { + ngx_http_v3_close_connection(c); + } +} + + +static ngx_int_t +ngx_http_v3_proxy_handle_quic_connection(ngx_connection_t *c) +{ + ngx_connection_t *sc; + + if (c->read->timedout) { + ngx_quic_set_app_error(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive shutdown"); + return NGX_DONE; + } + + while (!ngx_quic_get_error(c)) { + + sc = ngx_quic_accept_stream(c); + if (sc == NULL) { + break; + } + + if (!(sc->quic->stream->id & NGX_QUIC_STREAM_UNIDIRECTIONAL)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "upstream opened a quic bidi stream"); + return NGX_ERROR; + } + + ngx_http_v3_init_uni_stream(sc); + } + + if (ngx_quic_has_streams(c, 1, 1) == NGX_DECLINED) { + ngx_quic_set_app_error(c, NGX_HTTP_V3_ERR_NO_ERROR, "shutdown"); + return NGX_DONE; + } + + if (ngx_quic_get_error(c)) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_reinit_request(ngx_http_request_t *r) +{ + ngx_http_v3_proxy_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx == NULL) { + return NGX_OK; + } + + ctx->pseudo_done = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_body_output_filter(void *data, ngx_chain_t *in) +{ + ngx_http_request_t *r = data; + + off_t size; + size_t len; + u_char *chunk; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll, **fl; + ngx_http_proxy_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy output filter"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (in == NULL) { + out = in; + goto out; + } + + out = NULL; + ll = &out; + + if (!ctx->header_sent) { + /* first buffer contains headers, pass it unmodified */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy output header"); + + ctx->header_sent = 1; + + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = in->buf; + *ll = tl; + ll = &tl->next; + + in = in->next; + + if (in == NULL) { + tl->next = NULL; + goto out; + } + } + + size = 0; + cl = in; + fl = ll; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy output chunk: %O", ngx_buf_size(cl->buf)); + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || ngx_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + if (cl->next == NULL) { + break; + } + + cl = cl->next; + } + + if (size) { + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, 0xffffffffffffffffull); + + chunk = ngx_palloc(r->pool, len); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + len; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_proxy_body_output_filter; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + b->last_buf = cl->buf->last_buf; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + cl->buf->last_buf = 0; + + tl->next = *fl; + *fl = tl; + + } else if (cl->buf->last_buf) { + + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_proxy_body_output_filter; + b->temporary = 0; + b->memory = 0; + b->last_buf = 1; + b->pos = b->last; + + cl->buf->last_buf = 0; + + *ll = tl; + } + + *ll = NULL; + +out: + + rc = ngx_chain_writer(&r->upstream->writer, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_proxy_body_output_filter); + + return rc; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_response(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_connection_t *c; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + c = r->connection; + u = r->upstream; + + rc = ngx_http_v3_parse_headers(c, &ctx->parse_headers, &u->buffer); + + if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (rc > 0) { + if (u->peer.connection) { + ngx_quic_reset_stream(u->peer.connection, rc); + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream sent invalid header"); + } + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* XXX flood check */ + + if (rc != NGX_DONE) { + return rc; + } + + /* a whole header has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 proxy header done"); + + if (u->headers_in.status_n == 0) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + ctx->pseudo_done = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; + } + + /* + * if no "Server" and "Date" in header line, + * then add the special empty headers + */ + + if (r->upstream->headers_in.server == NULL) { + h = ngx_list_push(&r->upstream->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); + + ngx_str_set(&h->key, "Server"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "server"; + h->next = NULL; + } + + if (r->upstream->headers_in.date == NULL) { + h = ngx_list_push(&r->upstream->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); + + ngx_str_set(&h->key, "Date"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "date"; + h->next = NULL; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_header(void *data, ngx_str_t *name, ngx_str_t *value, + ngx_uint_t index, ngx_uint_t dynamic) +{ + ngx_http_request_t *r = data; + + ngx_int_t rc; + ngx_str_t namet, valuet; + ngx_table_elt_t *h; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_upstream_header_t *hh; + ngx_http_upstream_main_conf_t *umcf; + + if (name == NULL) { + + if (dynamic) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream is using dynamic table"); + return NGX_ERROR; + } + + if (ngx_http_v3_lookup_static(r->connection, index, &namet, &valuet) + != NGX_OK) + { + return NGX_ERROR; + } + + name = &namet; + + if (value == NULL) { + value = &valuet; + } + } + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + u = r->upstream; + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (name->len && name->data[0] == ':') { + + if (ctx->pseudo_done) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":status", 7) + == 0) + { + rc = ngx_atoi(value->data, value->len); + + if (rc == NGX_ERROR) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + u->headers_in.status_n = rc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy status %ui", + u->headers_in.status_n); + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy pseudo header: \"%V: %V\"", + name, value); + } + + return NGX_OK; + } + + ctx->pseudo_done = 1; + + h = ngx_list_push(ctx->ctx.trailers ? &r->upstream->headers_in.trailers + : &r->upstream->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = r->header_hash; + + h->key.len = name->len; + h->value.len = value->len; + + h->key.data = ngx_pnalloc(r->pool, + h->key.len + 1 + h->value.len + 1 + h->key.len); + if (h->key.data == NULL) { + h->hash = 0; + return NGX_ERROR; + } + + h->value.data = h->key.data + h->key.len + 1; + h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; + + ngx_memcpy(h->key.data, name->data, h->key.len); + h->key.data[h->key.len] = '\0'; + ngx_memcpy(h->value.data, value->data, h->value.len); + h->value.data[h->value.len] = '\0'; + + if (h->key.len == r->lowcase_index) { + ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); + + } else { + ngx_strlow(h->lowcase_key, h->key.data, h->key.len); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy header: \"%V: %V\"", + name, value); + + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS || ctx->ctx.trailers) { + return NGX_OK; + } + + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_insert_count(void *data, ngx_uint_t *insert_count) +{ + ngx_http_request_t *r = data; + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream is using dynamic table"); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_proxy_input_filter_init(void *data) +{ + ngx_http_request_t *r = data; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + + u = r->upstream; + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy filter init s:%ui h:%d l:%O", + u->headers_in.status_n, ctx->head, + u->headers_in.content_length_n); + + /* as per RFC9110, 6.4.1. Content Semantics */ + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED + || ctx->head) + { + /* 1xx, 204, and 304 and replies to HEAD requests */ + /* no 1xx since we don't send Expect and Upgrade */ + + u->pipe->length = 0; + u->length = 0; + + } else if (u->headers_in.content_length_n == 0) { + /* empty body: special case as filter won't be called */ + + u->pipe->length = 0; + u->length = 0; + + } else { + u->pipe->length = 1; + u->length = 1; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_body_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) +{ + off_t n; + ngx_int_t rc; + ngx_buf_t *b, **prev; + ngx_chain_t *cl; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_v3_parse_data_t *st; + ngx_http_proxy_loc_conf_t *plcf; + + if (buf->pos == buf->last) { + return NGX_OK; + } + + r = p->input_ctx; + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + u = r->upstream; + + st = &ctx->parse_data; + + if (p->upstream_done) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http3 proxy data after close"); + return NGX_OK; + } + + if (p->length == 0) { + + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after trailers"); + + p->upstream_done = 1; + + return NGX_OK; + } + + b = NULL; + + if (ctx->ctx.trailers) { + rc = ngx_http_v3_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + p->length = 0; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent data after trailers"); + } + } + + goto free_buf; + } + + n = r->headers_in.content_length_n; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + prev = &buf->shadow; + + while (buf->pos < buf->last) { + + if (st->length == 0) { + rc = ngx_http_v3_parse_data(r->connection, st, buf); + + /* XXX check flood */ + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + + if (plcf->upstream.pass_trailers) { + rc = ngx_http_v3_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + p->length = 1; + goto free_buf; + } + } + + p->length = 0; + goto free_buf; + } + + if (rc > 0) { + + if (u->peer.connection) { + ngx_quic_reset_stream(u->peer.connection, rc); + } + + ngx_log_error(NGX_LOG_ERR, p->log, 0, + "upstream sent invalid body"); + return NGX_ERROR; + } + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, p->log, 0, + "upstream sent invalid body"); + return NGX_ERROR; + } + + /* rc == NGX_OK */ + } + + if (n != -1 && n - ctx->body_received < (off_t) st->length) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + return NGX_ERROR; + } + + cl = ngx_chain_get_free_buf(p->pool, &p->free); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->pos = buf->pos; + b->start = buf->start; + b->end = buf->end; + b->tag = p->tag; + b->temporary = 1; + b->recycled = 1; + + *prev = b; + prev = &b->shadow; + + if (p->in) { + *p->last_in = cl; + } else { + p->in = cl; + } + p->last_in = &cl->next; + + if (buf->last - buf->pos > (ssize_t) st->length) { + ctx->body_received += st->length; + buf->pos += st->length; + st->length = 0; + + } else { + ctx->body_received += (buf->last - buf->pos); + st->length -= buf->last - buf->pos; + buf->pos = buf->last; + } + + b->last = buf->pos; + } + + if (st->length == 0) { + + if (n != -1 && ctx->body_received < n) { + p->length = 1; + + } else { + /* possible trailers */ + p->length = -1; + } + + } else { + p->length = st->length; + } + +free_buf: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http3 proxy body wait length %O", p->length); + + if (b) { + b->shadow = buf; + b->last_shadow = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, + "input buf %p %z", b->pos, b->last - b->pos); + + return NGX_OK; + } + + /* there is no data record in the buf, add it to free chain */ + + if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_non_buffered_body_filter(void *data, ssize_t bytes) +{ + ngx_http_request_t *r = data; + + off_t n; + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_v3_parse_data_t *st; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + u = r->upstream; + st = &ctx->parse_data; + + n = r->headers_in.content_length_n; + + buf = &u->buffer; + + buf->pos = buf->last; + buf->last += bytes; + + if (ctx->ctx.trailers) { + rc = ngx_http_v3_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + + /* a whole response has been parsed successfully */ + + r->upstream->keepalive = !u->headers_in.connection_close; + u->length = 0; + + if (buf->pos != buf->last) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent data after trailers"); + u->keepalive = 0; + } + } + + return NGX_OK; + } + + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { + ll = &cl->next; + } + + while (buf->pos < buf->last) { + + if (st->length == 0) { + rc = ngx_http_v3_parse_data(r->connection, st, buf); + + /* XXX check flood */ + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + + if (plcf->upstream.pass_trailers) { + rc = ngx_http_v3_proxy_process_trailer(r, buf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + u->length = 1; + return NGX_OK; + } + } + + u->length = 0; + return NGX_OK; + } + + if (rc > 0) { + + if (u->peer.connection) { + ngx_quic_reset_stream(u->peer.connection, rc); + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid body"); + return NGX_ERROR; + } + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid body"); + return NGX_ERROR; + } + + /* rc == NGX_OK */ + } + + if (n != -1 && n - ctx->body_received < (off_t) st->length) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + return NGX_ERROR; + } + + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + + b->flush = 1; + b->memory = 1; + + b->pos = buf->pos; + b->tag = u->output.tag; + + if (buf->last - buf->pos > (ssize_t) st->length) { + ctx->body_received += st->length; + buf->pos += st->length; + st->length = 0; + + } else { + ctx->body_received += (buf->last - buf->pos); + st->length -= buf->last - buf->pos; + buf->pos = buf->last; + } + + b->last = buf->pos; + } + + if (st->length == 0) { + + if (n != -1 && ctx->body_received < n) { + u->length = 1; + + } else { + /* possible trailers */ + u->length = -1; + } + + } else { + u->length = st->length; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_trailer(ngx_http_request_t *r, ngx_buf_t *buf) +{ + size_t len; + ngx_int_t rc; + ngx_buf_t *b; + ngx_http_upstream_t *u; + ngx_http_v3_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_proxy_module); + + if (ctx->ctx.trailers == NULL) { + ctx->ctx.trailers = ngx_create_temp_buf(r->pool, + plcf->upstream.buffer_size); + if (ctx->ctx.trailers == NULL) { + return NGX_ERROR; + } + } + + u = r->upstream; + + b = ctx->ctx.trailers; + len = ngx_min(buf->last - buf->pos, b->end - b->last); + + b->last = ngx_cpymem(b->last, buf->pos, len); + buf->pos += len; + + rc = ngx_http_v3_parse_headers(r->connection, &ctx->parse_headers, b); + + if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (rc > 0) { + if (u->peer.connection) { + ngx_quic_reset_stream(u->peer.connection, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header"); + } + + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + /* XXX flood check */ + + if (rc != NGX_DONE) { + return rc; + } + + /* a whole trailer has been parsed successfully */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 proxy trailer done"); + + return NGX_OK; +} + + +static void +ngx_http_v3_proxy_abort_request(ngx_http_request_t *r) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort http3 proxy request"); + + return; +} + + +static void +ngx_http_v3_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http3 proxy request"); + + return; +} diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h index 251480438..b7485931c 100644 --- a/src/http/v3/ngx_http_v3_table.h +++ b/src/http/v3/ngx_http_v3_table.h @@ -22,6 +22,9 @@ #define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 #define NGX_HTTP_V3_HEADER_LOCATION 12 #define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_METHOD_HEAD 18 +#define NGX_HTTP_V3_HEADER_METHOD_POST 20 +#define NGX_HTTP_V3_HEADER_METHOD_PUT 21 #define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 #define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 #define NGX_HTTP_V3_HEADER_STATUS_103 24