-- cgit v1.2.3 From e92cb24f40b865e3cc5b9f0993e328e4f0642e0f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: HTTP UDP layer, QUIC support autotest. --- auto/lib/openssl/conf | 9 +++++++++ src/http/ngx_http.c | 7 ++++++- src/http/ngx_http_core_module.c | 6 ++++++ src/http/ngx_http_core_module.h | 2 ++ src/http/ngx_http_request.c | 10 ++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 4fb52df7f..4f4390e11 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -140,3 +140,12 @@ END fi fi + +ngx_feature="OpenSSL QUIC support" +ngx_feature_name="NGX_OPENSSL_QUIC" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL" +ngx_feature_test="SSL_CTX_set_quic_method(NULL, NULL)" +. auto/feature diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 79ef9c644..3e82dc60d 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1163,7 +1163,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) + { continue; } @@ -1180,6 +1183,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, } port->family = sa->sa_family; + port->type = lsopt->type; port->port = p; port->addrs.elts = NULL; @@ -1735,6 +1739,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) } #endif + ls->type = addr->opt.type; ls->backlog = addr->opt.backlog; ls->rcvbuf = addr->opt.rcvbuf; ls->sndbuf = addr->opt.sndbuf; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 4867bed2b..b0c8aabfc 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -3800,6 +3800,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3821,6 +3822,11 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strcmp(value[n].data, "quic") == 0) { + lsopt.type = SOCK_DGRAM; + continue; + } + if (ngx_strcmp(value[n].data, "bind") == 0) { lsopt.set = 1; lsopt.bind = 1; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index f5434cc51..9f26b5794 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -86,6 +86,7 @@ typedef struct { int backlog; int rcvbuf; int sndbuf; + int type; #if (NGX_HAVE_SETFIB) int setfib; #endif @@ -266,6 +267,7 @@ typedef struct { typedef struct { ngx_int_t family; + ngx_int_t type; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ } ngx_http_conf_port_t; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index bb69e71d0..f137590fd 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -324,6 +324,10 @@ ngx_http_init_connection(ngx_connection_t *c) rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; + if (c->shared) { + rev->ready = 1; + } + #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -386,6 +390,10 @@ ngx_http_wait_request_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler"); + if (c->shared) { + goto request; + } + if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_close_connection(c); @@ -486,6 +494,8 @@ ngx_http_wait_request_handler(ngx_event_t *rev) } } +request: + c->log->action = "reading client request line"; ngx_reusable_connection(c, 0); -- cgit v1.2.3 From 26ac1c73f0fe90c77cbad84a6b4ef5712e35ba52 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: Initial QUIC support in http. --- auto/modules | 3 +- src/core/ngx_connection.h | 7 +- src/core/ngx_core.h | 36 +-- src/event/ngx_event_openssl.c | 143 ++++++++++ src/event/ngx_event_openssl.h | 3 + src/event/ngx_event_quic.h | 31 +++ src/http/modules/ngx_http_ssl_module.c | 27 +- src/http/modules/ngx_http_ssl_module.h | 1 + src/http/ngx_http.c | 26 ++ src/http/ngx_http_core_module.c | 18 +- src/http/ngx_http_core_module.h | 2 + src/http/ngx_http_request.c | 495 +++++++++++++++++++++++++++++++++ src/http/ngx_http_request.h | 1 + 13 files changed, 766 insertions(+), 27 deletions(-) create mode 100644 src/event/ngx_event_quic.h diff --git a/auto/modules b/auto/modules index d78e2823a..480f2b0a7 100644 --- a/auto/modules +++ b/auto/modules @@ -1242,7 +1242,8 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_openssl_module ngx_module_incs= - ngx_module_deps=src/event/ngx_event_openssl.h + ngx_module_deps="src/event/ngx_event_openssl.h \ + src/event/ngx_event_quic.h" ngx_module_srcs="src/event/ngx_event_openssl.c src/event/ngx_event_openssl_stapling.c" ngx_module_libs= diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index ad6556d0c..0d7e2166b 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -147,13 +147,14 @@ struct ngx_connection_s { socklen_t socklen; ngx_str_t addr_text; - ngx_proxy_protocol_t *proxy_protocol; + ngx_proxy_protocol_t *proxy_protocol; #if (NGX_SSL || NGX_COMPAT) - ngx_ssl_connection_t *ssl; + ngx_quic_connection_t *quic; + ngx_ssl_connection_t *ssl; #endif - ngx_udp_connection_t *udp; + ngx_udp_connection_t *udp; struct sockaddr *local_sockaddr; socklen_t local_socklen; diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 7ecdca0cb..549fae084 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -12,23 +12,24 @@ #include -typedef struct ngx_module_s ngx_module_t; -typedef struct ngx_conf_s ngx_conf_t; -typedef struct ngx_cycle_s ngx_cycle_t; -typedef struct ngx_pool_s ngx_pool_t; -typedef struct ngx_chain_s ngx_chain_t; -typedef struct ngx_log_s ngx_log_t; -typedef struct ngx_open_file_s ngx_open_file_t; -typedef struct ngx_command_s ngx_command_t; -typedef struct ngx_file_s ngx_file_t; -typedef struct ngx_event_s ngx_event_t; -typedef struct ngx_event_aio_s ngx_event_aio_t; -typedef struct ngx_connection_s ngx_connection_t; -typedef struct ngx_thread_task_s ngx_thread_task_t; -typedef struct ngx_ssl_s ngx_ssl_t; -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_module_s ngx_module_t; +typedef struct ngx_conf_s ngx_conf_t; +typedef struct ngx_cycle_s ngx_cycle_t; +typedef struct ngx_pool_s ngx_pool_t; +typedef struct ngx_chain_s ngx_chain_t; +typedef struct ngx_log_s ngx_log_t; +typedef struct ngx_open_file_s ngx_open_file_t; +typedef struct ngx_command_s ngx_command_t; +typedef struct ngx_file_s ngx_file_t; +typedef struct ngx_event_s ngx_event_t; +typedef struct ngx_event_aio_s ngx_event_aio_t; +typedef struct ngx_connection_s ngx_connection_t; +typedef struct ngx_thread_task_s ngx_thread_task_t; +typedef struct ngx_ssl_s ngx_ssl_t; +typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; +typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; +typedef struct ngx_udp_connection_s ngx_udp_connection_t; typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); @@ -82,6 +83,7 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #if (NGX_OPENSSL) #include +#include #endif #include #include diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 91b415caa..baf28ecfd 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -89,6 +89,126 @@ static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void ngx_openssl_exit(ngx_cycle_t *cycle); +#if NGX_OPENSSL_QUIC + +static int +quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len) +{ + size_t *len; + uint8_t **rsec, **wsec; + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_ssl_handshake_log(c); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + u_char buf[64]; + size_t m; + + m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + + m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + } +#endif + + switch (level) { + + case ssl_encryption_handshake: + len = &c->quic->handshake_secret_len; + rsec = &c->quic->handshake_read_secret; + wsec = &c->quic->handshake_write_secret; + break; + + case ssl_encryption_application: + len = &c->quic->application_secret_len; + rsec = &c->quic->application_read_secret; + wsec = &c->quic->application_write_secret; + break; + + default: + return 0; + } + + *len = secret_len; + + *rsec = ngx_pnalloc(c->pool, secret_len); + if (*rsec == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(*rsec, read_secret, secret_len); + + *wsec = ngx_pnalloc(c->pool, secret_len); + if (*wsec == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(*wsec, write_secret, secret_len); + + return 1; +} + + +static int +quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char buf[512]; + ngx_int_t m; + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 256)) - buf; + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data: %*s%s, len: %uz, level:%d", + m, buf, len < 512 ? "" : "...", len, (int) level); + + if (!(SSL_provide_quic_data(ssl_conn, level, data, len))) { + ERR_print_errors_fp(stderr); + return 0; + } + + return 1; +} + + +static int +quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ + printf("quic_flush_flight()\n"); + return 1; +} + + +static int +quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + printf("quic_send_alert(), lvl=%d, alert=%d\n", level, alert); + return 1; +} + + +static SSL_QUIC_METHOD quic_method = { + quic_set_encryption_secrets, + quic_add_handshake_data, + quic_flush_flight, + quic_send_alert, +}; + +#endif + static ngx_command_t ngx_openssl_commands[] = { @@ -1459,6 +1579,29 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) } +ngx_int_t +ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) +{ + if (!enable) { + return NGX_OK; + } + +#if NGX_OPENSSL_QUIC + + SSL_CTX_set_quic_method(ssl->ctx, &quic_method); +printf("%s\n", __func__); + return NGX_OK; + +#else + + ngx_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_quic\" is not supported on this platform"); + return NGX_ERROR; + +#endif +} + + ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 61da0c5db..c6124275f 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #endif #include +#include #include #ifndef OPENSSL_NO_OCSP #include @@ -189,6 +191,7 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); +ngx_int_t ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h new file mode 100644 index 000000000..6b7d32427 --- /dev/null +++ b/src/event/ngx_event_quic.h @@ -0,0 +1,31 @@ + +/* + * + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +struct ngx_quic_connection_s { + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; + + ngx_str_t client_in; + ngx_str_t client_in_key; + ngx_str_t client_in_iv; + ngx_str_t client_in_hp; + + size_t handshake_secret_len; + uint8_t *handshake_read_secret; + uint8_t *handshake_write_secret; + + size_t application_secret_len; + uint8_t *application_read_secret; + uint8_t *application_write_secret; +}; + + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 495e628d3..693e45a1c 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -249,6 +249,13 @@ static ngx_command_t ngx_http_ssl_commands[] = { offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, + { ngx_string("ssl_quic"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, quic), + NULL }, + ngx_null_command }; @@ -568,6 +575,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) sscf->enable = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; + sscf->quic = NGX_CONF_UNSET; sscf->buffer_size = NGX_CONF_UNSET_SIZE; sscf->verify = NGX_CONF_UNSET_UINT; sscf->verify_depth = NGX_CONF_UNSET_UINT; @@ -612,6 +620,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->early_data, prev->early_data, 0); + ngx_conf_merge_value(conf->quic, prev->quic, 0); + ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); @@ -696,6 +706,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } +printf("ngx_ssl_create\n"); if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) { return NGX_CONF_ERROR; } @@ -857,6 +868,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (ngx_ssl_quic(cf, &conf->ssl, conf->quic) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -1141,13 +1156,15 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { +printf("ssl %d http3 %d\n", addr[a].opt.ssl, addr[a].opt.http3); - if (!addr[a].opt.ssl) { + if (!addr[a].opt.ssl && !addr[a].opt.http3) { continue; } cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; +printf("sscf->protocols %lx\n", sscf->protocols); if (sscf->certificates == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, @@ -1156,6 +1173,14 @@ ngx_http_ssl_init(ngx_conf_t *cf) cscf->file_name, cscf->line); return NGX_ERROR; } + + if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" did not enable TLSv1.3 for " + "the \"listen ... http3\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } } } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index 26fdccfe4..310d7c737 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -21,6 +21,7 @@ typedef struct { ngx_flag_t prefer_server_ciphers; ngx_flag_t early_data; + ngx_flag_t quic; ngx_uint_t protocols; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 3e82dc60d..10f88fabb 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1203,6 +1203,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SSL) + ngx_uint_t http3; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1238,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SSL) + http3 = lsopt->http3 || addr[i].opt.http3; +#endif if (lsopt->set) { @@ -1274,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SSL) + addr[i].opt.http3 = http3; +#endif return NGX_OK; } @@ -1315,6 +1324,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, &lsopt->addr_text); } +#endif + +#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC) + + if (lsopt->http3) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks QUIC " + "support, HTTP/3 is not enabled for %V", + &lsopt->addr_text); + } + #endif addr = ngx_array_push(&port->addrs); @@ -1806,6 +1826,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_SSL) + addrs[i].conf.http3 = addr[i].opt.http3; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1871,6 +1894,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_SSL) + addrs6[i].conf.http3 = addr[i].opt.http3; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index b0c8aabfc..5c210bebd 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -3822,11 +3822,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } - if (ngx_strcmp(value[n].data, "quic") == 0) { - lsopt.type = SOCK_DGRAM; - continue; - } - if (ngx_strcmp(value[n].data, "bind") == 0) { lsopt.set = 1; lsopt.bind = 1; @@ -4004,6 +3999,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[n].data, "http3") == 0) { +#if (NGX_HTTP_SSL) + lsopt.http3 = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"http3\" parameter requires " + "ngx_http_ssl_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strcmp(value[n].data, "spdy") == 0) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "invalid parameter \"spdy\": " diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 9f26b5794..25327b2f4 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -75,6 +75,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned http3:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -238,6 +239,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned http3:1; unsigned proxy_protocol:1; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f137590fd..54a0da497 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -62,6 +62,8 @@ static u_char *ngx_http_log_error_handler(ngx_http_request_t *r, #if (NGX_HTTP_SSL) static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); + +static void ngx_http_quic_handshake(ngx_event_t *rev); #endif @@ -328,6 +330,14 @@ ngx_http_init_connection(ngx_connection_t *c) rev->ready = 1; } +#if (NGX_HTTP_SSL) + if (hc->addr_conf->http3) { + hc->quic = 1; + c->log->action = "QUIC handshaking"; + rev->handler = ngx_http_quic_handshake; + } +#endif + #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -647,6 +657,491 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_SSL) +static uint64_t +ngx_quic_parse_int(u_char **pos) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + p = *pos; + len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + + while (--len) { + value = (value << 8) + *p++; + } + + *pos = p; + return value; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) +{ + u_char *p; + uint64_t value; + + p = *pos; + value = *p++ ^ *mask++; + + while (--len) { + value = (value << 8) + (*p++ ^ *mask++); + } + + *pos = p; + return value; +} + + +static void +ngx_http_quic_handshake(ngx_event_t *rev) +{ + int n, sslerr; +#if (NGX_DEBUG) + u_char buf[512]; + size_t m; +#endif + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_connection_t *hc; + ngx_quic_connection_t *qc; + ngx_http_ssl_srv_conf_t *sscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake"); + + c = rev->data; + hc = c->data; + b = c->buffer; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + ngx_http_close_connection(c); + return; + } + + c->quic = qc; + + printf("buffer %p %p:%p:%p:%p \n", b, b->start, b->pos, b->last, b->end); + + if ((b->pos[0] & 0xf0) != 0xc0) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet"); + ngx_http_close_connection(c); + return; + } + + if (ngx_buf_size(b) < 1200) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "too small UDP datagram"); + ngx_http_close_connection(c); + return; + } + + ngx_int_t flags = *b->pos++; + uint32_t version = ngx_http_v2_parse_uint32(b->pos); + b->pos += 4; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic flags:%xi version:%xD", flags, version); + + if (version != 0xff000017) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); + ngx_http_close_connection(c); + return; + } + + qc->dcid.len = *b->pos++; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); + b->pos += qc->dcid.len; + + qc->scid.len = *b->pos++; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); + b->pos += qc->scid.len; + + qc->token.len = ngx_quic_parse_int(&b->pos); + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->token.data, b->pos, qc->token.len); + b->pos += qc->token.len; + + uint64_t plen = ngx_quic_parse_int(&b->pos); + /* draft-ietf-quic-tls-23#section-5.4.2: + * the Packet Number field is assumed to be 4 bytes long + * draft-ietf-quic-tls-23#section-5.4.3: + * AES-Based header protection samples 16 bytes + */ + u_char *sample = b->pos + 4; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len); + + m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic SCID: %*s, len: %uz", m, buf, qc->scid.len); + + m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic token: %*s, len: %uz", m, buf, qc->token.len); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet length: %d", plen); + + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic sample: %*s", m, buf); + } +#endif + +// initial secret + + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + const EVP_MD *digest; + static const uint8_t salt[20] = + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" + "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; + + digest = EVP_sha256(); + HKDF_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt, + sizeof(salt)); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic salt: %*s, len: %uz", m, buf, sizeof(salt)); + + m = ngx_hex_dump(buf, is, is_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic initial secret: %*s, len: %uz", m, buf, is_len); + } +#endif + + size_t hkdfl_len; + uint8_t hkdfl[20]; + uint8_t *p; + + /* draft-ietf-quic-tls-23#section-5.2 */ + + qc->client_in.len = SHA256_DIGEST_LENGTH; + qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len); + if (qc->client_in.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; + hkdfl[0] = 0; + hkdfl[1] = qc->client_in.len; + hkdfl[2] = sizeof("tls13 client in") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 client in", + sizeof("tls13 client in") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in.data, qc->client_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in) failed"); + ngx_http_close_connection(c); + return; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic EVP key:%d tag:%d nonce:%d", + EVP_AEAD_key_length(EVP_aead_aes_128_gcm()), + EVP_AEAD_max_tag_len(EVP_aead_aes_128_gcm()), + EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm())); + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + qc->client_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); + if (qc->client_in_key.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; + hkdfl[1] = qc->client_in_key.len; + hkdfl[2] = sizeof("tls13 quic key") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic key", + sizeof("tls13 quic key") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_key.data, qc->client_in_key.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_key) failed"); + ngx_http_close_connection(c); + return; + } + + qc->client_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); + qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); + if (qc->client_in_iv.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; + hkdfl[1] = qc->client_in_iv.len; + hkdfl[2] = sizeof("tls13 quic iv") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_iv.data, qc->client_in_iv.len, digest, + qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_iv) failed"); + ngx_http_close_connection(c); + return; + } + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + + qc->client_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); + if (qc->client_in_hp.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; + hkdfl[1] = qc->client_in_hp.len; + hkdfl[2] = sizeof("tls13 quic hp") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_hp.data, qc->client_in_hp.len, digest, + qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_hp) failed"); + ngx_http_close_connection(c); + return; + } + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->client_in.data, qc->client_in.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic client initial secret: %*s, len: %uz", + m, buf, qc->client_in.len); + + m = ngx_hex_dump(buf, qc->client_in_key.data, qc->client_in_key.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic key: %*s, len: %uz", + m, buf, qc->client_in_key.len); + + m = ngx_hex_dump(buf, qc->client_in_iv.data, qc->client_in_iv.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic iv: %*s, len: %uz", m, buf, qc->client_in_iv.len); + + m = ngx_hex_dump(buf, qc->client_in_hp.data, qc->client_in_hp.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic hp: %*s, len: %uz", m, buf, qc->client_in_hp.len); + } +#endif + +// header protection + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + uint8_t mask[16]; + int outlen; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, + qc->client_in_hp.data, NULL) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_CIPHER_CTX_free(ctx); + + u_char clearflags = flags ^ (mask[0] & 0x0f); + ngx_int_t pnl = (clearflags & 0x03) + 1; + uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic sample: %*s", m, buf); + + m = ngx_hex_dump(buf, mask, 5) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic mask: %*s", m, buf); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + } +#endif + +// packet protection + + ngx_str_t ciphertext; + ciphertext.data = b->pos; + ciphertext.len = plen - pnl; + + ngx_str_t ad; + ad.len = b->pos - b->start; + ad.data = ngx_pnalloc(c->pool, ad.len); + if (ad.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(ad.data, b->start, ad.len); + ad.data[0] = clearflags; + ad.data[ad.len - pnl] = (u_char)pn; + + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in_iv); + nonce[11] ^= pn; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic nonce: %*s, len: %uz", m, buf, 12); + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic ad: %*s, len: %uz", m, buf, ad.len); + } +#endif + + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), + qc->client_in_key.data, + qc->client_in_key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + uint8_t cleartext[1600]; + size_t cleartext_len = sizeof(cleartext); + + if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), + nonce, qc->client_in_iv.len, ciphertext.data, + ciphertext.len, ad.data, ad.len) + != 1) + { + EVP_AEAD_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_AEAD_CTX_open() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_AEAD_CTX_free(aead); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet: %*s%s, len: %uz", + m, buf, m < 512 ? "" : "...", cleartext_len); + } +#endif + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); + + if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) + != NGX_OK) + { + ngx_http_close_connection(c); + return; + } + + n = SSL_do_handshake(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + if (!SSL_provide_quic_data(c->ssl->connection, + SSL_quic_read_level(c->ssl->connection), + &cleartext[4], cleartext_len - 4)) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "SSL_provide_quic_data() failed"); + ngx_http_close_connection(c); + return; + } + + n = SSL_do_handshake(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr == SSL_ERROR_SSL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + ngx_http_close_connection(c); + return; +} + + static void ngx_http_ssl_handshake(ngx_event_t *rev) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 70c2d424d..8cc5d6432 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -323,6 +323,7 @@ typedef struct { ngx_chain_t *free; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t; -- cgit v1.2.3 From f03fe916636c25bfe6ac9a63b48f28f4bfaa72b2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: Server Initial Keys. --- src/event/ngx_event_quic.h | 5 ++ src/http/ngx_http_request.c | 141 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 6b7d32427..4aef2eec0 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -18,6 +18,11 @@ struct ngx_quic_connection_s { ngx_str_t client_in_iv; ngx_str_t client_in_hp; + ngx_str_t server_in; + ngx_str_t server_in_key; + ngx_str_t server_in_iv; + ngx_str_t server_in_hp; + size_t handshake_secret_len; uint8_t *handshake_read_secret; uint8_t *handshake_write_secret; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 54a0da497..bc4f2a13b 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -898,7 +898,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) "HKDF_expand(client_in_key) failed"); ngx_http_close_connection(c); return; - } + } qc->client_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); @@ -958,18 +958,151 @@ ngx_http_quic_handshake(ngx_event_t *rev) m = ngx_hex_dump(buf, qc->client_in_key.data, qc->client_in_key.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic key: %*s, len: %uz", + "quic client key: %*s, len: %uz", m, buf, qc->client_in_key.len); m = ngx_hex_dump(buf, qc->client_in_iv.data, qc->client_in_iv.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic iv: %*s, len: %uz", m, buf, qc->client_in_iv.len); + "quic client iv: %*s, len: %uz", + m, buf, qc->client_in_iv.len); m = ngx_hex_dump(buf, qc->client_in_hp.data, qc->client_in_hp.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic hp: %*s, len: %uz", m, buf, qc->client_in_hp.len); + "quic client hp: %*s, len: %uz", + m, buf, qc->client_in_hp.len); + } +#endif + +// server initial + + /* draft-ietf-quic-tls-23#section-5.2 */ + + qc->server_in.len = SHA256_DIGEST_LENGTH; + qc->server_in.data = ngx_pnalloc(c->pool, qc->server_in.len); + if (qc->server_in.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 server in") - 1 + 1; + hkdfl[0] = 0; + hkdfl[1] = qc->server_in.len; + hkdfl[2] = sizeof("tls13 server in") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 server in", + sizeof("tls13 server in") - 1); + *p = '\0'; + + if (HKDF_expand(qc->server_in.data, qc->server_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(server_in) failed"); + ngx_http_close_connection(c); + return; + } + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + qc->server_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->server_in_key.data = ngx_pnalloc(c->pool, qc->server_in_key.len); + if (qc->server_in_key.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; + hkdfl[1] = qc->server_in_key.len; + hkdfl[2] = sizeof("tls13 quic key") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic key", + sizeof("tls13 quic key") - 1); + *p = '\0'; + + if (HKDF_expand(qc->server_in_key.data, qc->server_in_key.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(server_in_key) failed"); + ngx_http_close_connection(c); + return; + } + + qc->server_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); + qc->server_in_iv.data = ngx_pnalloc(c->pool, qc->server_in_iv.len); + if (qc->server_in_iv.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; + hkdfl[1] = qc->server_in_iv.len; + hkdfl[2] = sizeof("tls13 quic iv") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); + *p = '\0'; + + if (HKDF_expand(qc->server_in_iv.data, qc->server_in_iv.len, digest, + qc->server_in.data, qc->server_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(server_in_iv) failed"); + ngx_http_close_connection(c); + return; + } + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + + qc->server_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->server_in_hp.data = ngx_pnalloc(c->pool, qc->server_in_hp.len); + if (qc->server_in_hp.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; + hkdfl[1] = qc->server_in_hp.len; + hkdfl[2] = sizeof("tls13 quic hp") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); + *p = '\0'; + + if (HKDF_expand(qc->server_in_hp.data, qc->server_in_hp.len, digest, + qc->server_in.data, qc->server_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(server_in_hp) failed"); + ngx_http_close_connection(c); + return; + } + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->server_in.data, qc->server_in.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic server initial secret: %*s, len: %uz", + m, buf, qc->server_in.len); + + m = ngx_hex_dump(buf, qc->server_in_key.data, qc->server_in_key.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic server key: %*s, len: %uz", + m, buf, qc->server_in_key.len); + + m = ngx_hex_dump(buf, qc->server_in_iv.data, qc->server_in_iv.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic server iv: %*s, len: %uz", + m, buf, qc->server_in_iv.len); + + m = ngx_hex_dump(buf, qc->server_in_hp.data, qc->server_in_hp.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic server hp: %*s, len: %uz", + m, buf, qc->server_in_hp.len); } #endif -- cgit v1.2.3 From b77c2d00b57233a87350dedbe44897fcb0b7adf1 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: QUIC set_encryption_secrets callback. --- src/event/ngx_event_openssl.c | 223 +++++++++++++++++++++++++++++++++++++++--- src/event/ngx_event_quic.h | 26 +++-- src/http/ngx_http_request.c | 4 +- 3 files changed, 232 insertions(+), 21 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index baf28ecfd..23aea779b 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -89,6 +89,7 @@ static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void ngx_openssl_exit(ngx_cycle_t *cycle); + #if NGX_OPENSSL_QUIC static int @@ -96,8 +97,11 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len) { - size_t *len; + size_t *rlen, *wlen; uint8_t **rsec, **wsec; + const char *name; + const EVP_MD *digest; + const EVP_AEAD *aead; ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -111,50 +115,245 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: %*s, len: %uz, level:%d", + "set_encryption_secrets: read %*s, len: %uz, level:%d", m, buf, secret_len, (int) level); m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: %*s, len: %uz, level:%d", + "set_encryption_secrets: write %*s, len: %uz, level:%d", m, buf, secret_len, (int) level); } #endif + name = SSL_get_cipher(ssl_conn); + + if (OPENSSL_strcasecmp(name, "TLS_AES_128_GCM_SHA256") == 0) { + aead = EVP_aead_aes_128_gcm(); + digest = EVP_sha256(); + + } else if (OPENSSL_strcasecmp(name, "TLS_AES_256_GCM_SHA384") == 0) { + aead = EVP_aead_aes_256_gcm(); + digest = EVP_sha384(); + + } else { + return 0; + } + + size_t hkdfl_len; + uint8_t hkdfl[20]; + uint8_t *p; + const char *label; + + ngx_str_t *client_key, *client_iv, *client_hp; + ngx_str_t *server_key, *server_iv, *server_hp; + switch (level) { case ssl_encryption_handshake: - len = &c->quic->handshake_secret_len; - rsec = &c->quic->handshake_read_secret; - wsec = &c->quic->handshake_write_secret; + rlen = &c->quic->client_hs.len; + rsec = &c->quic->client_hs.data; + wlen = &c->quic->server_hs.len; + wsec = &c->quic->server_hs.data; + + client_key = &c->quic->client_hs_key; + client_iv = &c->quic->client_hs_iv; + client_hp = &c->quic->client_hs_hp; + + server_key = &c->quic->server_hs_key; + server_iv = &c->quic->server_hs_iv; + server_hp = &c->quic->server_hs_hp; + break; case ssl_encryption_application: - len = &c->quic->application_secret_len; - rsec = &c->quic->application_read_secret; - wsec = &c->quic->application_write_secret; + rlen = &c->quic->client_ad.len; + rsec = &c->quic->client_ad.data; + wlen = &c->quic->server_ad.len; + wsec = &c->quic->server_ad.data; + + client_key = &c->quic->client_ad_key; + client_iv = &c->quic->client_ad_iv; + client_hp = &c->quic->client_ad_hp; + + server_key = &c->quic->server_ad_key; + server_iv = &c->quic->server_ad_iv; + server_hp = &c->quic->server_ad_hp; + break; default: return 0; } - *len = secret_len; + *rlen = *wlen = secret_len; *rsec = ngx_pnalloc(c->pool, secret_len); if (*rsec == NULL) { - return NGX_ERROR; + return 0; } ngx_memcpy(*rsec, read_secret, secret_len); *wsec = ngx_pnalloc(c->pool, secret_len); if (*wsec == NULL) { - return NGX_ERROR; + return 0; } ngx_memcpy(*wsec, write_secret, secret_len); + // client keys + + client_key->len = EVP_AEAD_key_length(aead); + client_key->data = ngx_pnalloc(c->pool, client_key->len); + if (client_key->data == NULL) { + return 0; + } + + label = "tls13 quic key"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = client_key->len / 256; + hkdfl[1] = client_key->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(client_key->data, client_key->len, + digest, *rsec, *rlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(client_key) failed"); + return 0; + } + + + client_iv->len = EVP_AEAD_nonce_length(aead); + client_iv->data = ngx_pnalloc(c->pool, client_iv->len); + if (client_iv->data == NULL) { + return 0; + } + + label = "tls13 quic iv"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = client_iv->len / 256; + hkdfl[1] = client_iv->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(client_iv->data, client_iv->len, + digest, *rsec, *rlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(client_iv) failed"); + return 0; + } + + + client_hp->len = EVP_AEAD_key_length(aead); + client_hp->data = ngx_pnalloc(c->pool, client_hp->len); + if (client_hp->data == NULL) { + return 0; + } + + label = "tls13 quic hp"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = client_hp->len / 256; + hkdfl[1] = client_hp->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(client_hp->data, client_hp->len, + digest, *rsec, *rlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(client_hp) failed"); + return 0; + } + + + // server keys + + server_key->len = EVP_AEAD_key_length(aead); + server_key->data = ngx_pnalloc(c->pool, server_key->len); + if (server_key->data == NULL) { + return 0; + } + + label = "tls13 quic key"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = server_key->len / 256; + hkdfl[1] = server_key->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(server_key->data, server_key->len, + digest, *wsec, *wlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(server_key) failed"); + return 0; + } + + + server_iv->len = EVP_AEAD_nonce_length(aead); + server_iv->data = ngx_pnalloc(c->pool, server_iv->len); + if (server_iv->data == NULL) { + return 0; + } + + label = "tls13 quic iv"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = server_iv->len / 256; + hkdfl[1] = server_iv->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(server_iv->data, server_iv->len, + digest, *wsec, *wlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(server_iv) failed"); + return 0; + } + + + server_hp->len = EVP_AEAD_key_length(aead); + server_hp->data = ngx_pnalloc(c->pool, server_hp->len); + if (server_hp->data == NULL) { + return 0; + } + + label = "tls13 quic hp"; + hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + hkdfl[0] = server_hp->len / 256; + hkdfl[1] = server_hp->len % 256; + hkdfl[2] = sizeof(label) - 1; + p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + *p = '\0'; + + if (HKDF_expand(server_hp->data, server_hp->len, + digest, *wsec, *wlen, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "HKDF_expand(server_hp) failed"); + return 0; + } + return 1; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 4aef2eec0..afc501424 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -23,13 +23,25 @@ struct ngx_quic_connection_s { ngx_str_t server_in_iv; ngx_str_t server_in_hp; - size_t handshake_secret_len; - uint8_t *handshake_read_secret; - uint8_t *handshake_write_secret; - - size_t application_secret_len; - uint8_t *application_read_secret; - uint8_t *application_write_secret; + ngx_str_t client_hs; + ngx_str_t client_hs_key; + ngx_str_t client_hs_iv; + ngx_str_t client_hs_hp; + + ngx_str_t server_hs; + ngx_str_t server_hs_key; + ngx_str_t server_hs_iv; + ngx_str_t server_hs_hp; + + ngx_str_t client_ad; + ngx_str_t client_ad_key; + ngx_str_t client_ad_iv; + ngx_str_t client_ad_hp; + + ngx_str_t server_ad; + ngx_str_t server_ad_key; + ngx_str_t server_ad_iv; + ngx_str_t server_ad_hp; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index bc4f2a13b..85b6835ee 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -783,8 +783,8 @@ ngx_http_quic_handshake(ngx_event_t *rev) uint64_t plen = ngx_quic_parse_int(&b->pos); /* draft-ietf-quic-tls-23#section-5.4.2: * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.3: - * AES-Based header protection samples 16 bytes + * draft-ietf-quic-tls-23#section-5.4.[34]: + * AES-Based and ChaCha20-Based header protections sample 16 bytes */ u_char *sample = b->pos + 4; -- cgit v1.2.3 From 812a0b69a072119de75d57ebdb222f540952c423 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: QUIC add_handshake_data callback, varint routines. --- auto/modules | 3 +- src/event/ngx_event_openssl.c | 147 ++++++++++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic.h | 8 +++ src/http/ngx_http_request.c | 40 +----------- 4 files changed, 153 insertions(+), 45 deletions(-) diff --git a/auto/modules b/auto/modules index 480f2b0a7..b612e6f9f 100644 --- a/auto/modules +++ b/auto/modules @@ -1245,7 +1245,8 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_deps="src/event/ngx_event_openssl.h \ src/event/ngx_event_quic.h" ngx_module_srcs="src/event/ngx_event_openssl.c - src/event/ngx_event_openssl_stapling.c" + src/event/ngx_event_openssl_stapling.c + src/event/ngx_event_quic.c" ngx_module_libs= ngx_module_link=YES ngx_module_order= diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 23aea779b..0985c941e 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -362,22 +362,159 @@ static int quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char buf[512]; - ngx_int_t m; - ngx_connection_t *c; + u_char buf[512], *p, *cipher, *clear, *ad; + size_t ad_len, clear_len; + ngx_int_t m; + ngx_str_t *server_key, *server_iv, *server_hp; + ngx_connection_t *c; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = c->quic; + + switch (level) { + + case ssl_encryption_initial: + server_key = &qc->server_in_key; + server_iv = &qc->server_in_iv; + server_hp = &qc->server_in_hp; + break; + + case ssl_encryption_handshake: + server_key = &qc->server_hs_key; + server_iv = &qc->server_hs_iv; + server_hp = &qc->server_hs_hp; + break; + + default: + return 0; + } m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 256)) - buf; ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data: %*s%s, len: %uz, level:%d", m, buf, len < 512 ? "" : "...", len, (int) level); - if (!(SSL_provide_quic_data(ssl_conn, level, data, len))) { - ERR_print_errors_fp(stderr); + clear = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); + if (clear == 0) { + return 0; + } + + p = clear; + ngx_quic_build_int(&p, 6); // crypto frame + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, len); + p = ngx_cpymem(p, data, len); + + ngx_quic_build_int(&p, 2); // ack frame + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + + clear_len = p - clear; + size_t cipher_len = clear_len + 16 /*expansion*/; + + ad = ngx_alloc(346 /*max header*/, c->log); + if (ad == 0) { return 0; } + p = ad; + if (level == ssl_encryption_initial) { + *p++ = 0xc0; // initial, packet number len + } else if (level == ssl_encryption_handshake) { + *p++ = 0xe0; // handshake, packet number len + } + *p++ = 0xff; + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x17; + *p++ = qc->scid.len; + p = ngx_cpymem(p, qc->scid.data, qc->scid.len); + *p++ = qc->dcid.len; + p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); + ngx_quic_build_int(&p, 0); // token length + ngx_quic_build_int(&p, cipher_len); // length + u_char *pnp = p; + *p++ = 0; // packet number 0 + + ad_len = p - ad; + + m = ngx_hex_dump(buf, (u_char *) ad, ad_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data ad: %*s, len: %uz", + m, buf, ad_len); + + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), + server_key->data, + server_key->len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + + cipher = ngx_alloc(cipher_len, c->log); + if (cipher == 0) { + return 0; + } + + size_t out_len; + + if (EVP_AEAD_CTX_seal(aead, cipher, &out_len, cipher_len, + server_iv->data, server_iv->len, + clear, clear_len, ad, ad_len) + != 1) + { + EVP_AEAD_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_AEAD_CTX_seal() failed"); + return 0; + } + + EVP_AEAD_CTX_free(aead); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + u_char *sample = cipher + 3; // pnl=0 + uint8_t mask[16]; + int outlen; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, server_hp->data, NULL) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_EncryptInit_ex() failed"); + return 0; + } + + if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_EncryptUpdate() failed"); + return 0; + } + + EVP_CIPHER_CTX_free(ctx); + + // header protection, pnl = 0 + ad[0] ^= mask[0] & 0x0f; + *pnp ^= mask[1]; + +printf("cipher_len %ld out_len %ld ad_len %ld\n", cipher_len, out_len, ad_len); + + u_char *packet = ngx_alloc(ad_len + out_len, c->log); + if (packet == 0) { + return 0; + } + + p = ngx_cpymem(packet, ad, ad_len); + p = ngx_cpymem(p, cipher, out_len); + + m = ngx_hex_dump(buf, (u_char *) packet, p - packet) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data packet: %*s, len: %uz", + m, buf, p - packet); + + c->send(c, packet, p - packet); + return 1; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index afc501424..c83b4d9b1 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -8,6 +8,9 @@ #define _NGX_EVENT_QUIC_H_INCLUDED_ +#include + + struct ngx_quic_connection_s { ngx_str_t scid; ngx_str_t dcid; @@ -45,4 +48,9 @@ struct ngx_quic_connection_s { }; +uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); +uint64_t ngx_quic_parse_int(u_char **pos); +void ngx_quic_build_int(u_char **pos, uint64_t value); + + #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 85b6835ee..d128190c5 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -657,44 +657,6 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_SSL) -static uint64_t -ngx_quic_parse_int(u_char **pos) -{ - u_char *p; - uint64_t value; - ngx_uint_t len; - - p = *pos; - len = 1 << ((*p & 0xc0) >> 6); - value = *p++ & 0x3f; - - while (--len) { - value = (value << 8) + *p++; - } - - *pos = p; - return value; -} - - -static uint64_t -ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) -{ - u_char *p; - uint64_t value; - - p = *pos; - value = *p++ ^ *mask++; - - while (--len) { - value = (value << 8) + (*p++ ^ *mask++); - } - - *pos = p; - return value; -} - - static void ngx_http_quic_handshake(ngx_event_t *rev) { @@ -1210,7 +1172,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet: %*s%s, len: %uz", + "quic packet payload: %*s%s, len: %uz", m, buf, m < 512 ? "" : "...", cleartext_len); } #endif -- cgit v1.2.3 From ac640641a6ee09affe12a77cd7a944c1ac3148f0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:51 +0300 Subject: OpenSSL compatibility. --- src/event/ngx_event_openssl.c | 369 +++++++++++++++++++++++++++++++++--------- src/event/ngx_event_openssl.h | 4 + src/event/ngx_event_quic.c | 165 +++++++++++++++++++ src/event/ngx_event_quic.h | 7 + src/http/ngx_http_request.c | 251 ++++++++++++++++++++++------ 5 files changed, 664 insertions(+), 132 deletions(-) create mode 100644 src/event/ngx_event_quic.c diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 0985c941e..45b2ed29a 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -97,11 +97,15 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len) { + u_char *name; size_t *rlen, *wlen; uint8_t **rsec, **wsec; - const char *name; const EVP_MD *digest; - const EVP_AEAD *aead; +#ifdef OPENSSL_IS_BORINGSSL + const EVP_AEAD *evp; +#else + const EVP_CIPHER *evp; +#endif ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -125,24 +129,38 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } #endif - name = SSL_get_cipher(ssl_conn); + name = (u_char *) SSL_get_cipher(ssl_conn); - if (OPENSSL_strcasecmp(name, "TLS_AES_128_GCM_SHA256") == 0) { - aead = EVP_aead_aes_128_gcm(); + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { +#ifdef OPENSSL_IS_BORINGSSL + evp = EVP_aead_aes_128_gcm(); +#else + evp = EVP_aes_128_gcm(); +#endif digest = EVP_sha256(); - } else if (OPENSSL_strcasecmp(name, "TLS_AES_256_GCM_SHA384") == 0) { - aead = EVP_aead_aes_256_gcm(); + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { +#ifdef OPENSSL_IS_BORINGSSL + evp = EVP_aead_aes_256_gcm(); +#else + evp = EVP_aes_256_gcm(); +#endif digest = EVP_sha384(); } else { return 0; } - size_t hkdfl_len; + size_t hkdfl_len, llen; uint8_t hkdfl[20]; uint8_t *p; const char *label; +#if (NGX_DEBUG) + u_char buf[512]; + size_t m; +#endif ngx_str_t *client_key, *client_iv, *client_hp; ngx_str_t *server_key, *server_iv, *server_hp; @@ -203,157 +221,229 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, // client keys - client_key->len = EVP_AEAD_key_length(aead); +#ifdef OPENSSL_IS_BORINGSSL + client_key->len = EVP_AEAD_key_length(evp); +#else + client_key->len = EVP_CIPHER_key_length(evp); +#endif client_key->data = ngx_pnalloc(c->pool, client_key->len); if (client_key->data == NULL) { return 0; } label = "tls13 quic key"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic key") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = client_key->len / 256; hkdfl[1] = client_key->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(client_key->data, client_key->len, - digest, *rsec, *rlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(client_key->data, client_key->len, + digest, *rsec, *rlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(client_key) failed"); + "ngx_hkdf_expand(client_key) failed"); return 0; } + m = ngx_hex_dump(buf, client_key->data, client_key->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic client key: %*s, len: %uz, level: %d", + m, buf, client_key->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); + - client_iv->len = EVP_AEAD_nonce_length(aead); +#ifdef OPENSSL_IS_BORINGSSL + client_iv->len = EVP_AEAD_nonce_length(evp); +#else + client_iv->len = EVP_CIPHER_iv_length(evp); +#endif client_iv->data = ngx_pnalloc(c->pool, client_iv->len); if (client_iv->data == NULL) { return 0; } label = "tls13 quic iv"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic iv") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = client_iv->len / 256; hkdfl[1] = client_iv->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(client_iv->data, client_iv->len, - digest, *rsec, *rlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(client_iv->data, client_iv->len, + digest, *rsec, *rlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(client_iv) failed"); + "ngx_hkdf_expand(client_iv) failed"); return 0; } + m = ngx_hex_dump(buf, client_iv->data, client_iv->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic client iv: %*s, len: %uz, level: %d", + m, buf, client_iv->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); + - client_hp->len = EVP_AEAD_key_length(aead); +#ifdef OPENSSL_IS_BORINGSSL + client_hp->len = EVP_AEAD_key_length(evp); +#else + client_hp->len = EVP_CIPHER_key_length(evp); +#endif client_hp->data = ngx_pnalloc(c->pool, client_hp->len); if (client_hp->data == NULL) { return 0; } label = "tls13 quic hp"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic hp") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = client_hp->len / 256; hkdfl[1] = client_hp->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(client_hp->data, client_hp->len, - digest, *rsec, *rlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(client_hp->data, client_hp->len, + digest, *rsec, *rlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(client_hp) failed"); + "ngx_hkdf_expand(client_hp) failed"); return 0; } + m = ngx_hex_dump(buf, client_hp->data, client_hp->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic client hp: %*s, len: %uz, level: %d", + m, buf, client_hp->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); + // server keys - server_key->len = EVP_AEAD_key_length(aead); +#ifdef OPENSSL_IS_BORINGSSL + server_key->len = EVP_AEAD_key_length(evp); +#else + server_key->len = EVP_CIPHER_key_length(evp); +#endif server_key->data = ngx_pnalloc(c->pool, server_key->len); if (server_key->data == NULL) { return 0; } label = "tls13 quic key"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic key") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = server_key->len / 256; hkdfl[1] = server_key->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(server_key->data, server_key->len, - digest, *wsec, *wlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(server_key->data, server_key->len, + digest, *wsec, *wlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(server_key) failed"); + "ngx_hkdf_expand(server_key) failed"); return 0; } + m = ngx_hex_dump(buf, server_key->data, server_key->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic server key: %*s, len: %uz, level: %d", + m, buf, server_key->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); + - server_iv->len = EVP_AEAD_nonce_length(aead); +#ifdef OPENSSL_IS_BORINGSSL + server_iv->len = EVP_AEAD_nonce_length(evp); +#else + server_iv->len = EVP_CIPHER_iv_length(evp); +#endif server_iv->data = ngx_pnalloc(c->pool, server_iv->len); if (server_iv->data == NULL) { return 0; } label = "tls13 quic iv"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic iv") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = server_iv->len / 256; hkdfl[1] = server_iv->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(server_iv->data, server_iv->len, - digest, *wsec, *wlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(server_iv->data, server_iv->len, + digest, *wsec, *wlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(server_iv) failed"); + "ngx_hkdf_expand(server_iv) failed"); return 0; } + m = ngx_hex_dump(buf, server_iv->data, server_iv->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic server iv: %*s, len: %uz, level: %d", + m, buf, server_iv->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - server_hp->len = EVP_AEAD_key_length(aead); + +#ifdef OPENSSL_IS_BORINGSSL + server_hp->len = EVP_AEAD_key_length(evp); +#else + server_hp->len = EVP_CIPHER_key_length(evp); +#endif server_hp->data = ngx_pnalloc(c->pool, server_hp->len); if (server_hp->data == NULL) { return 0; } label = "tls13 quic hp"; - hkdfl_len = 2 + 1 + sizeof(label) - 1 + 1; + llen = sizeof("tls13 quic hp") - 1; + hkdfl_len = 2 + 1 + llen + 1; hkdfl[0] = server_hp->len / 256; hkdfl[1] = server_hp->len % 256; - hkdfl[2] = sizeof(label) - 1; - p = ngx_cpymem(&hkdfl[3], label, sizeof(label) - 1); + hkdfl[2] = llen; + p = ngx_cpymem(&hkdfl[3], label, llen); *p = '\0'; - if (HKDF_expand(server_hp->data, server_hp->len, - digest, *wsec, *wlen, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(server_hp->data, server_hp->len, + digest, *wsec, *wlen, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "HKDF_expand(server_hp) failed"); + "ngx_hkdf_expand(server_hp) failed"); return 0; } + m = ngx_hex_dump(buf, server_hp->data, server_hp->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic server hp: %*s, len: %uz, level: %d", + m, buf, server_hp->len, level); + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "hkdf: %*s, len: %uz", m, buf, hkdfl_len); + return 1; } @@ -362,14 +452,22 @@ static int quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char buf[512], *p, *cipher, *clear, *ad; + u_char buf[512], *p, *ciphertext, *clear, *ad, *name; size_t ad_len, clear_len; ngx_int_t m; ngx_str_t *server_key, *server_iv, *server_hp; +#ifdef OPENSSL_IS_BORINGSSL + const EVP_AEAD *cipher; +#else + const EVP_CIPHER *cipher; +#endif ngx_connection_t *c; ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_ssl_handshake_log(c); + qc = c->quic; switch (level) { @@ -413,7 +511,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_build_int(&p, 0); clear_len = p - clear; - size_t cipher_len = clear_len + 16 /*expansion*/; + size_t ciphertext_len = clear_len + 16 /*expansion*/; ad = ngx_alloc(346 /*max header*/, c->log); if (ad == 0) { @@ -434,8 +532,10 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, p = ngx_cpymem(p, qc->scid.data, qc->scid.len); *p++ = qc->dcid.len; p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); - ngx_quic_build_int(&p, 0); // token length - ngx_quic_build_int(&p, cipher_len); // length + if (level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); // token length + } + ngx_quic_build_int(&p, ciphertext_len + 1); // length (inc. pnl) u_char *pnp = p; *p++ = 0; // packet number 0 @@ -446,19 +546,43 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "quic_add_handshake_data ad: %*s, len: %uz", m, buf, ad_len); - EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), - server_key->data, - server_key->len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - cipher = ngx_alloc(cipher_len, c->log); - if (cipher == 0) { + name = (u_char *) SSL_get_cipher(ssl_conn); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { +#ifdef OPENSSL_IS_BORINGSSL + cipher = EVP_aead_aes_128_gcm(); +#else + cipher = EVP_aes_128_gcm(); +#endif + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { +#ifdef OPENSSL_IS_BORINGSSL + cipher = EVP_aead_aes_256_gcm(); +#else + cipher = EVP_aes_256_gcm(); +#endif + + } else { + return 0; + } + + + ciphertext = ngx_alloc(ciphertext_len, c->log); + if (ciphertext == 0) { return 0; } +#ifdef OPENSSL_IS_BORINGSSL size_t out_len; + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, + server_key->data, + server_key->len, + EVP_AEAD_DEFAULT_TAG_LENGTH); - if (EVP_AEAD_CTX_seal(aead, cipher, &out_len, cipher_len, + if (EVP_AEAD_CTX_seal(aead, ciphertext, &out_len, ciphertext_len, server_iv->data, server_iv->len, clear, clear_len, ad, ad_len) != 1) @@ -470,9 +594,78 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } EVP_AEAD_CTX_free(aead); +#else + int out_len; + EVP_CIPHER_CTX *aead; + + aead = EVP_CIPHER_CTX_new(); + if (aead == NULL) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); + return 0; + } + + if (EVP_EncryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); + return 0; + } + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, server_iv->len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return 0; + } + + if (EVP_EncryptInit_ex(aead, NULL, NULL, server_key->data, server_iv->data) + != 1) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); + return 0; + } + + if (EVP_EncryptUpdate(aead, NULL, &out_len, ad, ad_len) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); + return 0; + } + + if (EVP_EncryptUpdate(aead, ciphertext, &out_len, clear, clear_len) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); + return 0; + } + + ciphertext_len = out_len; + + if (EVP_EncryptFinal_ex(aead, ciphertext + out_len, &out_len) <= 0) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_ex failed"); + return 0; + } + + ciphertext_len += out_len; + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, + ciphertext + clear_len) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + return 0; + } + + EVP_CIPHER_CTX_free(aead); + + out_len = ciphertext_len + EVP_GCM_TLS_TAG_LEN; +#endif EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - u_char *sample = cipher + 3; // pnl=0 + u_char *sample = ciphertext + 3; // pnl=0 uint8_t mask[16]; int outlen; @@ -494,11 +687,27 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, EVP_CIPHER_CTX_free(ctx); + m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data sample: %*s, len: %uz", + m, buf, 16); + + m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data mask: %*s, len: %uz", + m, buf, 16); + + m = ngx_hex_dump(buf, (u_char *) server_hp->data, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data hp_key: %*s, len: %uz", + m, buf, 16); + // header protection, pnl = 0 ad[0] ^= mask[0] & 0x0f; *pnp ^= mask[1]; -printf("cipher_len %ld out_len %ld ad_len %ld\n", cipher_len, out_len, ad_len); +printf("clear_len %ld ciphertext_len %ld out_len %ld ad_len %ld\n", +clear_len, ciphertext_len, (size_t) out_len, ad_len); u_char *packet = ngx_alloc(ad_len + out_len, c->log); if (packet == 0) { @@ -506,12 +715,12 @@ printf("cipher_len %ld out_len %ld ad_len %ld\n", cipher_len, out_len, ad_len); } p = ngx_cpymem(packet, ad, ad_len); - p = ngx_cpymem(p, cipher, out_len); + p = ngx_cpymem(p, ciphertext, out_len); - m = ngx_hex_dump(buf, (u_char *) packet, p - packet) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data packet: %*s, len: %uz", - m, buf, p - packet); + m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(256, p - packet)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data packet: %*s%s, len: %uz", + m, buf, len < 512 ? "" : "...", p - packet); c->send(c, packet, p - packet); diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index c6124275f..b562f0f17 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -23,7 +23,11 @@ #include #endif #include +#ifdef OPENSSL_IS_BORINGSSL #include +#else +#include +#endif #include #ifndef OPENSSL_NO_OCSP #include diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c new file mode 100644 index 000000000..a655e7ddc --- /dev/null +++ b/src/event/ngx_event_quic.c @@ -0,0 +1,165 @@ +#include +#include +#include + + +uint64_t +ngx_quic_parse_int(u_char **pos) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + p = *pos; + len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + + while (--len) { + value = (value << 8) + *p++; + } + + *pos = p; + return value; +} + + +void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + ngx_uint_t len;//, len2; + + p = *pos; + len = 0; + + while (value >> ((1 << len) * 8 - 2)) { + len++; + } + + *p = len << 6; + +// len2 = + len = (1 << len); + len--; + *p |= value >> (len * 8); + p++; + + while (len) { + *p++ = value >> ((len-- - 1) * 8); + } + + *pos = p; +// return len2; +} + + +uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) +{ + u_char *p; + uint64_t value; + + p = *pos; + value = *p++ ^ *mask++; + + while (--len) { + value = (value << 8) + (*p++ ^ *mask++); + } + + *pos = p; + return value; +} + + +ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const u_char *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index c83b4d9b1..236439b06 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -52,5 +52,12 @@ uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); uint64_t ngx_quic_parse_int(u_char **pos); void ngx_quic_build_int(u_char **pos, uint64_t value); +ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); +ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); + #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index d128190c5..17ad78986 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -775,16 +775,35 @@ ngx_http_quic_handshake(ngx_event_t *rev) // initial secret - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - const EVP_MD *digest; + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + const EVP_MD *digest; +#ifdef OPENSSL_IS_BORINGSSL + const EVP_AEAD *cipher; +#else + const EVP_CIPHER *cipher; +#endif static const uint8_t salt[20] = "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; digest = EVP_sha256(); - HKDF_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt, - sizeof(salt)); + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + +#ifdef OPENSSL_IS_BORINGSSL + cipher = EVP_aead_aes_128_gcm(); +#else + cipher = EVP_aes_128_gcm(); +#endif + + if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, + salt, sizeof(salt)) + != NGX_OK) + { + ngx_http_close_connection(c); + return; + } #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { @@ -812,6 +831,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) } hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; + bzero(hkdfl, sizeof(hkdfl)); hkdfl[0] = 0; hkdfl[1] = qc->client_in.len; hkdfl[2] = sizeof("tls13 client in") - 1; @@ -819,25 +839,39 @@ ngx_http_quic_handshake(ngx_event_t *rev) sizeof("tls13 client in") - 1); *p = '\0'; - if (HKDF_expand(qc->client_in.data, qc->client_in.len, - digest, is, is_len, hkdfl, hkdfl_len) - == 0) +#if 0 + ngx_memcpy(hkdfl, "\x00\x20\x0f\x74\x6c\x73\x31\x33\x20\x63\x6c\x69\x65\x6e\x74\x20\x69\x6e\x00\x00", 20); + + m = ngx_hex_dump(buf, hkdfl, sizeof(hkdfl)) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic initial secret hkdf: %*s, len: %uz", + m, buf, sizeof(hkdfl)); +#endif + + if (ngx_hkdf_expand(qc->client_in.data, qc->client_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(client_in) failed"); + "ngx_hkdf_expand(client_in) failed"); ngx_http_close_connection(c); return; } +#ifdef OPENSSL_IS_BORINGSSL ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic EVP key:%d tag:%d nonce:%d", - EVP_AEAD_key_length(EVP_aead_aes_128_gcm()), - EVP_AEAD_max_tag_len(EVP_aead_aes_128_gcm()), - EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm())); + EVP_AEAD_key_length(cipher), + EVP_AEAD_max_tag_len(cipher), + EVP_AEAD_nonce_length(cipher)); +#endif - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - qc->client_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->client_in_key.len = EVP_AEAD_key_length(cipher); +#else + qc->client_in_key.len = EVP_CIPHER_key_length(cipher); +#endif qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); if (qc->client_in_key.data == NULL) { ngx_http_close_connection(c); @@ -851,18 +885,22 @@ ngx_http_quic_handshake(ngx_event_t *rev) sizeof("tls13 quic key") - 1); *p = '\0'; - if (HKDF_expand(qc->client_in_key.data, qc->client_in_key.len, - digest, qc->client_in.data, qc->client_in.len, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->client_in_key.data, qc->client_in_key.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(client_in_key) failed"); + "ngx_hkdf_expand(client_in_key) failed"); ngx_http_close_connection(c); return; } - qc->client_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->client_in_iv.len = EVP_AEAD_nonce_length(cipher); +#else + qc->client_in_iv.len = EVP_CIPHER_iv_length(cipher); +#endif qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); if (qc->client_in_iv.data == NULL) { ngx_http_close_connection(c); @@ -875,19 +913,24 @@ ngx_http_quic_handshake(ngx_event_t *rev) p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); *p = '\0'; - if (HKDF_expand(qc->client_in_iv.data, qc->client_in_iv.len, digest, - qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->client_in_iv.data, qc->client_in_iv.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(client_in_iv) failed"); + "ngx_hkdf_expand(client_in_iv) failed"); ngx_http_close_connection(c); return; } /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - qc->client_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->client_in_hp.len = EVP_AEAD_key_length(cipher); +#else + qc->client_in_hp.len = EVP_CIPHER_key_length(cipher); +#endif qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); if (qc->client_in_hp.data == NULL) { ngx_http_close_connection(c); @@ -900,12 +943,13 @@ ngx_http_quic_handshake(ngx_event_t *rev) p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); *p = '\0'; - if (HKDF_expand(qc->client_in_hp.data, qc->client_in_hp.len, digest, - qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->client_in_hp.data, qc->client_in_hp.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(client_in_hp) failed"); + "ngx_hkdf_expand(client_in_hp) failed"); ngx_http_close_connection(c); return; } @@ -956,19 +1000,23 @@ ngx_http_quic_handshake(ngx_event_t *rev) sizeof("tls13 server in") - 1); *p = '\0'; - if (HKDF_expand(qc->server_in.data, qc->server_in.len, - digest, is, is_len, hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->server_in.data, qc->server_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(server_in) failed"); + "ngx_hkdf_expand(server_in) failed"); ngx_http_close_connection(c); return; } /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - qc->server_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->server_in_key.len = EVP_AEAD_key_length(cipher); +#else + qc->server_in_key.len = EVP_CIPHER_key_length(cipher); +#endif qc->server_in_key.data = ngx_pnalloc(c->pool, qc->server_in_key.len); if (qc->server_in_key.data == NULL) { ngx_http_close_connection(c); @@ -982,18 +1030,22 @@ ngx_http_quic_handshake(ngx_event_t *rev) sizeof("tls13 quic key") - 1); *p = '\0'; - if (HKDF_expand(qc->server_in_key.data, qc->server_in_key.len, - digest, qc->server_in.data, qc->server_in.len, - hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->server_in_key.data, qc->server_in_key.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(server_in_key) failed"); + "ngx_hkdf_expand(server_in_key) failed"); ngx_http_close_connection(c); return; } - qc->server_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->server_in_iv.len = EVP_AEAD_nonce_length(cipher); +#else + qc->server_in_iv.len = EVP_CIPHER_iv_length(cipher); +#endif qc->server_in_iv.data = ngx_pnalloc(c->pool, qc->server_in_iv.len); if (qc->server_in_iv.data == NULL) { ngx_http_close_connection(c); @@ -1006,19 +1058,24 @@ ngx_http_quic_handshake(ngx_event_t *rev) p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); *p = '\0'; - if (HKDF_expand(qc->server_in_iv.data, qc->server_in_iv.len, digest, - qc->server_in.data, qc->server_in.len, hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->server_in_iv.data, qc->server_in_iv.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(server_in_iv) failed"); + "ngx_hkdf_expand(server_in_iv) failed"); ngx_http_close_connection(c); return; } /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - qc->server_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); +#ifdef OPENSSL_IS_BORINGSSL + qc->server_in_hp.len = EVP_AEAD_key_length(cipher); +#else + qc->server_in_hp.len = EVP_CIPHER_key_length(cipher); +#endif qc->server_in_hp.data = ngx_pnalloc(c->pool, qc->server_in_hp.len); if (qc->server_in_hp.data == NULL) { ngx_http_close_connection(c); @@ -1031,12 +1088,13 @@ ngx_http_quic_handshake(ngx_event_t *rev) p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); *p = '\0'; - if (HKDF_expand(qc->server_in_hp.data, qc->server_in_hp.len, digest, - qc->server_in.data, qc->server_in.len, hkdfl, hkdfl_len) - == 0) + if (ngx_hkdf_expand(qc->server_in_hp.data, qc->server_in_hp.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "HKDF_expand(server_in_hp) failed"); + "ngx_hkdf_expand(server_in_hp) failed"); ngx_http_close_connection(c); return; } @@ -1147,12 +1205,20 @@ ngx_http_quic_handshake(ngx_event_t *rev) } #endif - EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), + uint8_t cleartext[1600]; + size_t cleartext_len; + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, qc->client_in_key.data, qc->client_in_key.len, EVP_AEAD_DEFAULT_TAG_LENGTH); - uint8_t cleartext[1600]; - size_t cleartext_len = sizeof(cleartext); + if (aead == NULL) { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_AEAD_CTX_new() failed"); + ngx_http_close_connection(c); + return; + } if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), nonce, qc->client_in_iv.len, ciphertext.data, @@ -1167,6 +1233,87 @@ ngx_http_quic_handshake(ngx_event_t *rev) } EVP_AEAD_CTX_free(aead); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *aead; + + aead = EVP_CIPHER_CTX_new(); + if (aead == NULL) { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_CIPHER_CTX_new() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_in_iv.len, + NULL) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_in_key.data, nonce) + != 1) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptUpdate(aead, NULL, &len, ad.data, ad.len) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptUpdate(aead, cleartext, &len, ciphertext.data, + ciphertext.len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + cleartext_len = len; + tag = ciphertext.data + ciphertext.len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, + tag) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptFinal_ex(aead, cleartext + len, &len) <= 0) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptFinal_ex failed"); + ngx_http_close_connection(c); + return; + } + + cleartext_len += len; + + EVP_CIPHER_CTX_free(aead); +#endif #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { -- cgit v1.2.3 From 0ddf4a2e67beb96a8de26e3a00082ebc98987902 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: PN-aware AEAD nonce, feeding proper CRYPTO length. --- src/event/ngx_event_openssl.c | 54 ++++++++++++++++++++++++++++++++----------- src/http/ngx_http_request.c | 22 +++++++++++++++++- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 45b2ed29a..b072caf0f 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -452,7 +452,7 @@ static int quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char buf[512], *p, *ciphertext, *clear, *ad, *name; + u_char buf[2048], *p, *ciphertext, *clear, *ad, *name; size_t ad_len, clear_len; ngx_int_t m; ngx_str_t *server_key, *server_iv, *server_hp; @@ -463,6 +463,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, #endif ngx_connection_t *c; ngx_quic_connection_t *qc; + static int pn = 0; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -488,10 +489,10 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 256)) - buf; + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data: %*s%s, len: %uz, level:%d", - m, buf, len < 512 ? "" : "...", len, (int) level); + m, buf, len < 2048 ? "" : "...", len, (int) level); clear = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); if (clear == 0) { @@ -504,15 +505,21 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_build_int(&p, len); p = ngx_cpymem(p, data, len); - ngx_quic_build_int(&p, 2); // ack frame - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); + if (level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 2); // ack frame + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + } clear_len = p - clear; size_t ciphertext_len = clear_len + 16 /*expansion*/; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz", + clear_len, ciphertext_len); + ad = ngx_alloc(346 /*max header*/, c->log); if (ad == 0) { return 0; @@ -537,7 +544,13 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } ngx_quic_build_int(&p, ciphertext_len + 1); // length (inc. pnl) u_char *pnp = p; - *p++ = 0; // packet number 0 + + if (level == ssl_encryption_initial) { + *p++ = 0; // packet number 0 + + } else if (level == ssl_encryption_handshake) { + *p++ = pn++; + } ad_len = p - ad; @@ -575,6 +588,21 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } + uint8_t *nonce = ngx_pstrdup(c->pool, server_iv); + if (level == ssl_encryption_handshake) { + nonce[11] ^= (pn - 1); + } + + m = ngx_hex_dump(buf, (u_char *) server_iv->data, 12) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data sample: server_iv %*s", + m, buf); + m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data sample: n=%d nonce %*s", + pn - 1, m, buf); + + #ifdef OPENSSL_IS_BORINGSSL size_t out_len; EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, @@ -583,7 +611,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, EVP_AEAD_DEFAULT_TAG_LENGTH); if (EVP_AEAD_CTX_seal(aead, ciphertext, &out_len, ciphertext_len, - server_iv->data, server_iv->len, + nonce, server_iv->len, clear, clear_len, ad, ad_len) != 1) { @@ -619,7 +647,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - if (EVP_EncryptInit_ex(aead, NULL, NULL, server_key->data, server_iv->data) + if (EVP_EncryptInit_ex(aead, NULL, NULL, server_key->data, nonce) != 1) { EVP_CIPHER_CTX_free(aead); @@ -717,10 +745,10 @@ clear_len, ciphertext_len, (size_t) out_len, ad_len); p = ngx_cpymem(packet, ad, ad_len); p = ngx_cpymem(p, ciphertext, out_len); - m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(256, p - packet)) - buf; + m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data packet: %*s%s, len: %uz", - m, buf, len < 512 ? "" : "...", p - packet); + m, buf, len < 2048 ? "" : "...", p - packet); c->send(c, packet, p - packet); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 17ad78986..fd30bf3d9 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1324,6 +1324,26 @@ ngx_http_quic_handshake(ngx_event_t *rev) } #endif + if (cleartext[0] != 0x06) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, + "unexpected frame in initial packet"); + ngx_http_close_connection(c); + return; + } + + if (cleartext[1] != 0x00) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, + "unexpected CRYPTO offset in initial packet"); + ngx_http_close_connection(c); + return; + } + + uint8_t *crypto = &cleartext[2]; + uint64_t crypto_len = ngx_quic_parse_int(&crypto); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic initial packet CRYPTO length: %uL pp:%p:%p", crypto_len, cleartext, crypto); + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) @@ -1351,7 +1371,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) if (!SSL_provide_quic_data(c->ssl->connection, SSL_quic_read_level(c->ssl->connection), - &cleartext[4], cleartext_len - 4)) + crypto, crypto_len)) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "SSL_provide_quic_data() failed"); -- cgit v1.2.3 From 56a80c228a7f4211e507ce48edf023f907821f1e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: Fixed indentation. --- src/http/ngx_http_request.c | 395 ++++++++++++++++++++++---------------------- 1 file changed, 196 insertions(+), 199 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index fd30bf3d9..389d7488f 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -817,46 +817,46 @@ ngx_http_quic_handshake(ngx_event_t *rev) } #endif - size_t hkdfl_len; - uint8_t hkdfl[20]; - uint8_t *p; + size_t hkdfl_len; + uint8_t hkdfl[20]; + uint8_t *p; - /* draft-ietf-quic-tls-23#section-5.2 */ + /* draft-ietf-quic-tls-23#section-5.2 */ - qc->client_in.len = SHA256_DIGEST_LENGTH; - qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len); - if (qc->client_in.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->client_in.len = SHA256_DIGEST_LENGTH; + qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len); + if (qc->client_in.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; - bzero(hkdfl, sizeof(hkdfl)); - hkdfl[0] = 0; - hkdfl[1] = qc->client_in.len; - hkdfl[2] = sizeof("tls13 client in") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 client in", - sizeof("tls13 client in") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; + bzero(hkdfl, sizeof(hkdfl)); + hkdfl[0] = 0; + hkdfl[1] = qc->client_in.len; + hkdfl[2] = sizeof("tls13 client in") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 client in", sizeof("tls13 client in") - 1); + *p = '\0'; #if 0 - ngx_memcpy(hkdfl, "\x00\x20\x0f\x74\x6c\x73\x31\x33\x20\x63\x6c\x69\x65\x6e\x74\x20\x69\x6e\x00\x00", 20); + ngx_memcpy(hkdfl, "\x00\x20\x0f\x74\x6c\x73\x31\x33\x20\x63" + "\x6c\x69\x65\x6e\x74\x20\x69\x6e\x00\x00", 20); - m = ngx_hex_dump(buf, hkdfl, sizeof(hkdfl)) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic initial secret hkdf: %*s, len: %uz", - m, buf, sizeof(hkdfl)); + m = ngx_hex_dump(buf, hkdfl, sizeof(hkdfl)) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic initial secret hkdf: %*s, len: %uz", + m, buf, sizeof(hkdfl)); #endif - if (ngx_hkdf_expand(qc->client_in.data, qc->client_in.len, - digest, is, is_len, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->client_in.data, qc->client_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(client_in) failed"); + ngx_http_close_connection(c); + return; + } #ifdef OPENSSL_IS_BORINGSSL ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, @@ -868,91 +868,90 @@ ngx_http_quic_handshake(ngx_event_t *rev) #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_key.len = EVP_AEAD_key_length(cipher); + qc->client_in_key.len = EVP_AEAD_key_length(cipher); #else - qc->client_in_key.len = EVP_CIPHER_key_length(cipher); + qc->client_in_key.len = EVP_CIPHER_key_length(cipher); #endif - qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); - if (qc->client_in_key.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); + if (qc->client_in_key.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->client_in_key.len; - hkdfl[2] = sizeof("tls13 quic key") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic key", - sizeof("tls13 quic key") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; + hkdfl[1] = qc->client_in_key.len; + hkdfl[2] = sizeof("tls13 quic key") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_key.data, qc->client_in_key.len, - digest, qc->client_in.data, qc->client_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_key) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->client_in_key.data, qc->client_in_key.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(client_in_key) failed"); + ngx_http_close_connection(c); + return; + } #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_iv.len = EVP_AEAD_nonce_length(cipher); + qc->client_in_iv.len = EVP_AEAD_nonce_length(cipher); #else - qc->client_in_iv.len = EVP_CIPHER_iv_length(cipher); + qc->client_in_iv.len = EVP_CIPHER_iv_length(cipher); #endif - qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); - if (qc->client_in_iv.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); + if (qc->client_in_iv.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->client_in_iv.len; - hkdfl[2] = sizeof("tls13 quic iv") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; + hkdfl[1] = qc->client_in_iv.len; + hkdfl[2] = sizeof("tls13 quic iv") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_iv.data, qc->client_in_iv.len, - digest, qc->client_in.data, qc->client_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_iv) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->client_in_iv.data, qc->client_in_iv.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(client_in_iv) failed"); + ngx_http_close_connection(c); + return; + } - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_hp.len = EVP_AEAD_key_length(cipher); + qc->client_in_hp.len = EVP_AEAD_key_length(cipher); #else - qc->client_in_hp.len = EVP_CIPHER_key_length(cipher); + qc->client_in_hp.len = EVP_CIPHER_key_length(cipher); #endif - qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); - if (qc->client_in_hp.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); + if (qc->client_in_hp.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->client_in_hp.len; - hkdfl[2] = sizeof("tls13 quic hp") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; + hkdfl[1] = qc->client_in_hp.len; + hkdfl[2] = sizeof("tls13 quic hp") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_hp.data, qc->client_in_hp.len, - digest, qc->client_in.data, qc->client_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_hp) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->client_in_hp.data, qc->client_in_hp.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(client_in_hp) failed"); + ngx_http_close_connection(c); + return; + } #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { @@ -983,121 +982,119 @@ ngx_http_quic_handshake(ngx_event_t *rev) // server initial - /* draft-ietf-quic-tls-23#section-5.2 */ + /* draft-ietf-quic-tls-23#section-5.2 */ - qc->server_in.len = SHA256_DIGEST_LENGTH; - qc->server_in.data = ngx_pnalloc(c->pool, qc->server_in.len); - if (qc->server_in.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->server_in.len = SHA256_DIGEST_LENGTH; + qc->server_in.data = ngx_pnalloc(c->pool, qc->server_in.len); + if (qc->server_in.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 server in") - 1 + 1; - hkdfl[0] = 0; - hkdfl[1] = qc->server_in.len; - hkdfl[2] = sizeof("tls13 server in") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 server in", - sizeof("tls13 server in") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 server in") - 1 + 1; + hkdfl[0] = 0; + hkdfl[1] = qc->server_in.len; + hkdfl[2] = sizeof("tls13 server in") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 server in", sizeof("tls13 server in") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->server_in.data, qc->server_in.len, - digest, is, is_len, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->server_in.data, qc->server_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(server_in) failed"); + ngx_http_close_connection(c); + return; + } - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_key.len = EVP_AEAD_key_length(cipher); + qc->server_in_key.len = EVP_AEAD_key_length(cipher); #else - qc->server_in_key.len = EVP_CIPHER_key_length(cipher); + qc->server_in_key.len = EVP_CIPHER_key_length(cipher); #endif - qc->server_in_key.data = ngx_pnalloc(c->pool, qc->server_in_key.len); - if (qc->server_in_key.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->server_in_key.data = ngx_pnalloc(c->pool, qc->server_in_key.len); + if (qc->server_in_key.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->server_in_key.len; - hkdfl[2] = sizeof("tls13 quic key") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic key", - sizeof("tls13 quic key") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; + hkdfl[1] = qc->server_in_key.len; + hkdfl[2] = sizeof("tls13 quic key") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_key.data, qc->server_in_key.len, - digest, qc->server_in.data, qc->server_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_key) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->server_in_key.data, qc->server_in_key.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(server_in_key) failed"); + ngx_http_close_connection(c); + return; + } #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_iv.len = EVP_AEAD_nonce_length(cipher); + qc->server_in_iv.len = EVP_AEAD_nonce_length(cipher); #else - qc->server_in_iv.len = EVP_CIPHER_iv_length(cipher); + qc->server_in_iv.len = EVP_CIPHER_iv_length(cipher); #endif - qc->server_in_iv.data = ngx_pnalloc(c->pool, qc->server_in_iv.len); - if (qc->server_in_iv.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->server_in_iv.data = ngx_pnalloc(c->pool, qc->server_in_iv.len); + if (qc->server_in_iv.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->server_in_iv.len; - hkdfl[2] = sizeof("tls13 quic iv") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; + hkdfl[1] = qc->server_in_iv.len; + hkdfl[2] = sizeof("tls13 quic iv") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_iv.data, qc->server_in_iv.len, - digest, qc->server_in.data, qc->server_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_iv) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->server_in_iv.data, qc->server_in_iv.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(server_in_iv) failed"); + ngx_http_close_connection(c); + return; + } - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_hp.len = EVP_AEAD_key_length(cipher); + qc->server_in_hp.len = EVP_AEAD_key_length(cipher); #else - qc->server_in_hp.len = EVP_CIPHER_key_length(cipher); + qc->server_in_hp.len = EVP_CIPHER_key_length(cipher); #endif - qc->server_in_hp.data = ngx_pnalloc(c->pool, qc->server_in_hp.len); - if (qc->server_in_hp.data == NULL) { - ngx_http_close_connection(c); - return; - } + qc->server_in_hp.data = ngx_pnalloc(c->pool, qc->server_in_hp.len); + if (qc->server_in_hp.data == NULL) { + ngx_http_close_connection(c); + return; + } - hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->server_in_hp.len; - hkdfl[2] = sizeof("tls13 quic hp") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); - *p = '\0'; + hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; + hkdfl[1] = qc->server_in_hp.len; + hkdfl[2] = sizeof("tls13 quic hp") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); + *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_hp.data, qc->server_in_hp.len, - digest, qc->server_in.data, qc->server_in.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_hp) failed"); - ngx_http_close_connection(c); - return; - } + if (ngx_hkdf_expand(qc->server_in_hp.data, qc->server_in_hp.len, + digest, qc->server_in.data, qc->server_in.len, + hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "ngx_hkdf_expand(server_in_hp) failed"); + ngx_http_close_connection(c); + return; + } #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { @@ -1214,8 +1211,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) qc->client_in_key.len, EVP_AEAD_DEFAULT_TAG_LENGTH); if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_AEAD_CTX_new() failed"); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); ngx_http_close_connection(c); return; } @@ -1342,7 +1338,8 @@ ngx_http_quic_handshake(ngx_event_t *rev) uint64_t crypto_len = ngx_quic_parse_int(&crypto); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic initial packet CRYPTO length: %uL pp:%p:%p", crypto_len, cleartext, crypto); + "quic initial packet CRYPTO length: %uL pp:%p:%p", + crypto_len, cleartext, crypto); sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); @@ -1373,10 +1370,10 @@ ngx_http_quic_handshake(ngx_event_t *rev) SSL_quic_read_level(c->ssl->connection), crypto, crypto_len)) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "SSL_provide_quic_data() failed"); - ngx_http_close_connection(c); - return; + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "SSL_provide_quic_data() failed"); + ngx_http_close_connection(c); + return; } n = SSL_do_handshake(c->ssl->connection); -- cgit v1.2.3 From aba1768d94a834dac08c8252576ca5279d4108ef Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: QUIC handshake handler, draft 24 bump. --- src/event/ngx_event_openssl.c | 2 +- src/http/ngx_http_request.c | 356 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 354 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index b072caf0f..f0cfc1933 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -534,7 +534,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, *p++ = 0xff; *p++ = 0x00; *p++ = 0x00; - *p++ = 0x17; + *p++ = 0x18; *p++ = qc->scid.len; p = ngx_cpymem(p, qc->scid.data, qc->scid.len); *p++ = qc->dcid.len; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 389d7488f..b8bca5547 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -64,6 +64,7 @@ static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); static void ngx_http_quic_handshake(ngx_event_t *rev); +static void ngx_http_quic_handshake_handler(ngx_event_t *rev); #endif @@ -706,7 +707,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic flags:%xi version:%xD", flags, version); - if (version != 0xff000017) { + if (version != 0xff000018) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); ngx_http_close_connection(c); return; @@ -742,7 +743,14 @@ ngx_http_quic_handshake(ngx_event_t *rev) ngx_memcpy(qc->token.data, b->pos, qc->token.len); b->pos += qc->token.len; - uint64_t plen = ngx_quic_parse_int(&b->pos); + ngx_int_t plen = ngx_quic_parse_int(&b->pos); + + if (plen > b->last - b->pos) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated initial packet"); + ngx_http_close_connection(c); + return; + } + /* draft-ietf-quic-tls-23#section-5.4.2: * the Packet Number field is assumed to be 4 bytes long * draft-ietf-quic-tls-23#section-5.4.[34]: @@ -1396,11 +1404,353 @@ ngx_http_quic_handshake(ngx_event_t *rev) (int) SSL_quic_read_level(c->ssl->connection), (int) SSL_quic_write_level(c->ssl->connection)); - ngx_http_close_connection(c); + if (!rev->timer_set) { + ngx_add_timer(rev, c->listening->post_accept_timeout); + } + + rev->handler = ngx_http_quic_handshake_handler; return; } +static void +ngx_http_quic_handshake_handler(ngx_event_t *rev) +{ + size_t m; + ssize_t n; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + u_char buf[4096], b[512], *p; + + c = rev->data; + qc = c->quic; + p = b; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + n = c->recv(c, b, sizeof(b)); + + if (n == NGX_AGAIN) { + return; + } + + if (n == NGX_ERROR) { + c->read->eof = 1; + ngx_http_close_connection(c); + return; + } + + m = ngx_hex_dump(buf, b, n) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic handshake handler: %*s, len: %uz", m, buf, n); + + /* XXX bug-for-bug compat - assuming initial ack in handshake pkt */ + + if ((p[0] & 0xf0) != 0xe0) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid packet type"); + ngx_http_close_connection(c); + return; + } + + ngx_int_t flags = *p++; + uint32_t version = ngx_http_v2_parse_uint32(p); + p += 4; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic flags:%xi version:%xD", flags, version); + + if (version != 0xff000018) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); + ngx_http_close_connection(c); + return; + } + + if (*p++ != qc->dcid.len) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcidl"); + ngx_http_close_connection(c); + return; + } + + if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcid"); + ngx_http_close_connection(c); + return; + } + + p += qc->dcid.len; + + if (*p++ != qc->scid.len) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scidl"); + ngx_http_close_connection(c); + return; + } + + if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scid"); + ngx_http_close_connection(c); + return; + } + + p += qc->scid.len; + + ngx_int_t plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet length: %d", plen); + + if (plen > b + n - p) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated handshake packet"); + ngx_http_close_connection(c); + return; + } + + u_char *sample = p + 4; + + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic sample: %*s", m, buf); + +// header protection + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + uint8_t mask[16]; + int outlen; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, + qc->client_hs_hp.data, NULL) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_CIPHER_CTX_free(ctx); + + u_char clearflags = flags ^ (mask[0] & 0x0f); + ngx_int_t pnl = (clearflags & 0x03) + 1; + uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, mask, 5) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic mask: %*s", m, buf); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic clear flags: %xi", clearflags); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + } +#endif + +// packet protection + + ngx_str_t ciphertext; + ciphertext.data = p; + ciphertext.len = plen - pnl; + + ngx_str_t ad; + ad.len = p - b; + ad.data = ngx_pnalloc(c->pool, ad.len); + if (ad.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(ad.data, b, ad.len); + ad.data[0] = clearflags; + ad.data[ad.len - pnl] = (u_char)pn; + + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs_iv); + nonce[11] ^= pn; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic nonce: %*s, len: %uz", m, buf, 12); + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic ad: %*s, len: %uz", m, buf, ad.len); + } +#endif + +#ifdef OPENSSL_IS_BORINGSSL + const EVP_AEAD *cipher; +#else + const EVP_CIPHER *cipher; +#endif + + u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic ssl cipher: %s", name); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { +#ifdef OPENSSL_IS_BORINGSSL + cipher = EVP_aead_aes_128_gcm(); +#else + cipher = EVP_aes_128_gcm(); +#endif + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { +#ifdef OPENSSL_IS_BORINGSSL + cipher = EVP_aead_aes_256_gcm(); +#else + cipher = EVP_aes_256_gcm(); +#endif + + } else { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "unexpected cipher"); + ngx_http_close_connection(c); + return; + } + + + uint8_t cleartext[1600]; + size_t cleartext_len; + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, + qc->client_hs_key.data, + qc->client_hs_key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (aead == NULL) { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), + nonce, qc->client_hs_iv.len, ciphertext.data, + ciphertext.len, ad.data, ad.len) + != 1) + { + EVP_AEAD_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_AEAD_CTX_open() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_AEAD_CTX_free(aead); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *aead; + + aead = EVP_CIPHER_CTX_new(); + if (aead == NULL) { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_CIPHER_CTX_new() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_hs_iv.len, + NULL) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_hs_key.data, nonce) + != 1) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptUpdate(aead, NULL, &len, ad.data, ad.len) != 1) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptUpdate(aead, cleartext, &len, ciphertext.data, + ciphertext.len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + cleartext_len = len; + tag = ciphertext.data + ciphertext.len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, + tag) + == 0) + { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + ngx_http_close_connection(c); + return; + } + + if (EVP_DecryptFinal_ex(aead, cleartext + len, &len) <= 0) { + EVP_CIPHER_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptFinal_ex failed"); + ngx_http_close_connection(c); + return; + } + + cleartext_len += len; + + EVP_CIPHER_CTX_free(aead); +#endif + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet payload: %*s%s, len: %uz", + m, buf, m < 512 ? "" : "...", cleartext_len); + } +#endif + + ngx_http_close_connection(c); +} + + static void ngx_http_ssl_handshake(ngx_event_t *rev) { -- cgit v1.2.3 From 27e5e87784f1464e9c40f31e8c119918073fb90b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: Introduced ngx_quic_secret_t. --- src/event/ngx_event_openssl.c | 52 ++++++------- src/event/ngx_event_quic.h | 51 +++++------- src/http/ngx_http_request.c | 176 +++++++++++++++++++++--------------------- 3 files changed, 132 insertions(+), 147 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index f0cfc1933..7938ae098 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -168,34 +168,34 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, switch (level) { case ssl_encryption_handshake: - rlen = &c->quic->client_hs.len; - rsec = &c->quic->client_hs.data; - wlen = &c->quic->server_hs.len; - wsec = &c->quic->server_hs.data; + rlen = &c->quic->client_hs.secret.len; + rsec = &c->quic->client_hs.secret.data; + wlen = &c->quic->server_hs.secret.len; + wsec = &c->quic->server_hs.secret.data; - client_key = &c->quic->client_hs_key; - client_iv = &c->quic->client_hs_iv; - client_hp = &c->quic->client_hs_hp; + client_key = &c->quic->client_hs.key; + client_iv = &c->quic->client_hs.iv; + client_hp = &c->quic->client_hs.hp; - server_key = &c->quic->server_hs_key; - server_iv = &c->quic->server_hs_iv; - server_hp = &c->quic->server_hs_hp; + server_key = &c->quic->server_hs.key; + server_iv = &c->quic->server_hs.iv; + server_hp = &c->quic->server_hs.hp; break; case ssl_encryption_application: - rlen = &c->quic->client_ad.len; - rsec = &c->quic->client_ad.data; - wlen = &c->quic->server_ad.len; - wsec = &c->quic->server_ad.data; + rlen = &c->quic->client_ad.secret.len; + rsec = &c->quic->client_ad.secret.data; + wlen = &c->quic->server_ad.secret.len; + wsec = &c->quic->server_ad.secret.data; - client_key = &c->quic->client_ad_key; - client_iv = &c->quic->client_ad_iv; - client_hp = &c->quic->client_ad_hp; + client_key = &c->quic->client_ad.key; + client_iv = &c->quic->client_ad.iv; + client_hp = &c->quic->client_ad.hp; - server_key = &c->quic->server_ad_key; - server_iv = &c->quic->server_ad_iv; - server_hp = &c->quic->server_ad_hp; + server_key = &c->quic->server_ad.key; + server_iv = &c->quic->server_ad.iv; + server_hp = &c->quic->server_ad.hp; break; @@ -474,15 +474,15 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, switch (level) { case ssl_encryption_initial: - server_key = &qc->server_in_key; - server_iv = &qc->server_in_iv; - server_hp = &qc->server_in_hp; + server_key = &qc->server_in.key; + server_iv = &qc->server_in.iv; + server_hp = &qc->server_in.hp; break; case ssl_encryption_handshake: - server_key = &qc->server_hs_key; - server_iv = &qc->server_hs_iv; - server_hp = &qc->server_hs_hp; + server_key = &qc->server_hs.key; + server_iv = &qc->server_hs.iv; + server_hp = &qc->server_hs.hp; break; default: diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 236439b06..86f2f58bd 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,40 +11,25 @@ #include +typedef struct { + ngx_str_t secret; + ngx_str_t key; + ngx_str_t iv; + ngx_str_t hp; +} ngx_quic_secret_t; + + struct ngx_quic_connection_s { - ngx_str_t scid; - ngx_str_t dcid; - ngx_str_t token; - - ngx_str_t client_in; - ngx_str_t client_in_key; - ngx_str_t client_in_iv; - ngx_str_t client_in_hp; - - ngx_str_t server_in; - ngx_str_t server_in_key; - ngx_str_t server_in_iv; - ngx_str_t server_in_hp; - - ngx_str_t client_hs; - ngx_str_t client_hs_key; - ngx_str_t client_hs_iv; - ngx_str_t client_hs_hp; - - ngx_str_t server_hs; - ngx_str_t server_hs_key; - ngx_str_t server_hs_iv; - ngx_str_t server_hs_hp; - - ngx_str_t client_ad; - ngx_str_t client_ad_key; - ngx_str_t client_ad_iv; - ngx_str_t client_ad_hp; - - ngx_str_t server_ad; - ngx_str_t server_ad_key; - ngx_str_t server_ad_iv; - ngx_str_t server_ad_hp; + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; + + ngx_quic_secret_t client_in; + ngx_quic_secret_t client_hs; + ngx_quic_secret_t client_ad; + ngx_quic_secret_t server_in; + ngx_quic_secret_t server_hs; + ngx_quic_secret_t server_ad; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index b8bca5547..7f62643c1 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -831,9 +831,9 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* draft-ietf-quic-tls-23#section-5.2 */ - qc->client_in.len = SHA256_DIGEST_LENGTH; - qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len); - if (qc->client_in.data == NULL) { + qc->client_in.secret.len = SHA256_DIGEST_LENGTH; + qc->client_in.secret.data = ngx_pnalloc(c->pool, qc->client_in.secret.len); + if (qc->client_in.secret.data == NULL) { ngx_http_close_connection(c); return; } @@ -841,7 +841,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; bzero(hkdfl, sizeof(hkdfl)); hkdfl[0] = 0; - hkdfl[1] = qc->client_in.len; + hkdfl[1] = qc->client_in.secret.len; hkdfl[2] = sizeof("tls13 client in") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 client in", sizeof("tls13 client in") - 1); *p = '\0'; @@ -856,7 +856,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) m, buf, sizeof(hkdfl)); #endif - if (ngx_hkdf_expand(qc->client_in.data, qc->client_in.len, + if (ngx_hkdf_expand(qc->client_in.secret.data, qc->client_in.secret.len, digest, is, is_len, hkdfl, hkdfl_len) != NGX_OK) { @@ -876,57 +876,57 @@ ngx_http_quic_handshake(ngx_event_t *rev) #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_key.len = EVP_AEAD_key_length(cipher); + qc->client_in.key.len = EVP_AEAD_key_length(cipher); #else - qc->client_in_key.len = EVP_CIPHER_key_length(cipher); + qc->client_in.key.len = EVP_CIPHER_key_length(cipher); #endif - qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); - if (qc->client_in_key.data == NULL) { + qc->client_in.key.data = ngx_pnalloc(c->pool, qc->client_in.key.len); + if (qc->client_in.key.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->client_in_key.len; + hkdfl[1] = qc->client_in.key.len; hkdfl[2] = sizeof("tls13 quic key") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_key.data, qc->client_in_key.len, - digest, qc->client_in.data, qc->client_in.len, + if (ngx_hkdf_expand(qc->client_in.key.data, qc->client_in.key.len, + digest, qc->client_in.secret.data, qc->client_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_key) failed"); + "ngx_hkdf_expand(client_in.key) failed"); ngx_http_close_connection(c); return; } #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_iv.len = EVP_AEAD_nonce_length(cipher); + qc->client_in.iv.len = EVP_AEAD_nonce_length(cipher); #else - qc->client_in_iv.len = EVP_CIPHER_iv_length(cipher); + qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); #endif - qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); - if (qc->client_in_iv.data == NULL) { + qc->client_in.iv.data = ngx_pnalloc(c->pool, qc->client_in.iv.len); + if (qc->client_in.iv.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->client_in_iv.len; + hkdfl[1] = qc->client_in.iv.len; hkdfl[2] = sizeof("tls13 quic iv") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_iv.data, qc->client_in_iv.len, - digest, qc->client_in.data, qc->client_in.len, + if (ngx_hkdf_expand(qc->client_in.iv.data, qc->client_in.iv.len, + digest, qc->client_in.secret.data, qc->client_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_iv) failed"); + "ngx_hkdf_expand(client_in.iv) failed"); ngx_http_close_connection(c); return; } @@ -934,57 +934,57 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ #ifdef OPENSSL_IS_BORINGSSL - qc->client_in_hp.len = EVP_AEAD_key_length(cipher); + qc->client_in.hp.len = EVP_AEAD_key_length(cipher); #else - qc->client_in_hp.len = EVP_CIPHER_key_length(cipher); + qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); #endif - qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); - if (qc->client_in_hp.data == NULL) { + qc->client_in.hp.data = ngx_pnalloc(c->pool, qc->client_in.hp.len); + if (qc->client_in.hp.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->client_in_hp.len; + hkdfl[1] = qc->client_in.hp.len; hkdfl[2] = sizeof("tls13 quic hp") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->client_in_hp.data, qc->client_in_hp.len, - digest, qc->client_in.data, qc->client_in.len, + if (ngx_hkdf_expand(qc->client_in.hp.data, qc->client_in.hp.len, + digest, qc->client_in.secret.data, qc->client_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in_hp) failed"); + "ngx_hkdf_expand(client_in.hp) failed"); ngx_http_close_connection(c); return; } #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->client_in.data, qc->client_in.len) - buf; + m = ngx_hex_dump(buf, qc->client_in.secret.data, qc->client_in.secret.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic client initial secret: %*s, len: %uz", - m, buf, qc->client_in.len); + m, buf, qc->client_in.secret.len); - m = ngx_hex_dump(buf, qc->client_in_key.data, qc->client_in_key.len) + m = ngx_hex_dump(buf, qc->client_in.key.data, qc->client_in.key.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic client key: %*s, len: %uz", - m, buf, qc->client_in_key.len); + m, buf, qc->client_in.key.len); - m = ngx_hex_dump(buf, qc->client_in_iv.data, qc->client_in_iv.len) + m = ngx_hex_dump(buf, qc->client_in.iv.data, qc->client_in.iv.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic client iv: %*s, len: %uz", - m, buf, qc->client_in_iv.len); + m, buf, qc->client_in.iv.len); - m = ngx_hex_dump(buf, qc->client_in_hp.data, qc->client_in_hp.len) + m = ngx_hex_dump(buf, qc->client_in.hp.data, qc->client_in.hp.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic client hp: %*s, len: %uz", - m, buf, qc->client_in_hp.len); + m, buf, qc->client_in.hp.len); } #endif @@ -992,21 +992,21 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* draft-ietf-quic-tls-23#section-5.2 */ - qc->server_in.len = SHA256_DIGEST_LENGTH; - qc->server_in.data = ngx_pnalloc(c->pool, qc->server_in.len); - if (qc->server_in.data == NULL) { + qc->server_in.secret.len = SHA256_DIGEST_LENGTH; + qc->server_in.secret.data = ngx_pnalloc(c->pool, qc->server_in.secret.len); + if (qc->server_in.secret.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 server in") - 1 + 1; hkdfl[0] = 0; - hkdfl[1] = qc->server_in.len; + hkdfl[1] = qc->server_in.secret.len; hkdfl[2] = sizeof("tls13 server in") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 server in", sizeof("tls13 server in") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->server_in.data, qc->server_in.len, + if (ngx_hkdf_expand(qc->server_in.secret.data, qc->server_in.secret.len, digest, is, is_len, hkdfl, hkdfl_len) != NGX_OK) { @@ -1019,57 +1019,57 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_key.len = EVP_AEAD_key_length(cipher); + qc->server_in.key.len = EVP_AEAD_key_length(cipher); #else - qc->server_in_key.len = EVP_CIPHER_key_length(cipher); + qc->server_in.key.len = EVP_CIPHER_key_length(cipher); #endif - qc->server_in_key.data = ngx_pnalloc(c->pool, qc->server_in_key.len); - if (qc->server_in_key.data == NULL) { + qc->server_in.key.data = ngx_pnalloc(c->pool, qc->server_in.key.len); + if (qc->server_in.key.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->server_in_key.len; + hkdfl[1] = qc->server_in.key.len; hkdfl[2] = sizeof("tls13 quic key") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_key.data, qc->server_in_key.len, - digest, qc->server_in.data, qc->server_in.len, + if (ngx_hkdf_expand(qc->server_in.key.data, qc->server_in.key.len, + digest, qc->server_in.secret.data, qc->server_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_key) failed"); + "ngx_hkdf_expand(server_in.key) failed"); ngx_http_close_connection(c); return; } #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_iv.len = EVP_AEAD_nonce_length(cipher); + qc->server_in.iv.len = EVP_AEAD_nonce_length(cipher); #else - qc->server_in_iv.len = EVP_CIPHER_iv_length(cipher); + qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); #endif - qc->server_in_iv.data = ngx_pnalloc(c->pool, qc->server_in_iv.len); - if (qc->server_in_iv.data == NULL) { + qc->server_in.iv.data = ngx_pnalloc(c->pool, qc->server_in.iv.len); + if (qc->server_in.iv.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->server_in_iv.len; + hkdfl[1] = qc->server_in.iv.len; hkdfl[2] = sizeof("tls13 quic iv") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_iv.data, qc->server_in_iv.len, - digest, qc->server_in.data, qc->server_in.len, + if (ngx_hkdf_expand(qc->server_in.iv.data, qc->server_in.iv.len, + digest, qc->server_in.secret.data, qc->server_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_iv) failed"); + "ngx_hkdf_expand(server_in.iv) failed"); ngx_http_close_connection(c); return; } @@ -1077,57 +1077,57 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ #ifdef OPENSSL_IS_BORINGSSL - qc->server_in_hp.len = EVP_AEAD_key_length(cipher); + qc->server_in.hp.len = EVP_AEAD_key_length(cipher); #else - qc->server_in_hp.len = EVP_CIPHER_key_length(cipher); + qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); #endif - qc->server_in_hp.data = ngx_pnalloc(c->pool, qc->server_in_hp.len); - if (qc->server_in_hp.data == NULL) { + qc->server_in.hp.data = ngx_pnalloc(c->pool, qc->server_in.hp.len); + if (qc->server_in.hp.data == NULL) { ngx_http_close_connection(c); return; } hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->server_in_hp.len; + hkdfl[1] = qc->server_in.hp.len; hkdfl[2] = sizeof("tls13 quic hp") - 1; p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); *p = '\0'; - if (ngx_hkdf_expand(qc->server_in_hp.data, qc->server_in_hp.len, - digest, qc->server_in.data, qc->server_in.len, + if (ngx_hkdf_expand(qc->server_in.hp.data, qc->server_in.hp.len, + digest, qc->server_in.secret.data, qc->server_in.secret.len, hkdfl, hkdfl_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in_hp) failed"); + "ngx_hkdf_expand(server_in.hp) failed"); ngx_http_close_connection(c); return; } #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->server_in.data, qc->server_in.len) - buf; + m = ngx_hex_dump(buf, qc->server_in.secret.data, qc->server_in.secret.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic server initial secret: %*s, len: %uz", - m, buf, qc->server_in.len); + m, buf, qc->server_in.secret.len); - m = ngx_hex_dump(buf, qc->server_in_key.data, qc->server_in_key.len) + m = ngx_hex_dump(buf, qc->server_in.key.data, qc->server_in.key.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic server key: %*s, len: %uz", - m, buf, qc->server_in_key.len); + m, buf, qc->server_in.key.len); - m = ngx_hex_dump(buf, qc->server_in_iv.data, qc->server_in_iv.len) + m = ngx_hex_dump(buf, qc->server_in.iv.data, qc->server_in.iv.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic server iv: %*s, len: %uz", - m, buf, qc->server_in_iv.len); + m, buf, qc->server_in.iv.len); - m = ngx_hex_dump(buf, qc->server_in_hp.data, qc->server_in_hp.len) + m = ngx_hex_dump(buf, qc->server_in.hp.data, qc->server_in.hp.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic server hp: %*s, len: %uz", - m, buf, qc->server_in_hp.len); + m, buf, qc->server_in.hp.len); } #endif @@ -1138,7 +1138,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) int outlen; if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, - qc->client_in_hp.data, NULL) + qc->client_in.hp.data, NULL) != 1) { EVP_CIPHER_CTX_free(ctx); @@ -1195,7 +1195,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) ad.data[0] = clearflags; ad.data[ad.len - pnl] = (u_char)pn; - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in_iv); + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); nonce[11] ^= pn; #if (NGX_DEBUG) @@ -1215,8 +1215,8 @@ ngx_http_quic_handshake(ngx_event_t *rev) #ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, - qc->client_in_key.data, - qc->client_in_key.len, + qc->client_in.key.data, + qc->client_in.key.len, EVP_AEAD_DEFAULT_TAG_LENGTH); if (aead == NULL) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); @@ -1225,7 +1225,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) } if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), - nonce, qc->client_in_iv.len, ciphertext.data, + nonce, qc->client_in.iv.len, ciphertext.data, ciphertext.len, ad.data, ad.len) != 1) { @@ -1256,7 +1256,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) return; } - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_in_iv.len, + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_in.iv.len, NULL) == 0) { @@ -1267,7 +1267,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) return; } - if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_in_key.data, nonce) + if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_in.key.data, nonce) != 1) { EVP_CIPHER_CTX_free(aead); @@ -1527,7 +1527,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) int outlen; if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, - qc->client_hs_hp.data, NULL) + qc->client_hs.hp.data, NULL) != 1) { EVP_CIPHER_CTX_free(ctx); @@ -1581,7 +1581,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) ad.data[0] = clearflags; ad.data[ad.len - pnl] = (u_char)pn; - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs_iv); + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv); nonce[11] ^= pn; #if (NGX_DEBUG) @@ -1634,8 +1634,8 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) #ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, - qc->client_hs_key.data, - qc->client_hs_key.len, + qc->client_hs.key.data, + qc->client_hs.key.len, EVP_AEAD_DEFAULT_TAG_LENGTH); if (aead == NULL) { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); @@ -1644,7 +1644,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) } if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), - nonce, qc->client_hs_iv.len, ciphertext.data, + nonce, qc->client_hs.iv.len, ciphertext.data, ciphertext.len, ad.data, ad.len) != 1) { @@ -1675,7 +1675,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_hs_iv.len, + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_hs.iv.len, NULL) == 0) { @@ -1686,7 +1686,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } - if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_hs_key.data, nonce) + if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_hs.key.data, nonce) != 1) { EVP_CIPHER_CTX_free(aead); -- cgit v1.2.3 From 8c90e6f440f432908ad70002bb6acb1f9aec1758 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: Transport parameters stub, to complete handshake. --- src/http/ngx_http_request.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7f62643c1..318933a40 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1358,6 +1358,17 @@ ngx_http_quic_handshake(ngx_event_t *rev) return; } + static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03"; + + if (SSL_set_quic_transport_params(c->ssl->connection, params, + sizeof(params)) == 0) + { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, + "SSL_set_quic_transport_params() failed"); + ngx_http_close_connection(c); + return; + } + n = SSL_do_handshake(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); @@ -1747,7 +1758,6 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) } #endif - ngx_http_close_connection(c); } -- cgit v1.2.3 From 56eead6176d2d63392fc82668b2233dfadbae33e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: AEAD routines, introduced ngx_quic_tls_open()/ngx_quic_tls_seal(). --- src/event/ngx_event_openssl.c | 166 ++++++-------------------- src/event/ngx_event_quic.c | 208 ++++++++++++++++++++++++++++++++ src/event/ngx_event_quic.h | 16 +++ src/http/ngx_http_request.c | 270 +++++------------------------------------- 4 files changed, 284 insertions(+), 376 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 7938ae098..561819e0b 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -452,17 +452,13 @@ static int quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char buf[2048], *p, *ciphertext, *clear, *ad, *name; - size_t ad_len, clear_len; - ngx_int_t m; - ngx_str_t *server_key, *server_iv, *server_hp; -#ifdef OPENSSL_IS_BORINGSSL - const EVP_AEAD *cipher; -#else - const EVP_CIPHER *cipher; -#endif - ngx_connection_t *c; - ngx_quic_connection_t *qc; + u_char buf[2048], *p, *name; + ngx_int_t m; + ngx_str_t in, out, ad; + ngx_quic_secret_t *secret; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + const ngx_aead_cipher_t *cipher; static int pn = 0; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -474,15 +470,11 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, switch (level) { case ssl_encryption_initial: - server_key = &qc->server_in.key; - server_iv = &qc->server_in.iv; - server_hp = &qc->server_in.hp; + secret = &qc->server_in; break; case ssl_encryption_handshake: - server_key = &qc->server_hs.key; - server_iv = &qc->server_hs.iv; - server_hp = &qc->server_hs.hp; + secret = &qc->server_hs; break; default: @@ -494,12 +486,12 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "quic_add_handshake_data: %*s%s, len: %uz, level:%d", m, buf, len < 2048 ? "" : "...", len, (int) level); - clear = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); - if (clear == 0) { + in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); + if (in.data == 0) { return 0; } - p = clear; + p = in.data; ngx_quic_build_int(&p, 6); // crypto frame ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, len); @@ -513,19 +505,19 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_build_int(&p, 0); } - clear_len = p - clear; - size_t ciphertext_len = clear_len + 16 /*expansion*/; + in.len = p - in.data; + out.len = in.len + EVP_GCM_TLS_TAG_LEN; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz", - clear_len, ciphertext_len); + in.len, out.len); - ad = ngx_alloc(346 /*max header*/, c->log); - if (ad == 0) { + ad.data = ngx_alloc(346 /*max header*/, c->log); + if (ad.data == 0) { return 0; } - p = ad; + p = ad.data; if (level == ssl_encryption_initial) { *p++ = 0xc0; // initial, packet number len } else if (level == ssl_encryption_handshake) { @@ -542,7 +534,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, if (level == ssl_encryption_initial) { ngx_quic_build_int(&p, 0); // token length } - ngx_quic_build_int(&p, ciphertext_len + 1); // length (inc. pnl) + ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) u_char *pnp = p; if (level == ssl_encryption_initial) { @@ -552,12 +544,12 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, *p++ = pn++; } - ad_len = p - ad; + ad.len = p - ad.data; - m = ngx_hex_dump(buf, (u_char *) ad, ad_len) - buf; + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data ad: %*s, len: %uz", - m, buf, ad_len); + m, buf, ad.len); name = (u_char *) SSL_get_cipher(ssl_conn); @@ -582,18 +574,12 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - - ciphertext = ngx_alloc(ciphertext_len, c->log); - if (ciphertext == 0) { - return 0; - } - - uint8_t *nonce = ngx_pstrdup(c->pool, server_iv); + uint8_t *nonce = ngx_pstrdup(c->pool, &secret->iv); if (level == ssl_encryption_handshake) { nonce[11] ^= (pn - 1); } - m = ngx_hex_dump(buf, (u_char *) server_iv->data, 12) - buf; + m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data sample: server_iv %*s", m, buf); @@ -602,102 +588,17 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "quic_add_handshake_data sample: n=%d nonce %*s", pn - 1, m, buf); - -#ifdef OPENSSL_IS_BORINGSSL - size_t out_len; - EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, - server_key->data, - server_key->len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - - if (EVP_AEAD_CTX_seal(aead, ciphertext, &out_len, ciphertext_len, - nonce, server_iv->len, - clear, clear_len, ad, ad_len) - != 1) - { - EVP_AEAD_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_AEAD_CTX_seal() failed"); - return 0; - } - - EVP_AEAD_CTX_free(aead); -#else - int out_len; - EVP_CIPHER_CTX *aead; - - aead = EVP_CIPHER_CTX_new(); - if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); - return 0; - } - - if (EVP_EncryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); - return 0; - } - - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, server_iv->len, NULL) - == 0) + if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); return 0; } - if (EVP_EncryptInit_ex(aead, NULL, NULL, server_key->data, nonce) - != 1) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); - return 0; - } - - if (EVP_EncryptUpdate(aead, NULL, &out_len, ad, ad_len) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); - return 0; - } - - if (EVP_EncryptUpdate(aead, ciphertext, &out_len, clear, clear_len) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); - return 0; - } - - ciphertext_len = out_len; - - if (EVP_EncryptFinal_ex(aead, ciphertext + out_len, &out_len) <= 0) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_ex failed"); - return 0; - } - - ciphertext_len += out_len; - - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, - ciphertext + clear_len) - == 0) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); - return 0; - } - - EVP_CIPHER_CTX_free(aead); - - out_len = ciphertext_len + EVP_GCM_TLS_TAG_LEN; -#endif - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - u_char *sample = ciphertext + 3; // pnl=0 + u_char *sample = &out.data[3]; // pnl=0 uint8_t mask[16]; int outlen; - if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, server_hp->data, NULL) + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, secret->hp.data, NULL) != 1) { EVP_CIPHER_CTX_free(ctx); @@ -725,25 +626,24 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "quic_add_handshake_data mask: %*s, len: %uz", m, buf, 16); - m = ngx_hex_dump(buf, (u_char *) server_hp->data, 16) - buf; + m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data hp_key: %*s, len: %uz", m, buf, 16); // header protection, pnl = 0 - ad[0] ^= mask[0] & 0x0f; + ad.data[0] ^= mask[0] & 0x0f; *pnp ^= mask[1]; -printf("clear_len %ld ciphertext_len %ld out_len %ld ad_len %ld\n", -clear_len, ciphertext_len, (size_t) out_len, ad_len); +printf("clear_len %ld ciphertext_len %ld ad_len %ld\n", in.len, out.len, ad.len); - u_char *packet = ngx_alloc(ad_len + out_len, c->log); + u_char *packet = ngx_alloc(ad.len + out.len, c->log); if (packet == 0) { return 0; } - p = ngx_cpymem(packet, ad, ad_len); - p = ngx_cpymem(p, ciphertext, out_len); + p = ngx_cpymem(packet, ad.data, ad.len); + p = ngx_cpymem(p, out.data, out.len); m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a655e7ddc..106ca0d34 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -163,3 +163,211 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, return NGX_OK; } + + +ngx_int_t +ngx_quic_tls_open(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad) +{ + out->len = in->len - EVP_GCM_TLS_TAG_LEN; + out->data = ngx_pnalloc(c->pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, + in->len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + EVP_CIPHER_CTX_free(ctx); +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_tls_seal(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad) +{ + out->len = in->len + EVP_GCM_TLS_TAG_LEN; + out->data = ngx_pnalloc(c->pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, + out->data + in->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + return NGX_ERROR; + } + + EVP_CIPHER_CTX_free(ctx); + + out->len += EVP_GCM_TLS_TAG_LEN; +#endif + + return NGX_OK; +} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 86f2f58bd..c1b20f65b 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,6 +11,15 @@ #include +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_aead_cipher_t EVP_AEAD +#define NGX_QUIC_INITIAL_CIPHER EVP_aead_aes_128_gcm() +#else +#define ngx_aead_cipher_t EVP_CIPHER +#define NGX_QUIC_INITIAL_CIPHER EVP_aes_128_gcm() +#endif + + typedef struct { ngx_str_t secret; ngx_str_t key; @@ -44,5 +53,12 @@ ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len); +ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, + const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad); +ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, + const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad); + #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 318933a40..c9bddc6dd 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -783,27 +783,18 @@ ngx_http_quic_handshake(ngx_event_t *rev) // initial secret - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - const EVP_MD *digest; -#ifdef OPENSSL_IS_BORINGSSL - const EVP_AEAD *cipher; -#else - const EVP_CIPHER *cipher; -#endif + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + const EVP_MD *digest; + const ngx_aead_cipher_t *cipher; static const uint8_t salt[20] = "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; - digest = EVP_sha256(); - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ -#ifdef OPENSSL_IS_BORINGSSL - cipher = EVP_aead_aes_128_gcm(); -#else - cipher = EVP_aes_128_gcm(); -#endif + cipher = NGX_QUIC_INITIAL_CIPHER; + digest = EVP_sha256(); if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt, sizeof(salt)) @@ -1179,9 +1170,9 @@ ngx_http_quic_handshake(ngx_event_t *rev) // packet protection - ngx_str_t ciphertext; - ciphertext.data = b->pos; - ciphertext.len = plen - pnl; + ngx_str_t in; + in.data = b->pos; + in.len = plen - pnl; ngx_str_t ad; ad.len = b->pos - b->start; @@ -1210,144 +1201,44 @@ ngx_http_quic_handshake(ngx_event_t *rev) } #endif - uint8_t cleartext[1600]; - size_t cleartext_len; - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, - qc->client_in.key.data, - qc->client_in.key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), - nonce, qc->client_in.iv.len, ciphertext.data, - ciphertext.len, ad.data, ad.len) - != 1) - { - EVP_AEAD_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_AEAD_CTX_open() failed"); - ngx_http_close_connection(c); - return; - } - - EVP_AEAD_CTX_free(aead); -#else - int len; - u_char *tag; - EVP_CIPHER_CTX *aead; - - aead = EVP_CIPHER_CTX_new(); - if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_CIPHER_CTX_new() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); - ngx_http_close_connection(c); - return; - } + ngx_str_t out; - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_in.iv.len, - NULL) - == 0) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_in.key.data, nonce) - != 1) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptUpdate(aead, NULL, &len, ad.data, ad.len) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptUpdate(aead, cleartext, &len, ciphertext.data, - ciphertext.len - EVP_GCM_TLS_TAG_LEN) - != 1) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - cleartext_len = len; - tag = ciphertext.data + ciphertext.len - EVP_GCM_TLS_TAG_LEN; - - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, - tag) - == 0) + if (ngx_quic_tls_open(c, cipher, &qc->client_in, &out, nonce, &in, &ad) + != NGX_OK) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); ngx_http_close_connection(c); return; } - if (EVP_DecryptFinal_ex(aead, cleartext + len, &len) <= 0) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptFinal_ex failed"); - ngx_http_close_connection(c); - return; - } - - cleartext_len += len; - - EVP_CIPHER_CTX_free(aead); -#endif - #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; + m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", cleartext_len); + m, buf, m < 512 ? "" : "...", out.len); } #endif - if (cleartext[0] != 0x06) { + if (out.data[0] != 0x06) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected frame in initial packet"); ngx_http_close_connection(c); return; } - if (cleartext[1] != 0x00) { + if (out.data[1] != 0x00) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected CRYPTO offset in initial packet"); ngx_http_close_connection(c); return; } - uint8_t *crypto = &cleartext[2]; + uint8_t *crypto = &out.data[2]; uint64_t crypto_len = ngx_quic_parse_int(&crypto); ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic initial packet CRYPTO length: %uL pp:%p:%p", - crypto_len, cleartext, crypto); + crypto_len, out.data, crypto); sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); @@ -1466,8 +1357,6 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake handler: %*s, len: %uz", m, buf, n); - /* XXX bug-for-bug compat - assuming initial ack in handshake pkt */ - if ((p[0] & 0xf0) != 0xe0) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid packet type"); ngx_http_close_connection(c); @@ -1576,9 +1465,9 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) // packet protection - ngx_str_t ciphertext; - ciphertext.data = p; - ciphertext.len = plen - pnl; + ngx_str_t in; + in.data = p; + in.len = plen - pnl; ngx_str_t ad; ad.len = p - b; @@ -1607,11 +1496,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) } #endif -#ifdef OPENSSL_IS_BORINGSSL - const EVP_AEAD *cipher; -#else - const EVP_CIPHER *cipher; -#endif + const ngx_aead_cipher_t *cipher; u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, @@ -1639,122 +1524,21 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } + ngx_str_t out; - uint8_t cleartext[1600]; - size_t cleartext_len; - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(cipher, - qc->client_hs.key.data, - qc->client_hs.key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_AEAD_CTX_new() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), - nonce, qc->client_hs.iv.len, ciphertext.data, - ciphertext.len, ad.data, ad.len) - != 1) - { - EVP_AEAD_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_AEAD_CTX_open() failed"); - ngx_http_close_connection(c); - return; - } - - EVP_AEAD_CTX_free(aead); -#else - int len; - u_char *tag; - EVP_CIPHER_CTX *aead; - - aead = EVP_CIPHER_CTX_new(); - if (aead == NULL) { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_CIPHER_CTX_new() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptInit_ex(aead, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_IVLEN, qc->client_hs.iv.len, - NULL) - == 0) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptInit_ex(aead, NULL, NULL, qc->client_hs.key.data, nonce) - != 1) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptInit_ex() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptUpdate(aead, NULL, &len, ad.data, ad.len) != 1) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - if (EVP_DecryptUpdate(aead, cleartext, &len, ciphertext.data, - ciphertext.len - EVP_GCM_TLS_TAG_LEN) - != 1) - { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - cleartext_len = len; - tag = ciphertext.data + ciphertext.len - EVP_GCM_TLS_TAG_LEN; - - if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, - tag) - == 0) + if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad) + != NGX_OK) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); ngx_http_close_connection(c); return; } - if (EVP_DecryptFinal_ex(aead, cleartext + len, &len) <= 0) { - EVP_CIPHER_CTX_free(aead); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "EVP_DecryptFinal_ex failed"); - ngx_http_close_connection(c); - return; - } - - cleartext_len += len; - - EVP_CIPHER_CTX_free(aead); -#endif - #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; + m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", cleartext_len); + m, buf, m < 512 ? "" : "...", out.len); } #endif -- cgit v1.2.3 From a3620d469f2378420b52199b5da4fff9fa0b8995 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: QUIC header protection routines, introduced ngx_quic_tls_hp(). --- src/event/ngx_event_openssl.c | 19 +------------------ src/event/ngx_event_quic.c | 34 ++++++++++++++++++++++++++++++++++ src/event/ngx_event_quic.h | 3 +++ src/http/ngx_http_request.c | 42 ++++-------------------------------------- 4 files changed, 42 insertions(+), 56 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 561819e0b..7d12b9625 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -593,29 +593,12 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); u_char *sample = &out.data[3]; // pnl=0 uint8_t mask[16]; - int outlen; - - if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, secret->hp.data, NULL) - != 1) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_EncryptInit_ex() failed"); - return 0; - } - - if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_EncryptUpdate() failed"); + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) { return 0; } - EVP_CIPHER_CTX_free(ctx); - m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_add_handshake_data sample: %*s, len: %uz", diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 106ca0d34..b453d95f8 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -371,3 +371,37 @@ ngx_quic_tls_seal(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, return NGX_OK; } + + +ngx_int_t +ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, NULL) != 1) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); + goto failed; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, in, 16)) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); + goto failed; + } + + EVP_CIPHER_CTX_free(ctx); + + return NGX_OK; + +failed: + + EVP_CIPHER_CTX_free(ctx); + + return NGX_ERROR; +} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index c1b20f65b..1b900208b 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -60,5 +60,8 @@ ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); +ngx_int_t +ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in); #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index c9bddc6dd..8fbc20424 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1124,31 +1124,14 @@ ngx_http_quic_handshake(ngx_event_t *rev) // header protection - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); uint8_t mask[16]; - int outlen; - - if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, - qc->client_in.hp.data, NULL) - != 1) + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) + != NGX_OK) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_EncryptInit_ex() failed"); ngx_http_close_connection(c); return; } - if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_EncryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - EVP_CIPHER_CTX_free(ctx); - u_char clearflags = flags ^ (mask[0] & 0x0f); ngx_int_t pnl = (clearflags & 0x03) + 1; uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); @@ -1422,31 +1405,14 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) // header protection - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); uint8_t mask[16]; - int outlen; - - if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, - qc->client_hs.hp.data, NULL) - != 1) + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample) + != NGX_OK) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_EncryptInit_ex() failed"); ngx_http_close_connection(c); return; } - if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "EVP_EncryptUpdate() failed"); - ngx_http_close_connection(c); - return; - } - - EVP_CIPHER_CTX_free(ctx); - u_char clearflags = flags ^ (mask[0] & 0x0f); ngx_int_t pnl = (clearflags & 0x03) + 1; uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); -- cgit v1.2.3 From eb464a7feba7f3297e8127d5e707f36bec6b87a5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 26 Feb 2020 16:56:47 +0300 Subject: Generic function for HKDF expansion. --- src/event/ngx_event_openssl.c | 256 +++++------------------------- src/event/ngx_event_quic.c | 56 +++++++ src/event/ngx_event_quic.h | 3 + src/http/ngx_http_request.c | 351 +++++++++--------------------------------- 4 files changed, 166 insertions(+), 500 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 7d12b9625..f9102ecba 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -153,15 +153,6 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return 0; } - size_t hkdfl_len, llen; - uint8_t hkdfl[20]; - uint8_t *p; - const char *label; -#if (NGX_DEBUG) - u_char buf[512]; - size_t m; -#endif - ngx_str_t *client_key, *client_iv, *client_hp; ngx_str_t *server_key, *server_iv, *server_hp; @@ -219,231 +210,60 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ngx_memcpy(*wsec, write_secret, secret_len); - // client keys - #ifdef OPENSSL_IS_BORINGSSL client_key->len = EVP_AEAD_key_length(evp); -#else - client_key->len = EVP_CIPHER_key_length(evp); -#endif - client_key->data = ngx_pnalloc(c->pool, client_key->len); - if (client_key->data == NULL) { - return 0; - } - - label = "tls13 quic key"; - llen = sizeof("tls13 quic key") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = client_key->len / 256; - hkdfl[1] = client_key->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(client_key->data, client_key->len, - digest, *rsec, *rlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(client_key) failed"); - return 0; - } - - m = ngx_hex_dump(buf, client_key->data, client_key->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic client key: %*s, len: %uz, level: %d", - m, buf, client_key->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - + server_key->len = EVP_AEAD_key_length(evp); -#ifdef OPENSSL_IS_BORINGSSL client_iv->len = EVP_AEAD_nonce_length(evp); -#else - client_iv->len = EVP_CIPHER_iv_length(evp); -#endif - client_iv->data = ngx_pnalloc(c->pool, client_iv->len); - if (client_iv->data == NULL) { - return 0; - } - - label = "tls13 quic iv"; - llen = sizeof("tls13 quic iv") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = client_iv->len / 256; - hkdfl[1] = client_iv->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(client_iv->data, client_iv->len, - digest, *rsec, *rlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(client_iv) failed"); - return 0; - } - - m = ngx_hex_dump(buf, client_iv->data, client_iv->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic client iv: %*s, len: %uz, level: %d", - m, buf, client_iv->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - + server_iv->len = EVP_AEAD_nonce_length(evp); -#ifdef OPENSSL_IS_BORINGSSL client_hp->len = EVP_AEAD_key_length(evp); + server_hp->len = EVP_AEAD_key_length(evp); #else - client_hp->len = EVP_CIPHER_key_length(evp); -#endif - client_hp->data = ngx_pnalloc(c->pool, client_hp->len); - if (client_hp->data == NULL) { - return 0; - } - - label = "tls13 quic hp"; - llen = sizeof("tls13 quic hp") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = client_hp->len / 256; - hkdfl[1] = client_hp->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(client_hp->data, client_hp->len, - digest, *rsec, *rlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(client_hp) failed"); - return 0; - } - - m = ngx_hex_dump(buf, client_hp->data, client_hp->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic client hp: %*s, len: %uz, level: %d", - m, buf, client_hp->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - - - // server keys - -#ifdef OPENSSL_IS_BORINGSSL - server_key->len = EVP_AEAD_key_length(evp); -#else + client_key->len = EVP_CIPHER_key_length(evp); server_key->len = EVP_CIPHER_key_length(evp); -#endif - server_key->data = ngx_pnalloc(c->pool, server_key->len); - if (server_key->data == NULL) { - return 0; - } - - label = "tls13 quic key"; - llen = sizeof("tls13 quic key") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = server_key->len / 256; - hkdfl[1] = server_key->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(server_key->data, server_key->len, - digest, *wsec, *wlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(server_key) failed"); - return 0; - } - m = ngx_hex_dump(buf, server_key->data, server_key->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic server key: %*s, len: %uz, level: %d", - m, buf, server_key->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - - -#ifdef OPENSSL_IS_BORINGSSL - server_iv->len = EVP_AEAD_nonce_length(evp); -#else + client_iv->len = EVP_CIPHER_iv_length(evp); server_iv->len = EVP_CIPHER_iv_length(evp); -#endif - server_iv->data = ngx_pnalloc(c->pool, server_iv->len); - if (server_iv->data == NULL) { - return 0; - } - - label = "tls13 quic iv"; - llen = sizeof("tls13 quic iv") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = server_iv->len / 256; - hkdfl[1] = server_iv->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(server_iv->data, server_iv->len, - digest, *wsec, *wlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(server_iv) failed"); - return 0; - } - m = ngx_hex_dump(buf, server_iv->data, server_iv->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic server iv: %*s, len: %uz, level: %d", - m, buf, server_iv->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - - -#ifdef OPENSSL_IS_BORINGSSL - server_hp->len = EVP_AEAD_key_length(evp); -#else + client_hp->len = EVP_CIPHER_key_length(evp); server_hp->len = EVP_CIPHER_key_length(evp); #endif - server_hp->data = ngx_pnalloc(c->pool, server_hp->len); - if (server_hp->data == NULL) { - return 0; - } - label = "tls13 quic hp"; - llen = sizeof("tls13 quic hp") - 1; - hkdfl_len = 2 + 1 + llen + 1; - hkdfl[0] = server_hp->len / 256; - hkdfl[1] = server_hp->len % 256; - hkdfl[2] = llen; - p = ngx_cpymem(&hkdfl[3], label, llen); - *p = '\0'; - - if (ngx_hkdf_expand(server_hp->data, server_hp->len, - digest, *wsec, *wlen, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(server_hp) failed"); - return 0; + ngx_str_t rss = { + .data = *rsec, + .len = *rlen + }; + + ngx_str_t wss = { + .data = *wsec, + .len = *wlen + }; + + struct { + ngx_str_t id; + ngx_str_t *in; + ngx_str_t *prk; + } seq[] = { + { ngx_string("tls13 quic key"), client_key, &rss }, + { ngx_string("tls13 quic iv"), client_iv, &rss }, + { ngx_string("tls13 quic hp"), client_hp, &rss }, + { ngx_string("tls13 quic key"), server_key, &wss }, + { ngx_string("tls13 quic iv"), server_iv, &wss }, + { ngx_string("tls13 quic hp"), server_hp, &wss }, + }; + + ngx_uint_t i; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c, digest, seq[i].in, seq[i].prk, &seq[i].id, 1) + != NGX_OK) + { + return 0; + } } - m = ngx_hex_dump(buf, server_hp->data, server_hp->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic server hp: %*s, len: %uz, level: %d", - m, buf, server_hp->len, level); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "hkdf: %*s, len: %uz", m, buf, hkdfl_len); - return 1; } diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b453d95f8..71261f690 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -119,6 +119,62 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, } +ngx_int_t +ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, + ngx_str_t *prk, ngx_str_t *name, ngx_uint_t sender) +{ + uint8_t *p; + size_t hkdfl_len; + uint8_t hkdfl[20]; + +#if (NGX_DEBUG) + u_char buf[512]; + size_t m; +#endif + + out->data = ngx_pnalloc(c->pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + + hkdfl_len = 2 + 1 + name->len + 1; + + if (sender) { + hkdfl[0] = out->len / 256; + hkdfl[1] = out->len % 256; + + } else { + hkdfl[0] = 0; + hkdfl[1] = out->len; + } + + hkdfl[2] = name->len; + p = ngx_cpymem(&hkdfl[3], name->data, name->len); + *p = '\0'; + + if (ngx_hkdf_expand(out->data, out->len, digest, + prk->data, prk->len, hkdfl, hkdfl_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "ngx_hkdf_expand(%V) failed", name); + return NGX_ERROR; + } + + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, out->data, out->len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "%V: %*s, len: %uz", name, m, buf, out->len); + + m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "%V hkdf: %*s, len: %uz", name, m, buf, hkdfl_len); + } + + return NGX_OK; +} + + ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 1b900208b..21b04d7c1 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -53,6 +53,9 @@ ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len); +ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, + ngx_str_t *out, ngx_str_t *prk, ngx_str_t *name, ngx_uint_t sender); + ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 8fbc20424..7aa5a6408 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -785,6 +785,7 @@ ngx_http_quic_handshake(ngx_event_t *rev) size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; const EVP_MD *digest; const ngx_aead_cipher_t *cipher; static const uint8_t salt[20] = @@ -804,6 +805,11 @@ ngx_http_quic_handshake(ngx_event_t *rev) return; } + ngx_str_t iss = { + .data = is, + .len = is_len + }; + #if (NGX_DEBUG) if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; @@ -816,311 +822,92 @@ ngx_http_quic_handshake(ngx_event_t *rev) } #endif - size_t hkdfl_len; - uint8_t hkdfl[20]; - uint8_t *p; - /* draft-ietf-quic-tls-23#section-5.2 */ - qc->client_in.secret.len = SHA256_DIGEST_LENGTH; - qc->client_in.secret.data = ngx_pnalloc(c->pool, qc->client_in.secret.len); - if (qc->client_in.secret.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; - bzero(hkdfl, sizeof(hkdfl)); - hkdfl[0] = 0; - hkdfl[1] = qc->client_in.secret.len; - hkdfl[2] = sizeof("tls13 client in") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 client in", sizeof("tls13 client in") - 1); - *p = '\0'; - -#if 0 - ngx_memcpy(hkdfl, "\x00\x20\x0f\x74\x6c\x73\x31\x33\x20\x63" - "\x6c\x69\x65\x6e\x74\x20\x69\x6e\x00\x00", 20); - - m = ngx_hex_dump(buf, hkdfl, sizeof(hkdfl)) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic initial secret hkdf: %*s, len: %uz", - m, buf, sizeof(hkdfl)); -#endif - - if (ngx_hkdf_expand(qc->client_in.secret.data, qc->client_in.secret.len, - digest, is, is_len, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in) failed"); - ngx_http_close_connection(c); - return; - } - -#ifdef OPENSSL_IS_BORINGSSL - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic EVP key:%d tag:%d nonce:%d", - EVP_AEAD_key_length(cipher), - EVP_AEAD_max_tag_len(cipher), - EVP_AEAD_nonce_length(cipher)); -#endif - + qc->server_in.secret.len = SHA256_DIGEST_LENGTH; #ifdef OPENSSL_IS_BORINGSSL qc->client_in.key.len = EVP_AEAD_key_length(cipher); -#else - qc->client_in.key.len = EVP_CIPHER_key_length(cipher); -#endif - qc->client_in.key.data = ngx_pnalloc(c->pool, qc->client_in.key.len); - if (qc->client_in.key.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->client_in.key.len; - hkdfl[2] = sizeof("tls13 quic key") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); - *p = '\0'; - - if (ngx_hkdf_expand(qc->client_in.key.data, qc->client_in.key.len, - digest, qc->client_in.secret.data, qc->client_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in.key) failed"); - ngx_http_close_connection(c); - return; - } - -#ifdef OPENSSL_IS_BORINGSSL - qc->client_in.iv.len = EVP_AEAD_nonce_length(cipher); -#else - qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); -#endif - qc->client_in.iv.data = ngx_pnalloc(c->pool, qc->client_in.iv.len); - if (qc->client_in.iv.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->client_in.iv.len; - hkdfl[2] = sizeof("tls13 quic iv") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); - *p = '\0'; - - if (ngx_hkdf_expand(qc->client_in.iv.data, qc->client_in.iv.len, - digest, qc->client_in.secret.data, qc->client_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in.iv) failed"); - ngx_http_close_connection(c); - return; - } - - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + qc->server_in.key.len = EVP_AEAD_key_length(cipher); -#ifdef OPENSSL_IS_BORINGSSL qc->client_in.hp.len = EVP_AEAD_key_length(cipher); -#else - qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); -#endif - qc->client_in.hp.data = ngx_pnalloc(c->pool, qc->client_in.hp.len); - if (qc->client_in.hp.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->client_in.hp.len; - hkdfl[2] = sizeof("tls13 quic hp") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); - *p = '\0'; - - if (ngx_hkdf_expand(qc->client_in.hp.data, qc->client_in.hp.len, - digest, qc->client_in.secret.data, qc->client_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(client_in.hp) failed"); - ngx_http_close_connection(c); - return; - } - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->client_in.secret.data, qc->client_in.secret.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic client initial secret: %*s, len: %uz", - m, buf, qc->client_in.secret.len); - - m = ngx_hex_dump(buf, qc->client_in.key.data, qc->client_in.key.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic client key: %*s, len: %uz", - m, buf, qc->client_in.key.len); - - m = ngx_hex_dump(buf, qc->client_in.iv.data, qc->client_in.iv.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic client iv: %*s, len: %uz", - m, buf, qc->client_in.iv.len); - - m = ngx_hex_dump(buf, qc->client_in.hp.data, qc->client_in.hp.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic client hp: %*s, len: %uz", - m, buf, qc->client_in.hp.len); - } -#endif - -// server initial - - /* draft-ietf-quic-tls-23#section-5.2 */ - - qc->server_in.secret.len = SHA256_DIGEST_LENGTH; - qc->server_in.secret.data = ngx_pnalloc(c->pool, qc->server_in.secret.len); - if (qc->server_in.secret.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 server in") - 1 + 1; - hkdfl[0] = 0; - hkdfl[1] = qc->server_in.secret.len; - hkdfl[2] = sizeof("tls13 server in") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 server in", sizeof("tls13 server in") - 1); - *p = '\0'; - - if (ngx_hkdf_expand(qc->server_in.secret.data, qc->server_in.secret.len, - digest, is, is_len, hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in) failed"); - ngx_http_close_connection(c); - return; - } - - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + qc->server_in.hp.len = EVP_AEAD_key_length(cipher); -#ifdef OPENSSL_IS_BORINGSSL - qc->server_in.key.len = EVP_AEAD_key_length(cipher); + qc->client_in.iv.len = EVP_AEAD_nonce_length(cipher); + qc->server_in.iv.len = EVP_AEAD_nonce_length(cipher); #else + qc->client_in.key.len = EVP_CIPHER_key_length(cipher); qc->server_in.key.len = EVP_CIPHER_key_length(cipher); -#endif - qc->server_in.key.data = ngx_pnalloc(c->pool, qc->server_in.key.len); - if (qc->server_in.key.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; - hkdfl[1] = qc->server_in.key.len; - hkdfl[2] = sizeof("tls13 quic key") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic key", sizeof("tls13 quic key") - 1); - *p = '\0'; - if (ngx_hkdf_expand(qc->server_in.key.data, qc->server_in.key.len, - digest, qc->server_in.secret.data, qc->server_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in.key) failed"); - ngx_http_close_connection(c); - return; - } + qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); + qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); -#ifdef OPENSSL_IS_BORINGSSL - qc->server_in.iv.len = EVP_AEAD_nonce_length(cipher); -#else + qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); #endif - qc->server_in.iv.data = ngx_pnalloc(c->pool, qc->server_in.iv.len); - if (qc->server_in.iv.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; - hkdfl[1] = qc->server_in.iv.len; - hkdfl[2] = sizeof("tls13 quic iv") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); - *p = '\0'; - - if (ngx_hkdf_expand(qc->server_in.iv.data, qc->server_in.iv.len, - digest, qc->server_in.secret.data, qc->server_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in.iv) failed"); - ngx_http_close_connection(c); - return; - } - - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ #ifdef OPENSSL_IS_BORINGSSL - qc->server_in.hp.len = EVP_AEAD_key_length(cipher); -#else - qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic EVP key:%d tag:%d nonce:%d", + EVP_AEAD_key_length(cipher), + EVP_AEAD_max_tag_len(cipher), + EVP_AEAD_nonce_length(cipher)); #endif - qc->server_in.hp.data = ngx_pnalloc(c->pool, qc->server_in.hp.len); - if (qc->server_in.hp.data == NULL) { - ngx_http_close_connection(c); - return; - } - - hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; - hkdfl[1] = qc->server_in.hp.len; - hkdfl[2] = sizeof("tls13 quic hp") - 1; - p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); - *p = '\0'; - if (ngx_hkdf_expand(qc->server_in.hp.data, qc->server_in.hp.len, - digest, qc->server_in.secret.data, qc->server_in.secret.len, - hkdfl, hkdfl_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "ngx_hkdf_expand(server_in.hp) failed"); - ngx_http_close_connection(c); - return; - } + struct { + ngx_str_t id; + ngx_str_t *in; + ngx_str_t *prk; + } seq[] = { -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->server_in.secret.data, qc->server_in.secret.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic server initial secret: %*s, len: %uz", - m, buf, qc->server_in.secret.len); + /* draft-ietf-quic-tls-23#section-5.2 */ + { ngx_string("tls13 client in"), &qc->client_in.secret, &iss }, + { + ngx_string("tls13 quic key"), + &qc->client_in.key, + &qc->client_in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qc->client_in.iv, + &qc->client_in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qc->client_in.hp, + &qc->client_in.secret, + }, + { ngx_string("tls13 server in"), &qc->server_in.secret, &iss }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + ngx_string("tls13 quic key"), + &qc->server_in.key, + &qc->server_in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qc->server_in.iv, + &qc->server_in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qc->server_in.hp, + &qc->server_in.secret + }, - m = ngx_hex_dump(buf, qc->server_in.key.data, qc->server_in.key.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic server key: %*s, len: %uz", - m, buf, qc->server_in.key.len); + }; - m = ngx_hex_dump(buf, qc->server_in.iv.data, qc->server_in.iv.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic server iv: %*s, len: %uz", - m, buf, qc->server_in.iv.len); + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - m = ngx_hex_dump(buf, qc->server_in.hp.data, qc->server_in.hp.len) - - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic server hp: %*s, len: %uz", - m, buf, qc->server_in.hp.len); + if (ngx_quic_hkdf_expand(c, digest, seq[i].in, seq[i].prk, &seq[i].id, 0) + != NGX_OK) + { + ngx_http_close_connection(c); + return; + } } -#endif // header protection -- cgit v1.2.3 From ef8b06b186a2f7ac25b8ee49a325c935c3e5bb9f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: Cleanup. --- src/event/ngx_event_openssl.c | 172 ++++++++++----------------------- src/event/ngx_event_quic.c | 45 ++++----- src/event/ngx_event_quic.h | 15 +-- src/http/modules/ngx_http_ssl_module.c | 3 - src/http/ngx_http_request.c | 67 ++++--------- 5 files changed, 91 insertions(+), 211 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index f9102ecba..6851f1e43 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -98,15 +98,11 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, const uint8_t *write_secret, size_t secret_len) { u_char *name; - size_t *rlen, *wlen; - uint8_t **rsec, **wsec; + ngx_uint_t i; const EVP_MD *digest; -#ifdef OPENSSL_IS_BORINGSSL - const EVP_AEAD *evp; -#else - const EVP_CIPHER *evp; -#endif + const EVP_CIPHER *cipher; ngx_connection_t *c; + ngx_quic_secret_t *client, *server; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -134,59 +130,29 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - evp = EVP_aead_aes_128_gcm(); -#else - evp = EVP_aes_128_gcm(); -#endif + cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - evp = EVP_aead_aes_256_gcm(); -#else - evp = EVP_aes_256_gcm(); -#endif + cipher = EVP_aes_256_gcm(); digest = EVP_sha384(); } else { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); return 0; } - ngx_str_t *client_key, *client_iv, *client_hp; - ngx_str_t *server_key, *server_iv, *server_hp; - switch (level) { case ssl_encryption_handshake: - rlen = &c->quic->client_hs.secret.len; - rsec = &c->quic->client_hs.secret.data; - wlen = &c->quic->server_hs.secret.len; - wsec = &c->quic->server_hs.secret.data; - - client_key = &c->quic->client_hs.key; - client_iv = &c->quic->client_hs.iv; - client_hp = &c->quic->client_hs.hp; - - server_key = &c->quic->server_hs.key; - server_iv = &c->quic->server_hs.iv; - server_hp = &c->quic->server_hs.hp; + client = &c->quic->client_hs; + server = &c->quic->server_hs; break; case ssl_encryption_application: - rlen = &c->quic->client_ad.secret.len; - rsec = &c->quic->client_ad.secret.data; - wlen = &c->quic->server_ad.secret.len; - wsec = &c->quic->server_ad.secret.data; - - client_key = &c->quic->client_ad.key; - client_iv = &c->quic->client_ad.iv; - client_hp = &c->quic->client_ad.hp; - - server_key = &c->quic->server_ad.key; - server_iv = &c->quic->server_ad.iv; - server_hp = &c->quic->server_ad.hp; + client = &c->quic->client_ad; + server = &c->quic->server_ad; break; @@ -194,70 +160,32 @@ quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return 0; } - *rlen = *wlen = secret_len; - - *rsec = ngx_pnalloc(c->pool, secret_len); - if (*rsec == NULL) { - return 0; - } - - ngx_memcpy(*rsec, read_secret, secret_len); - - *wsec = ngx_pnalloc(c->pool, secret_len); - if (*wsec == NULL) { - return 0; - } - - ngx_memcpy(*wsec, write_secret, secret_len); - -#ifdef OPENSSL_IS_BORINGSSL - client_key->len = EVP_AEAD_key_length(evp); - server_key->len = EVP_AEAD_key_length(evp); - - client_iv->len = EVP_AEAD_nonce_length(evp); - server_iv->len = EVP_AEAD_nonce_length(evp); - - client_hp->len = EVP_AEAD_key_length(evp); - server_hp->len = EVP_AEAD_key_length(evp); -#else - client_key->len = EVP_CIPHER_key_length(evp); - server_key->len = EVP_CIPHER_key_length(evp); - - client_iv->len = EVP_CIPHER_iv_length(evp); - server_iv->len = EVP_CIPHER_iv_length(evp); + client->key.len = EVP_CIPHER_key_length(cipher); + server->key.len = EVP_CIPHER_key_length(cipher); - client_hp->len = EVP_CIPHER_key_length(evp); - server_hp->len = EVP_CIPHER_key_length(evp); -#endif - - ngx_str_t rss = { - .data = *rsec, - .len = *rlen - }; + client->iv.len = EVP_CIPHER_iv_length(cipher); + server->iv.len = EVP_CIPHER_iv_length(cipher); - ngx_str_t wss = { - .data = *wsec, - .len = *wlen - }; + client->hp.len = EVP_CIPHER_key_length(cipher); + server->hp.len = EVP_CIPHER_key_length(cipher); struct { - ngx_str_t id; - ngx_str_t *in; - ngx_str_t *prk; + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; } seq[] = { - { ngx_string("tls13 quic key"), client_key, &rss }, - { ngx_string("tls13 quic iv"), client_iv, &rss }, - { ngx_string("tls13 quic hp"), client_hp, &rss }, - { ngx_string("tls13 quic key"), server_key, &wss }, - { ngx_string("tls13 quic iv"), server_iv, &wss }, - { ngx_string("tls13 quic hp"), server_hp, &wss }, + { ngx_string("tls13 quic key"), &client->key, read_secret }, + { ngx_string("tls13 quic iv"), &client->iv, read_secret }, + { ngx_string("tls13 quic hp"), &client->hp, read_secret }, + { ngx_string("tls13 quic key"), &server->key, write_secret }, + { ngx_string("tls13 quic iv"), &server->iv, write_secret }, + { ngx_string("tls13 quic hp"), &server->hp, write_secret }, }; - ngx_uint_t i; - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(c, digest, seq[i].in, seq[i].prk, &seq[i].id, 1) + if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) != NGX_OK) { return 0; @@ -272,21 +200,21 @@ static int quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char buf[2048], *p, *name; + u_char *p, *pnp, *name, *nonce, *sample; ngx_int_t m; ngx_str_t in, out, ad; - ngx_quic_secret_t *secret; + static int pn; + const EVP_CIPHER *cipher; ngx_connection_t *c; + ngx_quic_secret_t *secret; ngx_quic_connection_t *qc; - const ngx_aead_cipher_t *cipher; - static int pn = 0; + u_char buf[2048], mask[16]; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = c->quic; ngx_ssl_handshake_log(c); - qc = c->quic; - switch (level) { case ssl_encryption_initial: @@ -355,7 +283,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_build_int(&p, 0); // token length } ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) - u_char *pnp = p; + pnp = p; if (level == ssl_encryption_initial) { *p++ = 0; // packet number 0 @@ -377,24 +305,16 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - cipher = EVP_aead_aes_128_gcm(); -#else cipher = EVP_aes_128_gcm(); -#endif } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - cipher = EVP_aead_aes_256_gcm(); -#else cipher = EVP_aes_256_gcm(); -#endif } else { return 0; } - uint8_t *nonce = ngx_pstrdup(c->pool, &secret->iv); + nonce = ngx_pstrdup(c->pool, &secret->iv); if (level == ssl_encryption_handshake) { nonce[11] ^= (pn - 1); } @@ -413,8 +333,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - u_char *sample = &out.data[3]; // pnl=0 - uint8_t mask[16]; + sample = &out.data[3]; // pnl=0 if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) { return 0; } @@ -438,8 +357,6 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ad.data[0] ^= mask[0] & 0x0f; *pnp ^= mask[1]; -printf("clear_len %ld ciphertext_len %ld ad_len %ld\n", in.len, out.len, ad.len); - u_char *packet = ngx_alloc(ad.len + out.len, c->log); if (packet == 0) { return 0; @@ -462,7 +379,12 @@ printf("clear_len %ld ciphertext_len %ld ad_len %ld\n", in.len, out.len, ad.len) static int quic_flush_flight(ngx_ssl_conn_t *ssl_conn) { - printf("quic_flush_flight()\n"); + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_flush_flight()"); + return 1; } @@ -471,7 +393,14 @@ static int quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert) { - printf("quic_send_alert(), lvl=%d, alert=%d\n", level, alert); + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_send_alert(), lvl=%d, alert=%d", + (int) level, (int) alert); + return 1; } @@ -1865,7 +1794,6 @@ ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) #if NGX_OPENSSL_QUIC SSL_CTX_set_quic_method(ssl->ctx, &quic_method); -printf("%s\n", __func__); return NGX_OK; #else diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 71261f690..8470d623f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -121,11 +121,11 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, - ngx_str_t *prk, ngx_str_t *name, ngx_uint_t sender) + ngx_str_t *label, const uint8_t *prk, size_t prk_len) { uint8_t *p; - size_t hkdfl_len; - uint8_t hkdfl[20]; + size_t info_len; + uint8_t info[20]; #if (NGX_DEBUG) u_char buf[512]; @@ -137,38 +137,31 @@ ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, return NGX_ERROR; } - hkdfl_len = 2 + 1 + name->len + 1; + info_len = 2 + 1 + label->len + 1; - if (sender) { - hkdfl[0] = out->len / 256; - hkdfl[1] = out->len % 256; - - } else { - hkdfl[0] = 0; - hkdfl[1] = out->len; - } - - hkdfl[2] = name->len; - p = ngx_cpymem(&hkdfl[3], name->data, name->len); + info[0] = 0; + info[1] = out->len; + info[2] = label->len; + p = ngx_cpymem(&info[3], label->data, label->len); *p = '\0'; if (ngx_hkdf_expand(out->data, out->len, digest, - prk->data, prk->len, hkdfl, hkdfl_len) + prk, prk_len, info, info_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(%V) failed", name); + "ngx_hkdf_expand(%V) failed", label); return NGX_ERROR; } if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, out->data, out->len) - buf; + m = ngx_hex_dump(buf, info, info_len) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "%V: %*s, len: %uz", name, m, buf, out->len); + "%V info: %*s, len: %uz", label, m, buf, info_len); - m = ngx_hex_dump(buf, hkdfl, hkdfl_len) - buf; + m = ngx_hex_dump(buf, out->data, out->len) - buf; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "%V hkdf: %*s, len: %uz", name, m, buf, hkdfl_len); + "%V key: %*s, len: %uz", label, m, buf, out->len); } return NGX_OK; @@ -177,7 +170,7 @@ ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, - const u_char *prk, size_t prk_len, const u_char *info, size_t info_len) + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) { #ifdef OPENSSL_IS_BORINGSSL if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) @@ -222,7 +215,7 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, ngx_int_t -ngx_quic_tls_open(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, +ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) { @@ -232,7 +225,7 @@ ngx_quic_tls_open(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, return NGX_ERROR; } -#ifdef OPENSSL_IS_BORINGSSL +#ifdef OPENSSL_IS_BORINGSSLL EVP_AEAD_CTX *ctx; ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, @@ -327,7 +320,7 @@ ngx_quic_tls_open(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, ngx_int_t -ngx_quic_tls_seal(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, +ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) { @@ -337,7 +330,7 @@ ngx_quic_tls_seal(ngx_connection_t *c, const ngx_aead_cipher_t *cipher, return NGX_ERROR; } -#ifdef OPENSSL_IS_BORINGSSL +#ifdef OPENSSL_IS_BORINGSSLL EVP_AEAD_CTX *ctx; ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 21b04d7c1..be2a4c156 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,15 +11,6 @@ #include -#ifdef OPENSSL_IS_BORINGSSL -#define ngx_aead_cipher_t EVP_AEAD -#define NGX_QUIC_INITIAL_CIPHER EVP_aead_aes_128_gcm() -#else -#define ngx_aead_cipher_t EVP_CIPHER -#define NGX_QUIC_INITIAL_CIPHER EVP_aes_128_gcm() -#endif - - typedef struct { ngx_str_t secret; ngx_str_t key; @@ -54,13 +45,13 @@ ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const u_char *info, size_t info_len); ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, - ngx_str_t *out, ngx_str_t *prk, ngx_str_t *name, ngx_uint_t sender); + ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, - const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, - const ngx_aead_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); ngx_int_t diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 693e45a1c..8640c2211 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -706,7 +706,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } -printf("ngx_ssl_create\n"); if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1156,7 +1155,6 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { -printf("ssl %d http3 %d\n", addr[a].opt.ssl, addr[a].opt.http3); if (!addr[a].opt.ssl && !addr[a].opt.http3) { continue; @@ -1164,7 +1162,6 @@ printf("ssl %d http3 %d\n", addr[a].opt.ssl, addr[a].opt.http3); cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; -printf("sscf->protocols %lx\n", sscf->protocols); if (sscf->certificates == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7aa5a6408..bc2797b21 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -678,16 +678,6 @@ ngx_http_quic_handshake(ngx_event_t *rev) hc = c->data; b = c->buffer; - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { - ngx_http_close_connection(c); - return; - } - - c->quic = qc; - - printf("buffer %p %p:%p:%p:%p \n", b, b->start, b->pos, b->last, b->end); - if ((b->pos[0] & 0xf0) != 0xc0) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet"); ngx_http_close_connection(c); @@ -713,6 +703,14 @@ ngx_http_quic_handshake(ngx_event_t *rev) return; } + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + ngx_http_close_connection(c); + return; + } + + c->quic = qc; + qc->dcid.len = *b->pos++; qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); if (qc->dcid.data == NULL) { @@ -787,14 +785,14 @@ ngx_http_quic_handshake(ngx_event_t *rev) uint8_t is[SHA256_DIGEST_LENGTH]; ngx_uint_t i; const EVP_MD *digest; - const ngx_aead_cipher_t *cipher; + const EVP_CIPHER *cipher; static const uint8_t salt[20] = "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - cipher = NGX_QUIC_INITIAL_CIPHER; + cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, @@ -826,16 +824,6 @@ ngx_http_quic_handshake(ngx_event_t *rev) qc->client_in.secret.len = SHA256_DIGEST_LENGTH; qc->server_in.secret.len = SHA256_DIGEST_LENGTH; -#ifdef OPENSSL_IS_BORINGSSL - qc->client_in.key.len = EVP_AEAD_key_length(cipher); - qc->server_in.key.len = EVP_AEAD_key_length(cipher); - - qc->client_in.hp.len = EVP_AEAD_key_length(cipher); - qc->server_in.hp.len = EVP_AEAD_key_length(cipher); - - qc->client_in.iv.len = EVP_AEAD_nonce_length(cipher); - qc->server_in.iv.len = EVP_AEAD_nonce_length(cipher); -#else qc->client_in.key.len = EVP_CIPHER_key_length(cipher); qc->server_in.key.len = EVP_CIPHER_key_length(cipher); @@ -844,19 +832,10 @@ ngx_http_quic_handshake(ngx_event_t *rev) qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); -#endif - -#ifdef OPENSSL_IS_BORINGSSL - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic EVP key:%d tag:%d nonce:%d", - EVP_AEAD_key_length(cipher), - EVP_AEAD_max_tag_len(cipher), - EVP_AEAD_nonce_length(cipher)); -#endif struct { - ngx_str_t id; - ngx_str_t *in; + ngx_str_t label; + ngx_str_t *key; ngx_str_t *prk; } seq[] = { @@ -894,14 +873,15 @@ ngx_http_quic_handshake(ngx_event_t *rev) /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), &qc->server_in.hp, - &qc->server_in.secret + &qc->server_in.secret, }, }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(c, digest, seq[i].in, seq[i].prk, &seq[i].id, 0) + if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, + seq[i].prk->data, seq[i].prk->len) != NGX_OK) { ngx_http_close_connection(c); @@ -973,7 +953,8 @@ ngx_http_quic_handshake(ngx_event_t *rev) ngx_str_t out; - if (ngx_quic_tls_open(c, cipher, &qc->client_in, &out, nonce, &in, &ad) + if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, + &in, &ad) != NGX_OK) { ngx_http_close_connection(c); @@ -1090,7 +1071,9 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) { size_t m; ssize_t n; + ngx_str_t out; ngx_connection_t *c; + const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; u_char buf[4096], b[512], *p; @@ -1249,8 +1232,6 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) } #endif - const ngx_aead_cipher_t *cipher; - u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic ssl cipher: %s", name); @@ -1258,18 +1239,10 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - cipher = EVP_aead_aes_128_gcm(); -#else cipher = EVP_aes_128_gcm(); -#endif } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { -#ifdef OPENSSL_IS_BORINGSSL - cipher = EVP_aead_aes_256_gcm(); -#else cipher = EVP_aes_256_gcm(); -#endif } else { ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "unexpected cipher"); @@ -1277,8 +1250,6 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } - ngx_str_t out; - if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad) != NGX_OK) { -- cgit v1.2.3 From 899372129879b5874ffddca4247fca12d3b7c257 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 Feb 2020 13:09:52 +0300 Subject: Introduced quic_version macro, uint16/uint32 routines ported. --- src/event/ngx_event_openssl.c | 5 +---- src/event/ngx_event_quic.h | 43 +++++++++++++++++++++++++++++++++++++++++++ src/http/ngx_http_request.c | 12 ++++++------ 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 6851f1e43..95bc955ff 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -271,10 +271,7 @@ quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } else if (level == ssl_encryption_handshake) { *p++ = 0xe0; // handshake, packet number len } - *p++ = 0xff; - *p++ = 0x00; - *p++ = 0x00; - *p++ = 0x18; + p = ngx_quic_write_uint32(p, quic_version); *p++ = qc->scid.len; p = ngx_cpymem(p, qc->scid.data, qc->scid.len); *p++ = qc->dcid.len; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index be2a4c156..ec7974094 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -10,6 +10,8 @@ #include +#define quic_version 0xff000018 + typedef struct { ngx_str_t secret; @@ -58,4 +60,45 @@ ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#endif + + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index bc2797b21..d40f50496 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -691,13 +691,13 @@ ngx_http_quic_handshake(ngx_event_t *rev) } ngx_int_t flags = *b->pos++; - uint32_t version = ngx_http_v2_parse_uint32(b->pos); - b->pos += 4; + uint32_t version = ngx_quic_parse_uint32(b->pos); + b->pos += sizeof(uint32_t); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic flags:%xi version:%xD", flags, version); - if (version != 0xff000018) { + if (version != quic_version) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); ngx_http_close_connection(c); return; @@ -1117,13 +1117,13 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) } ngx_int_t flags = *p++; - uint32_t version = ngx_http_v2_parse_uint32(p); - p += 4; + uint32_t version = ngx_quic_parse_uint32(p); + p += sizeof(uint32_t); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic flags:%xi version:%xD", flags, version); - if (version != 0xff000018) { + if (version != quic_version) { ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); ngx_http_close_connection(c); return; -- cgit v1.2.3 From b20ed8f7f1acc2a1ef38714ddd75c7ba7add5f1d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 28 Feb 2020 16:23:25 +0300 Subject: Moved all QUIC code into ngx_event_quic.c Introduced ngx_quic_input() and ngx_quic_output() as interface between nginx and protocol. They are the only functions that are exported. While there, added copyrights. --- src/event/ngx_event_openssl.c | 324 +------------ src/event/ngx_event_quic.c | 1041 ++++++++++++++++++++++++++++++++++++++++- src/event/ngx_event_quic.h | 94 +--- src/http/ngx_http_request.c | 555 +--------------------- 4 files changed, 1049 insertions(+), 965 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 95bc955ff..eac1981a2 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -90,328 +90,6 @@ static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void ngx_openssl_exit(ngx_cycle_t *cycle); -#if NGX_OPENSSL_QUIC - -static int -quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *read_secret, - const uint8_t *write_secret, size_t secret_len) -{ - u_char *name; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; - ngx_connection_t *c; - ngx_quic_secret_t *client, *server; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_ssl_handshake_log(c); - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - u_char buf[64]; - size_t m; - - m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: read %*s, len: %uz, level:%d", - m, buf, secret_len, (int) level); - - m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: write %*s, len: %uz, level:%d", - m, buf, secret_len, (int) level); - } -#endif - - name = (u_char *) SSL_get_cipher(ssl_conn); - - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { - cipher = EVP_aes_128_gcm(); - digest = EVP_sha256(); - - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { - cipher = EVP_aes_256_gcm(); - digest = EVP_sha384(); - - } else { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return 0; - } - - switch (level) { - - case ssl_encryption_handshake: - client = &c->quic->client_hs; - server = &c->quic->server_hs; - - break; - - case ssl_encryption_application: - client = &c->quic->client_ad; - server = &c->quic->server_ad; - - break; - - default: - return 0; - } - - client->key.len = EVP_CIPHER_key_length(cipher); - server->key.len = EVP_CIPHER_key_length(cipher); - - client->iv.len = EVP_CIPHER_iv_length(cipher); - server->iv.len = EVP_CIPHER_iv_length(cipher); - - client->hp.len = EVP_CIPHER_key_length(cipher); - server->hp.len = EVP_CIPHER_key_length(cipher); - - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &client->key, read_secret }, - { ngx_string("tls13 quic iv"), &client->iv, read_secret }, - { ngx_string("tls13 quic hp"), &client->hp, read_secret }, - { ngx_string("tls13 quic key"), &server->key, write_secret }, - { ngx_string("tls13 quic iv"), &server->iv, write_secret }, - { ngx_string("tls13 quic hp"), &server->hp, write_secret }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { - return 0; - } - } - - return 1; -} - - -static int -quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len) -{ - u_char *p, *pnp, *name, *nonce, *sample; - ngx_int_t m; - ngx_str_t in, out, ad; - static int pn; - const EVP_CIPHER *cipher; - ngx_connection_t *c; - ngx_quic_secret_t *secret; - ngx_quic_connection_t *qc; - u_char buf[2048], mask[16]; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = c->quic; - - ngx_ssl_handshake_log(c); - - switch (level) { - - case ssl_encryption_initial: - secret = &qc->server_in; - break; - - case ssl_encryption_handshake: - secret = &qc->server_hs; - break; - - default: - return 0; - } - - m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data: %*s%s, len: %uz, level:%d", - m, buf, len < 2048 ? "" : "...", len, (int) level); - - in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); - if (in.data == 0) { - return 0; - } - - p = in.data; - ngx_quic_build_int(&p, 6); // crypto frame - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, len); - p = ngx_cpymem(p, data, len); - - if (level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 2); // ack frame - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - } - - in.len = p - in.data; - out.len = in.len + EVP_GCM_TLS_TAG_LEN; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz", - in.len, out.len); - - ad.data = ngx_alloc(346 /*max header*/, c->log); - if (ad.data == 0) { - return 0; - } - - p = ad.data; - if (level == ssl_encryption_initial) { - *p++ = 0xc0; // initial, packet number len - } else if (level == ssl_encryption_handshake) { - *p++ = 0xe0; // handshake, packet number len - } - p = ngx_quic_write_uint32(p, quic_version); - *p++ = qc->scid.len; - p = ngx_cpymem(p, qc->scid.data, qc->scid.len); - *p++ = qc->dcid.len; - p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); - if (level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 0); // token length - } - ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) - pnp = p; - - if (level == ssl_encryption_initial) { - *p++ = 0; // packet number 0 - - } else if (level == ssl_encryption_handshake) { - *p++ = pn++; - } - - ad.len = p - ad.data; - - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data ad: %*s, len: %uz", - m, buf, ad.len); - - - name = (u_char *) SSL_get_cipher(ssl_conn); - - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { - cipher = EVP_aes_128_gcm(); - - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { - cipher = EVP_aes_256_gcm(); - - } else { - return 0; - } - - nonce = ngx_pstrdup(c->pool, &secret->iv); - if (level == ssl_encryption_handshake) { - nonce[11] ^= (pn - 1); - } - - m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data sample: server_iv %*s", - m, buf); - m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data sample: n=%d nonce %*s", - pn - 1, m, buf); - - if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK) - { - return 0; - } - - sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) { - return 0; - } - - m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data sample: %*s, len: %uz", - m, buf, 16); - - m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data mask: %*s, len: %uz", - m, buf, 16); - - m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data hp_key: %*s, len: %uz", - m, buf, 16); - - // header protection, pnl = 0 - ad.data[0] ^= mask[0] & 0x0f; - *pnp ^= mask[1]; - - u_char *packet = ngx_alloc(ad.len + out.len, c->log); - if (packet == 0) { - return 0; - } - - p = ngx_cpymem(packet, ad.data, ad.len); - p = ngx_cpymem(p, out.data, out.len); - - m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_add_handshake_data packet: %*s%s, len: %uz", - m, buf, len < 2048 ? "" : "...", p - packet); - - c->send(c, packet, p - packet); - - return 1; -} - - -static int -quic_flush_flight(ngx_ssl_conn_t *ssl_conn) -{ - ngx_connection_t *c; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic_flush_flight()"); - - return 1; -} - - -static int -quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, - uint8_t alert) -{ - ngx_connection_t *c; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic_send_alert(), lvl=%d, alert=%d", - (int) level, (int) alert); - - return 1; -} - - -static SSL_QUIC_METHOD quic_method = { - quic_set_encryption_secrets, - quic_add_handshake_data, - quic_flush_flight, - quic_send_alert, -}; - -#endif - - static ngx_command_t ngx_openssl_commands[] = { { ngx_string("ssl_engine"), @@ -1790,7 +1468,7 @@ ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) #if NGX_OPENSSL_QUIC - SSL_CTX_set_quic_method(ssl->ctx, &quic_method); + ngx_quic_init_ssl_methods(ssl->ctx); return NGX_OK; #else diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8470d623f..30045ecc1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1,6 +1,1031 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + #include #include -#include + + +#define quic_version 0xff000018 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + + +/* TODO: real states, these are stubs */ +typedef enum { + NGX_QUIC_ST_INITIAL, + NGX_QUIC_ST_HANDSHAKE, + NGX_QUIC_ST_APP_DATA +} ngx_quic_state_t; + + +typedef struct { + ngx_str_t secret; + ngx_str_t key; + ngx_str_t iv; + ngx_str_t hp; +} ngx_quic_secret_t; + + +struct ngx_quic_connection_s { + + ngx_quic_state_t state; + ngx_ssl_t *ssl; + + ngx_str_t out; // stub for some kind of output queue + + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; + + ngx_quic_secret_t client_in; + ngx_quic_secret_t client_hs; + ngx_quic_secret_t client_ad; + ngx_quic_secret_t server_in; + ngx_quic_secret_t server_hs; + ngx_quic_secret_t server_ad; +}; + + +static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_buf_t *b); +static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); + +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); +static uint64_t ngx_quic_parse_int(u_char **pos); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); + +static ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, + ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); + +static ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, + const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad); +static ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, + const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad); + +static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in); + + +static SSL_QUIC_METHOD quic_method = { + ngx_quic_set_encryption_secrets, + ngx_quic_add_handshake_data, + ngx_quic_flush_flight, + ngx_quic_send_alert, +}; + + +void +ngx_quic_init_ssl_methods(SSL_CTX* ctx) +{ + SSL_CTX_set_quic_method(ctx, &quic_method); +} + + +ngx_int_t +ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +{ + if (c->quic == NULL) { + return ngx_quic_new_connection(c, ssl, b); //TODO: change state by results + } + + switch (c->quic->state) { + case NGX_QUIC_ST_INITIAL: + case NGX_QUIC_ST_HANDSHAKE: + return ngx_quic_handshake_input(c, b); + default: + /* application data */ + break; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + /* stub for processing output queue */ + + if (c->quic->out.data) { + c->send(c, c->quic->out.data, c->quic->out.len); + c->quic->out.data = NULL; + } + + return NGX_OK; +} + + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len) +{ + u_char *name; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + ngx_connection_t *c; + ngx_quic_secret_t *client, *server; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + //ngx_ssl_handshake_log(c); // TODO: enable again + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + u_char buf[64]; + size_t m; + + m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: read %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + + m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: write %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + } +#endif + + name = (u_char *) SSL_get_cipher(ssl_conn); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { + cipher = EVP_aes_128_gcm(); + digest = EVP_sha256(); + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + cipher = EVP_aes_256_gcm(); + digest = EVP_sha384(); + + } else { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return 0; + } + + switch (level) { + + case ssl_encryption_handshake: + client = &c->quic->client_hs; + server = &c->quic->server_hs; + + break; + + case ssl_encryption_application: + client = &c->quic->client_ad; + server = &c->quic->server_ad; + + break; + + default: + return 0; + } + + client->key.len = EVP_CIPHER_key_length(cipher); + server->key.len = EVP_CIPHER_key_length(cipher); + + client->iv.len = EVP_CIPHER_iv_length(cipher); + server->iv.len = EVP_CIPHER_iv_length(cipher); + + client->hp.len = EVP_CIPHER_key_length(cipher); + server->hp.len = EVP_CIPHER_key_length(cipher); + + struct { + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; + } seq[] = { + { ngx_string("tls13 quic key"), &client->key, read_secret }, + { ngx_string("tls13 quic iv"), &client->iv, read_secret }, + { ngx_string("tls13 quic hp"), &client->hp, read_secret }, + { ngx_string("tls13 quic key"), &server->key, write_secret }, + { ngx_string("tls13 quic iv"), &server->iv, write_secret }, + { ngx_string("tls13 quic hp"), &server->hp, write_secret }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *pnp, *name, *nonce, *sample; + ngx_int_t m; + ngx_str_t in, out, ad; + static int pn; + const EVP_CIPHER *cipher; + ngx_connection_t *c; + ngx_quic_secret_t *secret; + ngx_quic_connection_t *qc; + u_char buf[2048], mask[16]; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = c->quic; + + //ngx_ssl_handshake_log(c); // TODO: enable again + + switch (level) { + + case ssl_encryption_initial: + secret = &qc->server_in; + break; + + case ssl_encryption_handshake: + secret = &qc->server_hs; + break; + + default: + return 0; + } + + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data: %*s%s, len: %uz, level:%d", + m, buf, len < 2048 ? "" : "...", len, (int) level); + + in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); + if (in.data == 0) { + return 0; + } + + p = in.data; + ngx_quic_build_int(&p, 6); // crypto frame + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, len); + p = ngx_cpymem(p, data, len); + + if (level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 2); // ack frame + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + } + + in.len = p - in.data; + out.len = in.len + EVP_GCM_TLS_TAG_LEN; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz", + in.len, out.len); + + ad.data = ngx_alloc(346 /*max header*/, c->log); + if (ad.data == 0) { + return 0; + } + + p = ad.data; + if (level == ssl_encryption_initial) { + *p++ = 0xc0; // initial, packet number len + } else if (level == ssl_encryption_handshake) { + *p++ = 0xe0; // handshake, packet number len + } + p = ngx_quic_write_uint32(p, quic_version); + *p++ = qc->scid.len; + p = ngx_cpymem(p, qc->scid.data, qc->scid.len); + *p++ = qc->dcid.len; + p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); + if (level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); // token length + } + ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) + pnp = p; + + if (level == ssl_encryption_initial) { + *p++ = 0; // packet number 0 + + } else if (level == ssl_encryption_handshake) { + *p++ = pn++; + } + + ad.len = p - ad.data; + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data ad: %*s, len: %uz", + m, buf, ad.len); + + + name = (u_char *) SSL_get_cipher(ssl_conn); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { + cipher = EVP_aes_128_gcm(); + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + cipher = EVP_aes_256_gcm(); + + } else { + return 0; + } + + nonce = ngx_pstrdup(c->pool, &secret->iv); + if (level == ssl_encryption_handshake) { + nonce[11] ^= (pn - 1); + } + + m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data sample: server_iv %*s", + m, buf); + m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data sample: n=%d nonce %*s", + pn - 1, m, buf); + + if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK) + { + return 0; + } + + sample = &out.data[3]; // pnl=0 + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) { + return 0; + } + + m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data sample: %*s, len: %uz", + m, buf, 16); + + m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data mask: %*s, len: %uz", + m, buf, 16); + + m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data hp_key: %*s, len: %uz", + m, buf, 16); + + // header protection, pnl = 0 + ad.data[0] ^= mask[0] & 0x0f; + *pnp ^= mask[1]; + + u_char *packet = ngx_alloc(ad.len + out.len, c->log); + if (packet == 0) { + return 0; + } + + p = ngx_cpymem(packet, ad.data, ad.len); + p = ngx_cpymem(p, out.data, out.len); + + m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data packet: %*s%s, len: %uz", + m, buf, len < 2048 ? "" : "...", p - packet); + + // TODO: save state of data to send into qc (push into queue) + + qc->out.data = packet; + qc->out.len = p - packet; + + if (ngx_quic_output(c) != NGX_OK) { + return 0; + } + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_send_alert(), lvl=%d, alert=%d", + (int) level, (int) alert); + + return 1; +} + + +static ngx_int_t +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +{ + int n, sslerr; +#if (NGX_DEBUG) + u_char buf[512]; + size_t m; +#endif + + ngx_quic_connection_t *qc; + + if ((b->pos[0] & 0xf0) != 0xc0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet"); + return NGX_ERROR; + } + + if (ngx_buf_size(b) < 1200) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); + return NGX_ERROR; + } + + ngx_int_t flags = *b->pos++; + uint32_t version = ngx_quic_parse_uint32(b->pos); + b->pos += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flags:%xi version:%xD", flags, version); + + if (version != quic_version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); + return NGX_ERROR; + } + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; + } + + c->quic = qc; + + qc->dcid.len = *b->pos++; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); + b->pos += qc->dcid.len; + + qc->scid.len = *b->pos++; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); + b->pos += qc->scid.len; + + qc->token.len = ngx_quic_parse_int(&b->pos); + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(qc->token.data, b->pos, qc->token.len); + b->pos += qc->token.len; + + ngx_int_t plen = ngx_quic_parse_int(&b->pos); + + if (plen > b->last - b->pos) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); + return NGX_ERROR; + } + + /* draft-ietf-quic-tls-23#section-5.4.2: + * the Packet Number field is assumed to be 4 bytes long + * draft-ietf-quic-tls-23#section-5.4.[34]: + * AES-Based and ChaCha20-Based header protections sample 16 bytes + */ + u_char *sample = b->pos + 4; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len); + + m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SCID: %*s, len: %uz", m, buf, qc->scid.len); + + m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic token: %*s, len: %uz", m, buf, qc->token.len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic sample: %*s", m, buf); + } +#endif + +// initial secret + + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + static const uint8_t salt[20] = + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" + "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + cipher = EVP_aes_128_gcm(); + digest = EVP_sha256(); + + if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_str_t iss = { + .data = is, + .len = is_len + }; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic salt: %*s, len: %uz", m, buf, sizeof(salt)); + + m = ngx_hex_dump(buf, is, is_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initial secret: %*s, len: %uz", m, buf, is_len); + } +#endif + + /* draft-ietf-quic-tls-23#section-5.2 */ + qc->client_in.secret.len = SHA256_DIGEST_LENGTH; + qc->server_in.secret.len = SHA256_DIGEST_LENGTH; + + qc->client_in.key.len = EVP_CIPHER_key_length(cipher); + qc->server_in.key.len = EVP_CIPHER_key_length(cipher); + + qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); + qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); + + qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); + qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); + + struct { + ngx_str_t label; + ngx_str_t *key; + ngx_str_t *prk; + } seq[] = { + + /* draft-ietf-quic-tls-23#section-5.2 */ + { ngx_string("tls13 client in"), &qc->client_in.secret, &iss }, + { + ngx_string("tls13 quic key"), + &qc->client_in.key, + &qc->client_in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qc->client_in.iv, + &qc->client_in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qc->client_in.hp, + &qc->client_in.secret, + }, + { ngx_string("tls13 server in"), &qc->server_in.secret, &iss }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + ngx_string("tls13 quic key"), + &qc->server_in.key, + &qc->server_in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qc->server_in.iv, + &qc->server_in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qc->server_in.hp, + &qc->server_in.secret, + }, + + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, + seq[i].prk->data, seq[i].prk->len) + != NGX_OK) + { + return NGX_ERROR; + } + } + +// header protection + + uint8_t mask[16]; + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) + != NGX_OK) + { + return NGX_ERROR; + } + + u_char clearflags = flags ^ (mask[0] & 0x0f); + ngx_int_t pnl = (clearflags & 0x03) + 1; + uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic sample: %*s", m, buf); + + m = ngx_hex_dump(buf, mask, 5) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic mask: %*s", m, buf); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + } +#endif + +// packet protection + + ngx_str_t in; + in.data = b->pos; + in.len = plen - pnl; + + ngx_str_t ad; + ad.len = b->pos - b->start; + ad.data = ngx_pnalloc(c->pool, ad.len); + if (ad.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(ad.data, b->start, ad.len); + ad.data[0] = clearflags; + ad.data[ad.len - pnl] = (u_char)pn; + + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); + nonce[11] ^= pn; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic nonce: %*s, len: %uz", m, buf, 12); + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ad: %*s, len: %uz", m, buf, ad.len); + } +#endif + + ngx_str_t out; + + if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, + &in, &ad) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet payload: %*s%s, len: %uz", + m, buf, m < 512 ? "" : "...", out.len); + } +#endif + + if (out.data[0] != 0x06) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unexpected frame in initial packet"); + return NGX_ERROR; + } + + if (out.data[1] != 0x00) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unexpected CRYPTO offset in initial packet"); + return NGX_ERROR; + } + + uint8_t *crypto = &out.data[2]; + uint64_t crypto_len = ngx_quic_parse_int(&crypto); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initial packet CRYPTO length: %uL pp:%p:%p", + crypto_len, out.data, crypto); + + if (ngx_ssl_create_connection(ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + return NGX_ERROR; + } + + static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03"; + + if (SSL_set_quic_transport_params(c->ssl->connection, params, + sizeof(params)) == 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + if (!SSL_provide_quic_data(c->ssl->connection, + SSL_quic_read_level(c->ssl->connection), + crypto, crypto_len)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr == SSL_ERROR_SSL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) +{ + size_t m; + ssize_t n; + ngx_str_t out; + const EVP_CIPHER *cipher; + ngx_quic_connection_t *qc; + u_char buf[4096], *p, *b; + + qc = c->quic; + + n = bb->last - bb->pos; + p = bb->pos; + b = bb->start; + + m = ngx_hex_dump(buf, b, n) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handshake handler: %*s, len: %uz", m, buf, n); + + if ((p[0] & 0xf0) != 0xe0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type"); + return NGX_ERROR; + } + + ngx_int_t flags = *p++; + uint32_t version = ngx_quic_parse_uint32(p); + p += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flags:%xi version:%xD", flags, version); + + if (version != quic_version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); + return NGX_ERROR; + } + + if (*p++ != qc->dcid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } + + p += qc->dcid.len; + + if (*p++ != qc->scid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); + return NGX_ERROR; + } + + p += qc->scid.len; + + ngx_int_t plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + + if (plen > b + n - p) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); + return NGX_ERROR; + } + + u_char *sample = p + 4; + + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic sample: %*s", m, buf); + +// header protection + + uint8_t mask[16]; + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample) + != NGX_OK) + { + return NGX_ERROR; + } + + u_char clearflags = flags ^ (mask[0] & 0x0f); + ngx_int_t pnl = (clearflags & 0x03) + 1; + uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, mask, 5) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic mask: %*s", m, buf); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear flags: %xi", clearflags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + } +#endif + +// packet protection + + ngx_str_t in; + in.data = p; + in.len = plen - pnl; + + ngx_str_t ad; + ad.len = p - b; + ad.data = ngx_pnalloc(c->pool, ad.len); + if (ad.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(ad.data, b, ad.len); + ad.data[0] = clearflags; + ad.data[ad.len - pnl] = (u_char)pn; + + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv); + nonce[11] ^= pn; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic nonce: %*s, len: %uz", m, buf, 12); + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ad: %*s, len: %uz", m, buf, ad.len); + } +#endif + + u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher: %s", name); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { + cipher = EVP_aes_128_gcm(); + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + cipher = EVP_aes_256_gcm(); + + } else { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet payload: %*s%s, len: %uz", + m, buf, m < 512 ? "" : "...", out.len); + } +#endif + + return NGX_OK; +} uint64_t @@ -53,7 +1078,7 @@ ngx_quic_build_int(u_char **pos, uint64_t value) } -uint64_t +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) { u_char *p; @@ -71,7 +1096,7 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) } -ngx_int_t +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, const u_char *secret, size_t secret_len, const u_char *salt, size_t salt_len) @@ -119,7 +1144,7 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, } -ngx_int_t +static ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len) { @@ -168,7 +1193,7 @@ ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, } -ngx_int_t +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) { @@ -214,7 +1239,7 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, } -ngx_int_t +static ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) @@ -319,7 +1344,7 @@ ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, } -ngx_int_t +static ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) @@ -422,7 +1447,7 @@ ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher, } -ngx_int_t +static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index ec7974094..edc9d8078 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -1,6 +1,6 @@ /* - * + * Copyright (C) Nginx, Inc. */ @@ -10,95 +10,11 @@ #include -#define quic_version 0xff000018 +/* TODO: get rid somehow of ssl argument? */ +ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b); +ngx_int_t ngx_quic_output(ngx_connection_t *c); +void ngx_quic_init_ssl_methods(SSL_CTX* ctx); -typedef struct { - ngx_str_t secret; - ngx_str_t key; - ngx_str_t iv; - ngx_str_t hp; -} ngx_quic_secret_t; - - -struct ngx_quic_connection_s { - ngx_str_t scid; - ngx_str_t dcid; - ngx_str_t token; - - ngx_quic_secret_t client_in; - ngx_quic_secret_t client_hs; - ngx_quic_secret_t client_ad; - ngx_quic_secret_t server_in; - ngx_quic_secret_t server_hs; - ngx_quic_secret_t server_ad; -}; - - -uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); -uint64_t ngx_quic_parse_int(u_char **pos); -void ngx_quic_build_int(u_char **pos, uint64_t value); - -ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, - const EVP_MD *digest, const u_char *secret, size_t secret_len, - const u_char *salt, size_t salt_len); -ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, - const EVP_MD *digest, const u_char *prk, size_t prk_len, - const u_char *info, size_t info_len); - -ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, - ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); - -ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, - const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, - u_char *nonce, ngx_str_t *in, ngx_str_t *ad); -ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, - const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, - u_char *nonce, ngx_str_t *in, ngx_str_t *ad); - -ngx_int_t -ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in); - - -#if (NGX_HAVE_NONALIGNED) - -#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) -#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) - -#else - -#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) -#define ngx_quic_parse_uint32(p) \ - ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) - -#endif - - -#define ngx_quic_write_uint16_aligned(p, s) \ - (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) -#define ngx_quic_write_uint32_aligned(p, s) \ - (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) - -#if (NGX_HAVE_NONALIGNED) - -#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned -#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned - -#else - -#define ngx_quic_write_uint16(p, s) \ - ((p)[0] = (u_char) ((s) >> 8), \ - (p)[1] = (u_char) (s), \ - (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32(p, s) \ - ((p)[0] = (u_char) ((s) >> 24), \ - (p)[1] = (u_char) ((s) >> 16), \ - (p)[2] = (u_char) ((s) >> 8), \ - (p)[3] = (u_char) (s), \ - (p) + sizeof(uint32_t)) - -#endif #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index d40f50496..6d89fef24 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -661,402 +661,22 @@ ngx_http_alloc_request(ngx_connection_t *c) static void ngx_http_quic_handshake(ngx_event_t *rev) { - int n, sslerr; -#if (NGX_DEBUG) - u_char buf[512]; - size_t m; -#endif - ngx_buf_t *b; ngx_connection_t *c; ngx_http_connection_t *hc; - ngx_quic_connection_t *qc; ngx_http_ssl_srv_conf_t *sscf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake"); c = rev->data; hc = c->data; - b = c->buffer; - - if ((b->pos[0] & 0xf0) != 0xc0) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet"); - ngx_http_close_connection(c); - return; - } - - if (ngx_buf_size(b) < 1200) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "too small UDP datagram"); - ngx_http_close_connection(c); - return; - } - - ngx_int_t flags = *b->pos++; - uint32_t version = ngx_quic_parse_uint32(b->pos); - b->pos += sizeof(uint32_t); - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic flags:%xi version:%xD", flags, version); - - if (version != quic_version) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); - ngx_http_close_connection(c); - return; - } - - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { - ngx_http_close_connection(c); - return; - } - - c->quic = qc; - - qc->dcid.len = *b->pos++; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); - b->pos += qc->dcid.len; - - qc->scid.len = *b->pos++; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); - if (qc->scid.data == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); - b->pos += qc->scid.len; - - qc->token.len = ngx_quic_parse_int(&b->pos); - qc->token.data = ngx_pnalloc(c->pool, qc->token.len); - if (qc->token.data == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(qc->token.data, b->pos, qc->token.len); - b->pos += qc->token.len; - - ngx_int_t plen = ngx_quic_parse_int(&b->pos); - - if (plen > b->last - b->pos) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated initial packet"); - ngx_http_close_connection(c); - return; - } - - /* draft-ietf-quic-tls-23#section-5.4.2: - * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.[34]: - * AES-Based and ChaCha20-Based header protections sample 16 bytes - */ - u_char *sample = b->pos + 4; - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len); - - m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic SCID: %*s, len: %uz", m, buf, qc->scid.len); - - m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic token: %*s, len: %uz", m, buf, qc->token.len); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet length: %d", plen); - - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic sample: %*s", m, buf); - } -#endif - -// initial secret - - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; - static const uint8_t salt[20] = - "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" - "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; - - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - - cipher = EVP_aes_128_gcm(); - digest = EVP_sha256(); - - if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, - salt, sizeof(salt)) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - - ngx_str_t iss = { - .data = is, - .len = is_len - }; - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic salt: %*s, len: %uz", m, buf, sizeof(salt)); - - m = ngx_hex_dump(buf, is, is_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic initial secret: %*s, len: %uz", m, buf, is_len); - } -#endif - - /* draft-ietf-quic-tls-23#section-5.2 */ - qc->client_in.secret.len = SHA256_DIGEST_LENGTH; - qc->server_in.secret.len = SHA256_DIGEST_LENGTH; - - qc->client_in.key.len = EVP_CIPHER_key_length(cipher); - qc->server_in.key.len = EVP_CIPHER_key_length(cipher); - - qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); - qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); - - qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); - qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); - - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *prk; - } seq[] = { - - /* draft-ietf-quic-tls-23#section-5.2 */ - { ngx_string("tls13 client in"), &qc->client_in.secret, &iss }, - { - ngx_string("tls13 quic key"), - &qc->client_in.key, - &qc->client_in.secret, - }, - { - ngx_string("tls13 quic iv"), - &qc->client_in.iv, - &qc->client_in.secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &qc->client_in.hp, - &qc->client_in.secret, - }, - { ngx_string("tls13 server in"), &qc->server_in.secret, &iss }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - ngx_string("tls13 quic key"), - &qc->server_in.key, - &qc->server_in.secret, - }, - { - ngx_string("tls13 quic iv"), - &qc->server_in.iv, - &qc->server_in.secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &qc->server_in.hp, - &qc->server_in.secret, - }, - - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, - seq[i].prk->data, seq[i].prk->len) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - } - -// header protection - - uint8_t mask[16]; - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - - u_char clearflags = flags ^ (mask[0] & 0x0f); - ngx_int_t pnl = (clearflags & 0x03) + 1; - uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic sample: %*s", m, buf); - - m = ngx_hex_dump(buf, mask, 5) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic mask: %*s", m, buf); - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - } -#endif - -// packet protection - - ngx_str_t in; - in.data = b->pos; - in.len = plen - pnl; - - ngx_str_t ad; - ad.len = b->pos - b->start; - ad.data = ngx_pnalloc(c->pool, ad.len); - if (ad.data == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(ad.data, b->start, ad.len); - ad.data[0] = clearflags; - ad.data[ad.len - pnl] = (u_char)pn; - - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); - nonce[11] ^= pn; - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic nonce: %*s, len: %uz", m, buf, 12); - - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic ad: %*s, len: %uz", m, buf, ad.len); - } -#endif - - ngx_str_t out; - - if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, - &in, &ad) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", out.len); - } -#endif - - if (out.data[0] != 0x06) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, - "unexpected frame in initial packet"); - ngx_http_close_connection(c); - return; - } - - if (out.data[1] != 0x00) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, - "unexpected CRYPTO offset in initial packet"); - ngx_http_close_connection(c); - return; - } - - uint8_t *crypto = &out.data[2]; - uint64_t crypto_len = ngx_quic_parse_int(&crypto); - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic initial packet CRYPTO length: %uL pp:%p:%p", - crypto_len, out.data, crypto); sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - - static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03"; - - if (SSL_set_quic_transport_params(c->ssl->connection, params, - sizeof(params)) == 0) - { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, - "SSL_set_quic_transport_params() failed"); + if (ngx_quic_input(c, &sscf->ssl, c->buffer) != NGX_OK) { ngx_http_close_connection(c); return; } - n = SSL_do_handshake(c->ssl->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); - - if (!SSL_provide_quic_data(c->ssl->connection, - SSL_quic_read_level(c->ssl->connection), - crypto, crypto_len)) - { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, - "SSL_provide_quic_data() failed"); - ngx_http_close_connection(c); - return; - } - - n = SSL_do_handshake(c->ssl->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - - if (sslerr == SSL_ERROR_SSL) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - } - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); - if (!rev->timer_set) { ngx_add_timer(rev, c->listening->post_accept_timeout); } @@ -1069,17 +689,16 @@ ngx_http_quic_handshake(ngx_event_t *rev) static void ngx_http_quic_handshake_handler(ngx_event_t *rev) { - size_t m; ssize_t n; - ngx_str_t out; ngx_connection_t *c; - const EVP_CIPHER *cipher; - ngx_quic_connection_t *qc; - u_char buf[4096], b[512], *p; + u_char buf[512]; + ngx_buf_t b; + + b.start = buf; + b.end = buf + 512; + b.pos = b.last = b.start; c = rev->data; - qc = c->quic; - p = b; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake handler"); @@ -1094,7 +713,7 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } - n = c->recv(c, b, sizeof(b)); + n = c->recv(c, b.start, b.end - b.start); if (n == NGX_AGAIN) { return; @@ -1106,166 +725,12 @@ ngx_http_quic_handshake_handler(ngx_event_t *rev) return; } - m = ngx_hex_dump(buf, b, n) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic handshake handler: %*s, len: %uz", m, buf, n); + b.last += n; - if ((p[0] & 0xf0) != 0xe0) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid packet type"); + if (ngx_quic_input(c, NULL, &b) != NGX_OK) { ngx_http_close_connection(c); return; } - - ngx_int_t flags = *p++; - uint32_t version = ngx_quic_parse_uint32(p); - p += sizeof(uint32_t); - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic flags:%xi version:%xD", flags, version); - - if (version != quic_version) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); - ngx_http_close_connection(c); - return; - } - - if (*p++ != qc->dcid.len) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcidl"); - ngx_http_close_connection(c); - return; - } - - if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic dcid"); - ngx_http_close_connection(c); - return; - } - - p += qc->dcid.len; - - if (*p++ != qc->scid.len) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scidl"); - ngx_http_close_connection(c); - return; - } - - if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unexpected quic scid"); - ngx_http_close_connection(c); - return; - } - - p += qc->scid.len; - - ngx_int_t plen = ngx_quic_parse_int(&p); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet length: %d", plen); - - if (plen > b + n - p) { - ngx_log_error(NGX_LOG_INFO, rev->log, 0, "truncated handshake packet"); - ngx_http_close_connection(c); - return; - } - - u_char *sample = p + 4; - - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic sample: %*s", m, buf); - -// header protection - - uint8_t mask[16]; - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - - u_char clearflags = flags ^ (mask[0] & 0x0f); - ngx_int_t pnl = (clearflags & 0x03) + 1; - uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, mask, 5) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic mask: %*s", m, buf); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic clear flags: %xi", clearflags); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - } -#endif - -// packet protection - - ngx_str_t in; - in.data = p; - in.len = plen - pnl; - - ngx_str_t ad; - ad.len = p - b; - ad.data = ngx_pnalloc(c->pool, ad.len); - if (ad.data == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(ad.data, b, ad.len); - ad.data[0] = clearflags; - ad.data[ad.len - pnl] = (u_char)pn; - - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv); - nonce[11] ^= pn; - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic nonce: %*s, len: %uz", m, buf, 12); - - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic ad: %*s, len: %uz", m, buf, ad.len); - } -#endif - - u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic ssl cipher: %s", name); - - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { - cipher = EVP_aes_128_gcm(); - - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { - cipher = EVP_aes_256_gcm(); - - } else { - ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, "unexpected cipher"); - ngx_http_close_connection(c); - return; - } - - if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad) - != NGX_OK) - { - ngx_http_close_connection(c); - return; - } - -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, - "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", out.len); - } -#endif - } -- cgit v1.2.3 From 820be1b8466ebe508fa91b1d706e5b3ef614c5c1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 2 Mar 2020 21:38:03 +0300 Subject: Aded the "ngx_quic_hexdump" macro. ngx_quic_hexdump0(log, format, buffer, buffer_size); - logs hexdump of buffer to specified error log ngx_quic_hexdump0(c->log, "this is foo:", foo.data, foo.len); ngx_quic_hexdump(log, format, buffer, buffer_size, ...) - same as hexdump0, but more format/args possible: ngx_quic_hexdump(c->log, "a=%d b=%d, foo is:", foo.data, foo.len, a, b); --- src/event/ngx_event_quic.c | 245 +++++++++++++-------------------------------- 1 file changed, 69 insertions(+), 176 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 30045ecc1..250395212 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -47,6 +47,34 @@ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#if (NGX_DEBUG) + +#define ngx_quic_hexdump(log, fmt, data, len, ...) \ +do { \ + ngx_int_t m; \ + u_char buf[2048]; \ + \ + if (log->log_level & NGX_LOG_DEBUG_EVENT) { \ + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \ + ngx_log_debug(NGX_LOG_DEBUG_EVENT, log, 0, \ + "%s: " fmt " %*s%s, len: %uz", \ + __FUNCTION__, __VA_ARGS__, m, buf, \ + len < 2048 ? "" : "...", len); \ + } \ +} while (0) + +#else + +#define ngx_quic_hexdump(log, fmt, data, len, ...) + +#endif + +#define ngx_quic_hexdump0(log, fmt, data, len) \ + ngx_quic_hexdump(log, fmt "%s", data, len, "") \ + + + /* TODO: real states, these are stubs */ typedef enum { NGX_QUIC_ST_INITIAL, @@ -186,22 +214,8 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, //ngx_ssl_handshake_log(c); // TODO: enable again -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - u_char buf[64]; - size_t m; - - m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: read %*s, len: %uz, level:%d", - m, buf, secret_len, (int) level); - - m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "set_encryption_secrets: write %*s, len: %uz, level:%d", - m, buf, secret_len, (int) level); - } -#endif + ngx_quic_hexdump(c->log, "level:%d read", read_secret, secret_len, level); + ngx_quic_hexdump(c->log, "level:%d read", write_secret, secret_len, level); name = (u_char *) SSL_get_cipher(ssl_conn); @@ -279,14 +293,13 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { u_char *p, *pnp, *name, *nonce, *sample; - ngx_int_t m; ngx_str_t in, out, ad; static int pn; const EVP_CIPHER *cipher; ngx_connection_t *c; ngx_quic_secret_t *secret; ngx_quic_connection_t *qc; - u_char buf[2048], mask[16]; + u_char mask[16]; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; @@ -307,10 +320,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data: %*s%s, len: %uz, level:%d", - m, buf, len < 2048 ? "" : "...", len, (int) level); + ngx_quic_hexdump(c->log, "level:%d read", data, len, level); in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); if (in.data == 0) { @@ -369,11 +379,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ad.len = p - ad.data; - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data ad: %*s, len: %uz", - m, buf, ad.len); - + ngx_quic_hexdump0(c->log, "ad", data, len); name = (u_char *) SSL_get_cipher(ssl_conn); @@ -394,14 +400,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, nonce[11] ^= (pn - 1); } - m = ngx_hex_dump(buf, (u_char *) secret->iv.data, 12) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data sample: server_iv %*s", - m, buf); - m = ngx_hex_dump(buf, (u_char *) nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data sample: n=%d nonce %*s", - pn - 1, m, buf); + ngx_quic_hexdump0(c->log, "server_iv", secret->iv.data, 12); + ngx_quic_hexdump(c->log, "sample: n=%d nonce", nonce, 12, pn - 1); if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK) { @@ -413,20 +413,9 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - m = ngx_hex_dump(buf, (u_char *) sample, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data sample: %*s, len: %uz", - m, buf, 16); - - m = ngx_hex_dump(buf, (u_char *) mask, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data mask: %*s, len: %uz", - m, buf, 16); - - m = ngx_hex_dump(buf, (u_char *) secret->hp.data, 16) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data hp_key: %*s, len: %uz", - m, buf, 16); + ngx_quic_hexdump0(c->log, "sample", sample, 16); + ngx_quic_hexdump0(c->log, "mask", mask, 16); + ngx_quic_hexdump0(c->log, "hp_key", secret->hp.data, 16); // header protection, pnl = 0 ad.data[0] ^= mask[0] & 0x0f; @@ -440,10 +429,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, p = ngx_cpymem(packet, ad.data, ad.len); p = ngx_cpymem(p, out.data, out.len); - m = ngx_hex_dump(buf, (u_char *) packet, ngx_min(1024, p - packet)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data packet: %*s%s, len: %uz", - m, buf, len < 2048 ? "" : "...", p - packet); + ngx_quic_hexdump0(c->log, "packet", packet, p - packet); // TODO: save state of data to send into qc (push into queue) @@ -490,13 +476,8 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) { - int n, sslerr; -#if (NGX_DEBUG) - u_char buf[512]; - size_t m; -#endif - - ngx_quic_connection_t *qc; + int n, sslerr; + ngx_quic_connection_t *qc; if ((b->pos[0] & 0xf0) != 0xc0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet"); @@ -568,28 +549,13 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) */ u_char *sample = b->pos + 4; -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len); - - m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SCID: %*s, len: %uz", m, buf, qc->scid.len); - - m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic token: %*s, len: %uz", m, buf, qc->token.len); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); + ngx_quic_hexdump0(c->log, "DCID", qc->dcid.data, qc->dcid.len); + ngx_quic_hexdump0(c->log, "SCID", qc->scid.data, qc->scid.len); + ngx_quic_hexdump0(c->log, "token", qc->token.data, qc->token.len); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + ngx_quic_hexdump0(c->log, "sample", sample, 16); - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic sample: %*s", m, buf); - } -#endif // initial secret @@ -619,17 +585,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) .len = is_len }; -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic salt: %*s, len: %uz", m, buf, sizeof(salt)); - - m = ngx_hex_dump(buf, is, is_len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic initial secret: %*s, len: %uz", m, buf, is_len); - } -#endif + ngx_quic_hexdump0(c->log, "salt", salt, sizeof(salt)); + ngx_quic_hexdump0(c->log, "initial secret", is, is_len); /* draft-ietf-quic-tls-23#section-5.2 */ qc->client_in.secret.len = SHA256_DIGEST_LENGTH; @@ -712,20 +669,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) ngx_int_t pnl = (clearflags & 0x03) + 1; uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic sample: %*s", m, buf); - - m = ngx_hex_dump(buf, mask, 5) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic mask: %*s", m, buf); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - } -#endif + ngx_quic_hexdump0(c->log, "sample", sample, 16); + ngx_quic_hexdump0(c->log, "mask", mask, 5); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); // packet protection @@ -747,17 +694,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); nonce[11] ^= pn; -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic nonce: %*s, len: %uz", m, buf, 12); - - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ad: %*s, len: %uz", m, buf, ad.len); - } -#endif + ngx_quic_hexdump0(c->log, "nonce", nonce, 12); + ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); ngx_str_t out; @@ -768,14 +706,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) return NGX_ERROR; } -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", out.len); - } -#endif + ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); if (out.data[0] != 0x06) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -862,12 +793,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) { - size_t m; ssize_t n; ngx_str_t out; const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; - u_char buf[4096], *p, *b; + u_char *p, *b; qc = c->quic; @@ -875,9 +805,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) p = bb->pos; b = bb->start; - m = ngx_hex_dump(buf, b, n) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake handler: %*s, len: %uz", m, buf, n); + ngx_quic_hexdump0(c->log, "input", buf, n); if ((p[0] & 0xf0) != 0xe0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type"); @@ -932,8 +860,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) u_char *sample = p + 4; - m = ngx_hex_dump(buf, sample, 16) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic sample: %*s", m, buf); + ngx_quic_hexdump0(c->log, "sample", sample, 16); // header protection @@ -948,17 +875,11 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_int_t pnl = (clearflags & 0x03) + 1; uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, mask, 5) - buf; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic mask: %*s", m, buf); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear flags: %xi", clearflags); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - } -#endif + ngx_quic_hexdump0(c->log, "mask", mask, 5); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear flags: %xi", clearflags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); // packet protection @@ -980,17 +901,8 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv); nonce[11] ^= pn; -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, nonce, 12) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic nonce: %*s, len: %uz", m, buf, 12); - - m = ngx_hex_dump(buf, ad.data, ad.len) - buf; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ad: %*s, len: %uz", m, buf, ad.len); - } -#endif + ngx_quic_hexdump0(c->log, "nonce", nonce, 12); + ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1015,14 +927,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) return NGX_ERROR; } -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, out.data, ngx_min(out.len, 256)) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet payload: %*s%s, len: %uz", - m, buf, m < 512 ? "" : "...", out.len); - } -#endif + ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); return NGX_OK; } @@ -1152,11 +1057,6 @@ ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, size_t info_len; uint8_t info[20]; -#if (NGX_DEBUG) - u_char buf[512]; - size_t m; -#endif - out->data = ngx_pnalloc(c->pool, out->len); if (out->data == NULL) { return NGX_ERROR; @@ -1179,15 +1079,8 @@ ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, return NGX_ERROR; } - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, info, info_len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "%V info: %*s, len: %uz", label, m, buf, info_len); - - m = ngx_hex_dump(buf, out->data, out->len) - buf; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "%V key: %*s, len: %uz", label, m, buf, out->len); - } + ngx_quic_hexdump(c->log, "%V info", info, info_len, label); + ngx_quic_hexdump(c->log, "%V key", out->data, out->len, label); return NGX_OK; } -- cgit v1.2.3 From d2deb6d6b407d79dc6a75114da2f295e39f5e3ea Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 3 Mar 2020 13:30:30 +0300 Subject: Split frame and packet generation into separate steps. While there, a number of QUIC constants from spec defined and magic numbers were replaced. --- src/event/ngx_event_quic.c | 271 ++++++++++++++++++++++++++++++--------------- 1 file changed, 181 insertions(+), 90 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 250395212..2031c3318 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -74,6 +74,39 @@ do { \ ngx_quic_hexdump(log, fmt "%s", data, len, "") \ +/* 17.2. Long Header Packets */ + +#define NGX_QUIC_PKT_LONG 0x80 + +#define NGX_QUIC_PKT_INITIAL 0xc0 +#define NGX_QUIC_PKT_HANDSHAKE 0xe0 + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM 0x08 // - 0x0f +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 // XXX +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 // XXX +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1a +#define NGX_QUIC_FT_PATH_RESPONSE 0x1b +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1c +#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1d // XXX +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1e + /* TODO: real states, these are stubs */ typedef enum { @@ -102,6 +135,11 @@ struct ngx_quic_connection_s { ngx_str_t dcid; ngx_str_t token; + /* current packet numbers for each namespace */ + ngx_uint_t initial_pn; + ngx_uint_t handshake_pn; + ngx_uint_t appdata_pn; + ngx_quic_secret_t client_in; ngx_quic_secret_t client_hs; ngx_quic_secret_t client_ad; @@ -111,6 +149,19 @@ struct ngx_quic_connection_s { }; +typedef enum ssl_encryption_level_t ngx_quic_level_t; + +typedef struct { + ngx_quic_secret_t *secret; + ngx_uint_t type; + ngx_uint_t *number; + ngx_uint_t flags; + ngx_uint_t version; + ngx_str_t *token; + ngx_quic_level_t level; +} ngx_quic_header_t; + + static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); @@ -120,6 +171,9 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, const uint8_t *write_secret, size_t secret_len); static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, + ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, + ngx_str_t *res); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); @@ -288,98 +342,50 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } -static int -ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +static ngx_int_t +ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) { - u_char *p, *pnp, *name, *nonce, *sample; - ngx_str_t in, out, ad; - static int pn; - const EVP_CIPHER *cipher; - ngx_connection_t *c; - ngx_quic_secret_t *secret; - ngx_quic_connection_t *qc; - u_char mask[16]; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = c->quic; - - //ngx_ssl_handshake_log(c); // TODO: enable again - - switch (level) { - - case ssl_encryption_initial: - secret = &qc->server_in; - break; - - case ssl_encryption_handshake: - secret = &qc->server_hs; - break; - - default: - return 0; - } - - ngx_quic_hexdump(c->log, "level:%d read", data, len, level); - - in.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); - if (in.data == 0) { - return 0; - } - - p = in.data; - ngx_quic_build_int(&p, 6); // crypto frame - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, len); - p = ngx_cpymem(p, data, len); + u_char *p, *pnp, *name, *nonce, *sample, *packet; + ngx_str_t ad, out; + const EVP_CIPHER *cipher; + ngx_quic_connection_t *qc; - if (level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 2); // ack frame - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - } + u_char mask[16]; - in.len = p - in.data; - out.len = in.len + EVP_GCM_TLS_TAG_LEN; + qc = c->quic; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data: clear_len:%uz, ciphertext_len:%uz", - in.len, out.len); + out.len = payload->len + EVP_GCM_TLS_TAG_LEN; ad.data = ngx_alloc(346 /*max header*/, c->log); if (ad.data == 0) { - return 0; + return NGX_ERROR; } p = ad.data; - if (level == ssl_encryption_initial) { - *p++ = 0xc0; // initial, packet number len - } else if (level == ssl_encryption_handshake) { - *p++ = 0xe0; // handshake, packet number len - } + + *p++ = pkt->flags; + p = ngx_quic_write_uint32(p, quic_version); + *p++ = qc->scid.len; p = ngx_cpymem(p, qc->scid.data, qc->scid.len); + *p++ = qc->dcid.len; p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); - if (level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 0); // token length + + if (pkt->token) { // if pkt->flags & initial ? + ngx_quic_build_int(&p, pkt->token->len); } + ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) pnp = p; - if (level == ssl_encryption_initial) { - *p++ = 0; // packet number 0 - - } else if (level == ssl_encryption_handshake) { - *p++ = pn++; - } + *p++ = (*pkt->number)++; ad.len = p - ad.data; - ngx_quic_hexdump0(c->log, "ad", data, len); + ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); name = (u_char *) SSL_get_cipher(ssl_conn); @@ -392,49 +398,134 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, cipher = EVP_aes_256_gcm(); } else { - return 0; + return NGX_ERROR; } - nonce = ngx_pstrdup(c->pool, &secret->iv); - if (level == ssl_encryption_handshake) { - nonce[11] ^= (pn - 1); + nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); + if (pkt->level == ssl_encryption_handshake) { + nonce[11] ^= (*pkt->number - 1); } - ngx_quic_hexdump0(c->log, "server_iv", secret->iv.data, 12); - ngx_quic_hexdump(c->log, "sample: n=%d nonce", nonce, 12, pn - 1); + ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - if (ngx_quic_tls_seal(c, cipher, secret, &out, nonce, &in, &ad) != NGX_OK) - { - return 0; + if (ngx_quic_tls_seal(c, cipher, pkt->secret, &out, nonce, payload, &ad) != NGX_OK) { + return NGX_ERROR; } sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), secret, mask, sample) != NGX_OK) { - return 0; + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) != NGX_OK) { + return NGX_ERROR; } ngx_quic_hexdump0(c->log, "sample", sample, 16); ngx_quic_hexdump0(c->log, "mask", mask, 16); - ngx_quic_hexdump0(c->log, "hp_key", secret->hp.data, 16); + ngx_quic_hexdump0(c->log, "hp_key", pkt->secret->hp.data, 16); // header protection, pnl = 0 ad.data[0] ^= mask[0] & 0x0f; *pnp ^= mask[1]; - u_char *packet = ngx_alloc(ad.len + out.len, c->log); + packet = ngx_alloc(ad.len + out.len, c->log); if (packet == 0) { - return 0; + return NGX_ERROR; } p = ngx_cpymem(packet, ad.data, ad.len); p = ngx_cpymem(p, out.data, out.len); - ngx_quic_hexdump0(c->log, "packet", packet, p - packet); + res->data = packet; + res->len = p - packet; - // TODO: save state of data to send into qc (push into queue) + return NGX_OK; +} + + +static void +ngx_quic_create_ack(u_char **p) +{ + ngx_quic_build_int(p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(p, 0); + ngx_quic_build_int(p, 0); + ngx_quic_build_int(p, 0); + ngx_quic_build_int(p, 0); +} - qc->out.data = packet; - qc->out.len = p - packet; + +static void +ngx_quic_create_crypto(u_char **p, u_char *data, size_t len) +{ + ngx_quic_build_int(p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(p, 0); + ngx_quic_build_int(p, len); + *p = ngx_cpymem(*p, data, len); +} + + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p; + ngx_str_t payload, res; + ngx_connection_t *c; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + + ngx_str_t initial_token = ngx_null_string; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = c->quic; + + //ngx_ssl_handshake_log(c); // TODO: enable again + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + pkt.level = level; + + payload.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); + if (payload.data == 0) { + return 0; + } + p = payload.data; + + ngx_quic_create_crypto(&p, (u_char *) data, len); + + if (level == ssl_encryption_initial) { + ngx_quic_create_ack(&p); + + pkt.number = &qc->initial_pn; + pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.secret = &qc->server_in; + + pkt.token = &initial_token; + + } else if (level == ssl_encryption_handshake) { + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->server_hs; + + pkt.token = NULL; + + } else { + pkt.number = &qc->appdata_pn; + } + + payload.len = p - payload.data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data: clear_len:%uz", + payload.len); + + if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res) + != NGX_OK) + { + return 0; + } + + // TODO: save state of data to send into qc (push into queue) + qc->out = res; if (ngx_quic_output(c) != NGX_OK) { return 0; @@ -479,7 +570,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) int n, sslerr; ngx_quic_connection_t *qc; - if ((b->pos[0] & 0xf0) != 0xc0) { + if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet"); return NGX_ERROR; } @@ -807,7 +898,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_quic_hexdump0(c->log, "input", buf, n); - if ((p[0] & 0xf0) != 0xe0) { + if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type"); return NGX_ERROR; } -- cgit v1.2.3 From 3c1707212273182c60560d980cb2b3f95f702d74 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 3 Mar 2020 17:25:02 +0300 Subject: QUIC handshake final bits. Added handling of client Finished, both feeding and acknowledgement. This includes sending NST in 1-RTT triggered by a handshake process. --- src/event/ngx_event_quic.c | 216 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2031c3318..20d91117c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -174,6 +174,9 @@ static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, ngx_str_t *res); +static ngx_int_t ngx_quic_create_short_packet(ngx_connection_t *c, + ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, + ngx_str_t *res); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); @@ -441,14 +444,110 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, } +static ngx_int_t +ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +{ + u_char *p, *pnp, *name, *nonce, *sample, *packet; + ngx_str_t ad, out; + const EVP_CIPHER *cipher; + ngx_quic_connection_t *qc; + + u_char mask[16]; + + qc = c->quic; + + out.len = payload->len + EVP_GCM_TLS_TAG_LEN; + + ad.data = ngx_alloc(25 /*max header*/, c->log); + if (ad.data == 0) { + return NGX_ERROR; + } + + p = ad.data; + + *p++ = 0x40; + + p = ngx_cpymem(p, qc->scid.data, qc->scid.len); + + pnp = p; + + *p++ = (*pkt->number)++; + + ad.len = p - ad.data; + + ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); + + name = (u_char *) SSL_get_cipher(ssl_conn); + + if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 + || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) + { + cipher = EVP_aes_128_gcm(); + + } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + cipher = EVP_aes_256_gcm(); + + } else { + return NGX_ERROR; + } + + nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); + if (pkt->level == ssl_encryption_handshake) { + nonce[11] ^= (*pkt->number - 1); + } + + ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(c->log, "nonce", nonce, 12); + + if (ngx_quic_tls_seal(c, cipher, pkt->secret, &out, nonce, payload, &ad) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_hexdump0(c->log, "out", out.data, out.len); + + sample = &out.data[3]; // pnl=0 + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_hexdump0(c->log, "sample", sample, 16); + ngx_quic_hexdump0(c->log, "mask", mask, 16); + ngx_quic_hexdump0(c->log, "hp_key", pkt->secret->hp.data, 16); + + // header protection, pnl = 0 + ad.data[0] ^= mask[0] & 0x1f; + *pnp ^= mask[1]; + + packet = ngx_alloc(ad.len + out.len, c->log); + if (packet == 0) { + return NGX_ERROR; + } + + p = ngx_cpymem(packet, ad.data, ad.len); + p = ngx_cpymem(p, out.data, out.len); + + ngx_quic_hexdump0(c->log, "packet", packet, p - packet); + + res->data = packet; + res->len = p - packet; + + return NGX_OK; +} + + static void -ngx_quic_create_ack(u_char **p) +ngx_quic_create_ack(u_char **p, uint64_t num) { ngx_quic_build_int(p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(p, num); ngx_quic_build_int(p, 0); ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, 0); + ngx_quic_build_int(p, num); } @@ -493,7 +592,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_create_crypto(&p, (u_char *) data, len); if (level == ssl_encryption_initial) { - ngx_quic_create_ack(&p); + ngx_quic_create_ack(&p, 0); pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; @@ -510,6 +609,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } else { pkt.number = &qc->appdata_pn; + pkt.secret = &qc->server_ad; } payload.len = p - payload.data; @@ -518,10 +618,21 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "ngx_quic_add_handshake_data: clear_len:%uz", payload.len); - if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res) - != NGX_OK) - { - return 0; + if (level == ssl_encryption_application) { + + if (ngx_quic_create_short_packet(c, ssl_conn, &pkt, &payload, &res) + != NGX_OK) + { + return 0; + } + + } else { + + if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res) + != NGX_OK) + { + return 0; + } } // TODO: save state of data to send into qc (push into queue) @@ -884,11 +995,12 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) { + int sslerr; ssize_t n; ngx_str_t out; const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; - u_char *p, *b; + u_char *p, *b; qc = c->quic; @@ -1020,6 +1132,92 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); + if (out.data[0] != 0x06) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "non-CRYPTO frame in HS packet, skipping"); + return NGX_OK; + } + + if (out.data[1] != 0x00) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "not yet supported CRYPTO offset in initial packet"); + return NGX_ERROR; + } + + uint8_t *crypto = &out.data[2]; + uint64_t crypto_len = ngx_quic_parse_int(&crypto); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic Handshake packet CRYPTO length: %uL pp:%p:%p", + crypto_len, out.data, crypto); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + if (!SSL_provide_quic_data(c->ssl->connection, + SSL_quic_read_level(c->ssl->connection), + crypto, crypto_len)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(c->ssl->connection); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(c->ssl->connection, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr == SSL_ERROR_SSL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(c->ssl->connection), + (int) SSL_quic_write_level(c->ssl->connection)); + + // ACK Client Finished + + ngx_str_t payload, res; + ngx_quic_header_t pkt; + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + pkt.level = ssl_encryption_handshake; + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->server_hs; + + payload.data = ngx_alloc(5 /*minimal ACK*/, c->log); + if (payload.data == 0) { + return 0; + } + + p = payload.data; + ngx_quic_create_ack(&p, pn); + + payload.len = p - payload.data; + + if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, &payload, &res) + != NGX_OK) + { + return 0; + } + + qc->out = res; + + if (ngx_quic_output(c) != NGX_OK) { + return 0; + } + return NGX_OK; } -- cgit v1.2.3 From 309cdf496d686be504b1c684e480eb8edf5a0f43 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 4 Mar 2020 15:52:12 +0300 Subject: Implemented improved version of quic_output(). Now handshake generates frames, and they are queued in c->quic->frames. The ngx_quic_output() is called from ngx_quic_flush_flight() or manually, processes the queue and encrypts all frames according to required encryption level. --- src/event/ngx_event_quic.c | 371 +++++++++++++++++++++++++++++++++------------ 1 file changed, 276 insertions(+), 95 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 20d91117c..75947a633 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -124,12 +124,38 @@ typedef struct { } ngx_quic_secret_t; +typedef enum ssl_encryption_level_t ngx_quic_level_t; + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +typedef struct { + ngx_uint_t pn; + // ngx_uint_t nranges; + // ... +} ngx_quic_ack_frame_t; + +typedef ngx_str_t ngx_quic_crypto_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + ngx_quic_level_t level; + ngx_quic_frame_t *next; + union { + ngx_quic_crypto_frame_t crypto; + ngx_quic_ack_frame_t ack; + // more frames + } u; + + u_char info[128]; // for debug purposes +}; + + struct ngx_quic_connection_s { ngx_quic_state_t state; ngx_ssl_t *ssl; - ngx_str_t out; // stub for some kind of output queue + ngx_quic_frame_t *frames; ngx_str_t scid; ngx_str_t dcid; @@ -149,8 +175,6 @@ struct ngx_quic_connection_s { }; -typedef enum ssl_encryption_level_t ngx_quic_level_t; - typedef struct { ngx_quic_secret_t *secret; ngx_uint_t type; @@ -240,17 +264,218 @@ ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) return NGX_OK; } +static ngx_int_t +ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_level_t level, ngx_str_t *payload) +{ + ngx_str_t res; + ngx_quic_header_t pkt; + + static ngx_str_t initial_token = ngx_null_string; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); + + pkt.level = level; + + if (level == ssl_encryption_initial) { + pkt.number = &qc->initial_pn; + pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.secret = &qc->server_in; + pkt.token = &initial_token; + + if (ngx_quic_create_long_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + + } else if (level == ssl_encryption_handshake) { + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->server_hs; + + if (ngx_quic_create_long_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + pkt.number = &qc->appdata_pn; + pkt.secret = &qc->server_ad; + + if (ngx_quic_create_short_packet(c, c->ssl->connection, + &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; + } + } + + ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); + + c->send(c, res.data, res.len); // TODO: err handling + + return NGX_OK; +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) +{ + if (p == NULL) { + return 5; /* minimal ACK */ + } + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->pn); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->pn); + + return 5; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) +{ + u_char *start; + + if (p == NULL) { + if (crypto->len >= 64) { + return crypto->len + 4; + + } else { + return crypto->len + 3; + } // TODO: proper calculation of varint + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, crypto->len); + p = ngx_cpymem(p, crypto->data, crypto->len); + + return p - start; +} + +size_t +ngx_quic_frame_len(ngx_quic_frame_t *frame) +{ + switch (frame->type) { + case NGX_QUIC_FT_ACK: + return ngx_quic_create_ack(NULL, &frame->u.ack); + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(NULL, &frame->u.crypto); + default: + /* BUG: unsupported frame type generated */ + return 0; + } +} + + +/* pack a group of frames [start; end) into memory p and send as single packet */ +ngx_int_t +ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total) +{ + u_char *p; + ngx_str_t out; + ngx_quic_frame_t *f; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending frames %p...%p", start, end); + + p = ngx_pnalloc(c->pool, total); + if (p == NULL) { + return NGX_ERROR; + } + + out.data = p; + + for (f = start; f != end; f = f->next) { + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + p += ngx_quic_create_ack(p, &f->u.ack); + break; + + case NGX_QUIC_FT_CRYPTO: + p += ngx_quic_create_crypto(p, &f->u.crypto); + break; + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } + } + + out.len = p - out.data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "packet ready: %ui bytes at level %d", + out.len, start->level); + + // IOVEC/sendmsg_chain ? + if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} ngx_int_t ngx_quic_output(ngx_connection_t *c) { - /* stub for processing output queue */ + size_t len; + ngx_uint_t lvl; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; - if (c->quic->out.data) { - c->send(c, c->quic->out.data, c->quic->out.len); - c->quic->out.data = NULL; + qc = c->quic; + + if (qc->frames == NULL) { + return NGX_OK; } + lvl = qc->frames->level; + start = qc->frames; + f = start; + + do { + len = 0; + + do { + /* process same-level group of frames */ + + len += ngx_quic_frame_len(f);// TODO: handle overflow, max size + + f = f->next; + } while (f && f->level == lvl); + + + if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { + return NGX_ERROR; + } + + if (f == NULL) { + break; + } + + lvl = f->level; // TODO: must not decrease (ever, also between calls) + start = f; + + } while (1); + + qc->frames = NULL; + return NGX_OK; } @@ -541,25 +766,19 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, static void -ngx_quic_create_ack(u_char **p, uint64_t num) +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - ngx_quic_build_int(p, NGX_QUIC_FT_ACK); - ngx_quic_build_int(p, num); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, num); -} + ngx_quic_frame_t *f; + if (qc->frames == NULL) { + qc->frames = frame; + return; + } -static void -ngx_quic_create_crypto(u_char **p, u_char *data, size_t len) -{ - ngx_quic_build_int(p, NGX_QUIC_FT_CRYPTO); - ngx_quic_build_int(p, 0); - ngx_quic_build_int(p, len); - *p = ngx_cpymem(*p, data, len); -} + for (f = qc->frames; f->next; f = f->next) { /* void */ } + f->next = frame; +} static int @@ -567,79 +786,50 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { u_char *p; - ngx_str_t payload, res; + ngx_quic_frame_t *frame; ngx_connection_t *c; - ngx_quic_header_t pkt; ngx_quic_connection_t *qc; - ngx_str_t initial_token = ngx_null_string; - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; //ngx_ssl_handshake_log(c); // TODO: enable again - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - pkt.level = level; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data"); - payload.data = ngx_alloc(4 + len + 5 /*minimal ACK*/, c->log); - if (payload.data == 0) { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { return 0; } - p = payload.data; - - ngx_quic_create_crypto(&p, (u_char *) data, len); - - if (level == ssl_encryption_initial) { - ngx_quic_create_ack(&p, 0); - - pkt.number = &qc->initial_pn; - pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->server_in; - - pkt.token = &initial_token; - - } else if (level == ssl_encryption_handshake) { - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->server_hs; - - pkt.token = NULL; - } else { - pkt.number = &qc->appdata_pn; - pkt.secret = &qc->server_ad; + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return 0; } - payload.len = p - payload.data; + ngx_memcpy(p, data, len); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data: clear_len:%uz", - payload.len); + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.len = len; + frame->u.crypto.data = p; - if (level == ssl_encryption_application) { + ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); - if (ngx_quic_create_short_packet(c, ssl_conn, &pkt, &payload, &res) - != NGX_OK) - { - return 0; - } + ngx_quic_queue_frame(qc, frame); - } else { - - if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res) - != NGX_OK) - { + if (level == ssl_encryption_initial) { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { return 0; } - } + frame->level = level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.pn = 0; + ngx_sprintf(frame->info, "ACK for PN=0 at initial, added manually from add_handshake_data"); - // TODO: save state of data to send into qc (push into queue) - qc->out = res; - - if (ngx_quic_output(c) != NGX_OK) { - return 0; + ngx_quic_queue_frame(qc, frame); } return 1; @@ -655,6 +845,10 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + if (ngx_quic_output(c) != NGX_OK) { + return 0; + } + return 1; } @@ -1187,32 +1381,19 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) // ACK Client Finished - ngx_str_t payload, res; - ngx_quic_header_t pkt; - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_frame_t *frame; - pkt.level = ssl_encryption_handshake; - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->server_hs; - - payload.data = ngx_alloc(5 /*minimal ACK*/, c->log); - if (payload.data == 0) { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { return 0; } - p = payload.data; - ngx_quic_create_ack(&p, pn); - - payload.len = p - payload.data; - - if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, &payload, &res) - != NGX_OK) - { - return 0; - } + frame->level = ssl_encryption_handshake; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.pn = pn; - qc->out = res; + ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn); + ngx_quic_queue_frame(qc, frame); if (ngx_quic_output(c) != NGX_OK) { return 0; -- cgit v1.2.3 From 9fa29e40431f86eb149c341fa78deaf640c92def Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 4 Mar 2020 16:05:39 +0300 Subject: Adjusted transport parameters stub for active_connection_id_limit. As was objserved with ngtcp2 client, Finished CRYPTO frame within Handshake packet may not be sent for some reason if there's nothing to append on 1-RTT. This results in unnecessary retransmit. To avoid this edge case, a non-zero active_connection_id_limit transport parameter is now used to append datagram with NEW_CONNECTION_ID 1-RTT frames. --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 75947a633..9ba20f3fb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1127,7 +1127,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) return NGX_ERROR; } - static const uint8_t params[12] = "\x00\x0a\x00\x3a\x00\x01\x00\x00\x09\x00\x01\x03"; + /* STUB: initial_max_streams_uni=3, active_connection_id_limit=5 */ + static const uint8_t params[12] = "\x00\x0a\x00\x0e\x00\x01\x05\x00\x09\x00\x01\x03"; if (SSL_set_quic_transport_params(c->ssl->connection, params, sizeof(params)) == 0) -- cgit v1.2.3 From 46bd853801b0e2a47bbc3fad06cfea22b25d7b04 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 12:51:49 +0300 Subject: Using cached ssl_conn in ngx_quic_handshake_input(), NFC. --- src/event/ngx_event_quic.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9ba20f3fb..b4f57df84 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1191,13 +1191,15 @@ static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) { int sslerr; + u_char *p, *b; ssize_t n; ngx_str_t out; + ngx_ssl_conn_t *ssl_conn; const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; - u_char *p, *b; qc = c->quic; + ssl_conn = c->ssl->connection; n = bb->last - bb->pos; p = bb->pos; @@ -1302,7 +1304,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - u_char *name = (u_char *) SSL_get_cipher(c->ssl->connection); + u_char *name = (u_char *) SSL_get_cipher(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ssl cipher: %s", name); @@ -1348,11 +1350,10 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); - if (!SSL_provide_quic_data(c->ssl->connection, - SSL_quic_read_level(c->ssl->connection), + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), crypto, crypto_len)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, @@ -1360,12 +1361,12 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) return NGX_ERROR; } - n = SSL_do_handshake(c->ssl->connection); + n = SSL_do_handshake(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); if (n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); + sslerr = SSL_get_error(ssl_conn, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -1377,8 +1378,8 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); // ACK Client Finished -- cgit v1.2.3 From 0bc9857b47b17c85eacb79f828485fd27e70a9b5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 13:00:59 +0300 Subject: Using SSL cipher suite id to obtain cipher/digest, part 1. While here, log the negotiated cipher just once, - after handshake. --- src/event/ngx_event_quic.c | 65 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b4f57df84..89bcb445a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -10,6 +10,9 @@ #define quic_version 0xff000018 +#define NGX_AES_128_GCM_SHA256 0x1301 +#define NGX_AES_256_GCM_SHA384 0x1302 + #if (NGX_HAVE_NONALIGNED) @@ -485,7 +488,6 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len) { - u_char *name; ngx_uint_t i; const EVP_MD *digest; const EVP_CIPHER *cipher; @@ -494,24 +496,22 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - //ngx_ssl_handshake_log(c); // TODO: enable again - ngx_quic_hexdump(c->log, "level:%d read", read_secret, secret_len, level); ngx_quic_hexdump(c->log, "level:%d read", write_secret, secret_len, level); - name = (u_char *) SSL_get_cipher(ssl_conn); + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { + case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); + break; - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); digest = EVP_sha384(); + break; - } else { + default: ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); return 0; } @@ -574,7 +574,7 @@ static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) { - u_char *p, *pnp, *name, *nonce, *sample, *packet; + u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; @@ -615,17 +615,17 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - name = (u_char *) SSL_get_cipher(ssl_conn); + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { + case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); + break; - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); + break; - } else { + default: return NGX_ERROR; } @@ -673,7 +673,7 @@ static ngx_int_t ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) { - u_char *p, *pnp, *name, *nonce, *sample, *packet; + u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; @@ -703,17 +703,17 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - name = (u_char *) SSL_get_cipher(ssl_conn); + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { + case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); + break; - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); + break; - } else { + default: return NGX_ERROR; } @@ -793,8 +793,6 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; - //ngx_ssl_handshake_log(c); // TODO: enable again - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_add_handshake_data"); @@ -1304,19 +1302,17 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - u_char *name = (u_char *) SSL_get_cipher(ssl_conn); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher: %s", name); + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0 - || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0) - { + case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); + break; - } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) { + case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); + break; - } else { + default: ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); return NGX_ERROR; } @@ -1381,6 +1377,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + // ACK Client Finished ngx_quic_frame_t *frame; -- cgit v1.2.3 From 941d4f1c2ebd6da06fcbda24a71c974166024ef3 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 13:10:01 +0300 Subject: Fixed packet "input" debug log message. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 89bcb445a..ee8a0c716 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1203,7 +1203,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) p = bb->pos; b = bb->start; - ngx_quic_hexdump0(c->log, "input", buf, n); + ngx_quic_hexdump0(c->log, "input", p, n); if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type"); -- cgit v1.2.3 From e3e5e21ed53eac32b84e808cb92c0731176a466a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 4 Mar 2020 23:24:51 +0300 Subject: Macro for calculating size of varint. --- src/event/ngx_event_quic.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ee8a0c716..f3035dd70 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -49,6 +49,8 @@ #define ngx_quic_write_uint32_aligned(p, s) \ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) +#define ngx_quic_varint_len(value) \ + ((value) <= 63 ? 1 : (value) <= 16383 ? 2 : (value) <= 1073741823 ? 4 : 8) #if (NGX_DEBUG) @@ -349,12 +351,7 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) u_char *start; if (p == NULL) { - if (crypto->len >= 64) { - return crypto->len + 4; - - } else { - return crypto->len + 3; - } // TODO: proper calculation of varint + return 3 + ngx_quic_varint_len(crypto->len) + crypto->len; } start = p; -- cgit v1.2.3 From 12fc8a8bac9bc2835e41e14be67ab18d708e6485 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 15:26:15 +0300 Subject: Fixed ngx_quic_varint_len misuse in the previous change. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f3035dd70..5bd423f45 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -351,7 +351,7 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) u_char *start; if (p == NULL) { - return 3 + ngx_quic_varint_len(crypto->len) + crypto->len; + return 2 + ngx_quic_varint_len(crypto->len) + crypto->len; } start = p; -- cgit v1.2.3 From 32b2728ebb15c793f98beca09e21bb81386d5ac5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Mar 2020 17:18:33 +0300 Subject: Added functions to decrypt long packets. --- src/event/ngx_event_quic.c | 443 +++++++++++++++++++++++++++------------------ 1 file changed, 264 insertions(+), 179 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5bd423f45..cc515f054 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -185,9 +185,22 @@ typedef struct { ngx_uint_t type; ngx_uint_t *number; ngx_uint_t flags; - ngx_uint_t version; - ngx_str_t *token; + uint32_t version; + ngx_str_t token; ngx_quic_level_t level; + + /* filled in by parser */ + ngx_str_t buf; /* quic packet from wire */ + u_char *pos; /* current parser position */ + + /* cleartext fields */ + ngx_str_t dcid; + ngx_str_t scid; + + uint64_t pn; + + ngx_str_t payload; /* decrypted payload */ + } ngx_quic_header_t; @@ -210,6 +223,15 @@ static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c); +static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt); + static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); static uint64_t ngx_quic_parse_int(u_char **pos); static void ngx_quic_build_int(u_char **pos, uint64_t value); @@ -287,7 +309,7 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; pkt.secret = &qc->server_in; - pkt.token = &initial_token; + pkt.token = initial_token; if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, payload, &res) @@ -599,8 +621,8 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, *p++ = qc->dcid.len; p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); - if (pkt->token) { // if pkt->flags & initial ? - ngx_quic_build_int(&p, pkt->token->len); + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, pkt->token.len); } ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) @@ -865,90 +887,114 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt) { - int n, sslerr; - ngx_quic_connection_t *qc; + u_char *p; - if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet"); - return NGX_ERROR; - } + p = pkt->buf.data; - if (ngx_buf_size(b) < 1200) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); + ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + + if (!(p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet"); return NGX_ERROR; } - ngx_int_t flags = *b->pos++; - uint32_t version = ngx_quic_parse_uint32(b->pos); - b->pos += sizeof(uint32_t); + pkt->flags = *p++; + + pkt->version = ngx_quic_parse_uint32(p); + p += sizeof(uint32_t); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flags:%xi version:%xD", flags, version); + "quic flags:%xi version:%xD", pkt->flags, pkt->version); - if (version != quic_version) { + if (pkt->version != quic_version) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); return NGX_ERROR; } - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { - return NGX_ERROR; - } + pkt->dcid.len = *p++; + pkt->dcid.data = p; + p += pkt->dcid.len; - c->quic = qc; + pkt->scid.len = *p++; + pkt->scid.data = p; + p += pkt->scid.len; - qc->dcid.len = *b->pos++; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } + pkt->pos = p; - ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); - b->pos += qc->dcid.len; + return NGX_OK; +} - qc->scid.len = *b->pos++; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); - if (qc->scid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); - b->pos += qc->scid.len; +static ngx_int_t +ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; - qc->token.len = ngx_quic_parse_int(&b->pos); - qc->token.data = ngx_pnalloc(c->pool, qc->token.len); - if (qc->token.data == NULL) { + p = pkt->pos; + + pkt->token.len = ngx_quic_parse_int(&p); + pkt->token.data = p; + + p += pkt->token.len; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->buf.data + pkt->buf.len - p) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); return NGX_ERROR; } - ngx_memcpy(qc->token.data, b->pos, qc->token.len); - b->pos += qc->token.len; + pkt->pos = p; + pkt->buf.len = plen; - ngx_int_t plen = ngx_quic_parse_int(&b->pos); + ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len); + ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len); - if (plen > b->last - b->pos) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + + return NGX_OK; +} + +static ngx_int_t +ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; + + p = pkt->pos; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->buf.data + pkt->buf.len - p) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); return NGX_ERROR; } - /* draft-ietf-quic-tls-23#section-5.4.2: - * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.[34]: - * AES-Based and ChaCha20-Based header protections sample 16 bytes - */ - u_char *sample = b->pos + 4; + pkt->pos = p; + pkt->buf.len = plen; - ngx_quic_hexdump0(c->log, "DCID", qc->dcid.data, qc->dcid.len); - ngx_quic_hexdump0(c->log, "SCID", qc->scid.data, qc->scid.len); - ngx_quic_hexdump0(c->log, "token", qc->token.data, qc->token.len); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); - ngx_quic_hexdump0(c->log, "sample", sample, 16); + return NGX_OK; +} -// initial secret + +static ngx_int_t +ngx_quic_initial_secret(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc = c->quic; size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -1047,57 +1093,176 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) } } -// header protection + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char clearflags, *p, *sample; + uint8_t *nonce; + uint64_t pn; + ngx_int_t pnl, rc; + ngx_str_t in, ad; + + const EVP_CIPHER *cipher; + + uint8_t mask[16]; + + p = pkt->pos; + + /* draft-ietf-quic-tls-23#section-5.4.2: + * the Packet Number field is assumed to be 4 bytes long + * draft-ietf-quic-tls-23#section-5.4.[34]: + * AES-Based and ChaCha20-Based header protections sample 16 bytes + */ + + sample = p + 4; + + ngx_quic_hexdump0(c->log, "sample", sample, 16); + + /* header protection */ - uint8_t mask[16]; - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) + if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } - u_char clearflags = flags ^ (mask[0] & 0x0f); - ngx_int_t pnl = (clearflags & 0x03) + 1; - uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]); + clearflags = pkt->flags ^ (mask[0] & 0x0f); + pnl = (clearflags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); + + pkt->pn = pn; - ngx_quic_hexdump0(c->log, "sample", sample, 16); ngx_quic_hexdump0(c->log, "mask", mask, 5); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear flags: %xi", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet number: %uL, len: %xi", pn, pnl); -// packet protection + /* packet protection */ - ngx_str_t in; - in.data = b->pos; - in.len = plen - pnl; + in.data = p; + in.len = pkt->buf.len - pnl; - ngx_str_t ad; - ad.len = b->pos - b->start; + ad.len = p - pkt->buf.data;; ad.data = ngx_pnalloc(c->pool, ad.len); if (ad.data == NULL) { return NGX_ERROR; } - ngx_memcpy(ad.data, b->start, ad.len); + ngx_memcpy(ad.data, pkt->buf.data, ad.len); ad.data[0] = clearflags; - ad.data[ad.len - pnl] = (u_char)pn; + ad.data[ad.len - pnl] = (u_char) pn; - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv); + nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); nonce[11] ^= pn; ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - ngx_str_t out; + if (c->ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) { - if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, - &in, &ad) - != NGX_OK) - { + case NGX_AES_128_GCM_SHA256: + cipher = EVP_aes_128_gcm(); + break; + case NGX_AES_256_GCM_SHA384: + cipher = EVP_aes_256_gcm(); + break; + default: + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + } else { + /* initial packets */ + cipher = EVP_aes_128_gcm(); + } + + rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload, + nonce, &in, &ad); + + ngx_quic_hexdump0(c->log, "packet payload", + pkt->payload.data, pkt->payload.len); + + return rc; +} + + +static ngx_int_t +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +{ + int n, sslerr; + ngx_str_t out; + ngx_quic_connection_t *qc; + + ngx_quic_header_t pkt = { 0 }; + + pkt.buf.data = b->start; + pkt.buf.len = b->last - b->pos; + + if (ngx_buf_size(b) < 1200) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); return NGX_ERROR; } - ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); + if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + + if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid initial packet: 0x%x", pkt.flags); + return NGX_ERROR; + } + + if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; + } + + c->quic = qc; + + qc->dcid.len = pkt.dcid.len; + qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len); + + qc->scid.len = pkt.scid.len; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->scid.data, pkt.scid.data, qc->scid.len); + + qc->token.len = pkt.token.len; + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->token.data, pkt.token.data, qc->token.len); + + + if (ngx_quic_initial_secret(c) != NGX_OK) { + return NGX_ERROR; + } + + pkt.secret = &qc->client_in; + + if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + + out = pkt.payload; if (out.data[0] != 0x06) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1183,144 +1348,64 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) static ngx_int_t -ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) +ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) { int sslerr; - u_char *p, *b; ssize_t n; ngx_str_t out; ngx_ssl_conn_t *ssl_conn; - const EVP_CIPHER *cipher; ngx_quic_connection_t *qc; + ngx_quic_header_t pkt = { 0 }; + qc = c->quic; ssl_conn = c->ssl->connection; - n = bb->last - bb->pos; - p = bb->pos; - b = bb->start; + pkt.buf.data = b->start; + pkt.buf.len = b->last - b->pos; - ngx_quic_hexdump0(c->log, "input", p, n); - - if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type"); + /* extract cleartext data into pkt */ + if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { return NGX_ERROR; } - ngx_int_t flags = *p++; - uint32_t version = ngx_quic_parse_uint32(p); - p += sizeof(uint32_t); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flags:%xi version:%xD", flags, version); - - if (version != quic_version) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); - return NGX_ERROR; - } - - if (*p++ != qc->dcid.len) { + if (pkt.dcid.len != qc->dcid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); return NGX_ERROR; } - if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) { + if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); return NGX_ERROR; } - p += qc->dcid.len; - - if (*p++ != qc->scid.len) { + if (pkt.scid.len != qc->scid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); return NGX_ERROR; } - if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) { + if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); return NGX_ERROR; } - p += qc->scid.len; - - ngx_int_t plen = ngx_quic_parse_int(&p); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); - - if (plen > b + n - p) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); - return NGX_ERROR; - } - - u_char *sample = p + 4; - - ngx_quic_hexdump0(c->log, "sample", sample, 16); - -// header protection - - uint8_t mask[16]; - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample) - != NGX_OK) - { + if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid packet type: 0x%x", pkt.flags); return NGX_ERROR; } - u_char clearflags = flags ^ (mask[0] & 0x0f); - ngx_int_t pnl = (clearflags & 0x03) + 1; - uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); - - ngx_quic_hexdump0(c->log, "mask", mask, 5); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear flags: %xi", clearflags); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - -// packet protection - - ngx_str_t in; - in.data = p; - in.len = plen - pnl; - - ngx_str_t ad; - ad.len = p - b; - ad.data = ngx_pnalloc(c->pool, ad.len); - if (ad.data == NULL) { + if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) { return NGX_ERROR; } - ngx_memcpy(ad.data, b, ad.len); - ad.data[0] = clearflags; - ad.data[ad.len - pnl] = (u_char)pn; - - uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv); - nonce[11] ^= pn; - - ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - break; - - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - break; + pkt.secret = &qc->client_hs; - default: - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return NGX_ERROR; - } - - if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad) - != NGX_OK) - { + if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { return NGX_ERROR; } - ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); + out = pkt.payload; if (out.data[0] != 0x06) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1388,9 +1473,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb) frame->level = ssl_encryption_handshake; frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.pn = pn; + frame->u.ack.pn = pkt.pn; - ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn); + ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pkt.pn); ngx_quic_queue_frame(qc, frame); if (ngx_quic_output(c) != NGX_OK) { -- cgit v1.2.3 From fe30a167d2c6b6356777d7a9eaaf95ca06df138f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Mar 2020 17:24:04 +0300 Subject: Style. --- src/event/ngx_event_quic.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index cc515f054..233a4610b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -994,13 +994,13 @@ ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c) { - ngx_quic_connection_t *qc = c->quic; + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + ngx_quic_connection_t *qc; - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; static const uint8_t salt[20] = "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; @@ -1010,6 +1010,8 @@ ngx_quic_initial_secret(ngx_connection_t *c) cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); + qc = c->quic; + if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt, sizeof(salt)) != NGX_OK) @@ -1264,7 +1266,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) out = pkt.payload; - if (out.data[0] != 0x06) { + if (out.data[0] != NGX_QUIC_FT_CRYPTO) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected frame in initial packet"); return NGX_ERROR; @@ -1407,7 +1409,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) out = pkt.payload; - if (out.data[0] != 0x06) { + if (out.data[0] != NGX_QUIC_FT_CRYPTO) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "non-CRYPTO frame in HS packet, skipping"); return NGX_OK; -- cgit v1.2.3 From 08691ef3d496a716543299ecafa7055540f80caa Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Mar 2020 17:51:22 +0300 Subject: Fixed format specifiers. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 233a4610b..2d36863b4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1217,7 +1217,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%x", pkt.flags); + "invalid initial packet: 0x%xi", pkt.flags); return NGX_ERROR; } @@ -1393,7 +1393,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%x", pkt.flags); + "invalid packet type: 0x%xi", pkt.flags); return NGX_ERROR; } -- cgit v1.2.3 From b0f1302e7d765a646cdd8f11af2ef9b0ab52f772 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 18:01:18 +0300 Subject: Fixed write secret logging in set_encryption_secrets callback. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2d36863b4..a6999f7f4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -516,7 +516,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d read", read_secret, secret_len, level); - ngx_quic_hexdump(c->log, "level:%d read", write_secret, secret_len, level); + ngx_quic_hexdump(c->log, "level:%d write", write_secret, secret_len, level); switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { -- cgit v1.2.3 From ed0533c2c2ce01c059b0faf7eae2b0957deee82d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 19:49:49 +0300 Subject: Initial packets are protected with AEAD_AES_128_GCM. --- src/event/ngx_event_quic.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a6999f7f4..30a130339 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -634,18 +634,23 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { + if (pkt->level != ssl_encryption_initial) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - break; + case NGX_AES_128_GCM_SHA256: + cipher = EVP_aes_128_gcm(); + break; - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - break; + case NGX_AES_256_GCM_SHA384: + cipher = EVP_aes_256_gcm(); + break; - default: - return NGX_ERROR; + default: + return NGX_ERROR; + } + + } else { + cipher = EVP_aes_128_gcm(); } nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); -- cgit v1.2.3 From 547a1a0159082a9a81500b657aa20eedf13d4179 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 5 Mar 2020 20:05:40 +0300 Subject: Fixed header protection with negotiated cipher suite. --- src/event/ngx_event_quic.c | 62 ++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 30a130339..9d8e12d3a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -595,7 +595,7 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, { u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; - const EVP_CIPHER *cipher; + const EVP_CIPHER *cipher, *hp; ngx_quic_connection_t *qc; u_char mask[16]; @@ -639,10 +639,12 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); + hp = EVP_aes_128_ecb(); break; case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); + hp = EVP_aes_256_ecb(); break; default: @@ -651,6 +653,7 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, } else { cipher = EVP_aes_128_gcm(); + hp = EVP_aes_128_ecb(); } nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); @@ -666,7 +669,7 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, } sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -699,7 +702,7 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, { u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; - const EVP_CIPHER *cipher; + const EVP_CIPHER *cipher, *hp; ngx_quic_connection_t *qc; u_char mask[16]; @@ -731,10 +734,12 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, case NGX_AES_128_GCM_SHA256: cipher = EVP_aes_128_gcm(); + hp = EVP_aes_128_ecb(); break; case NGX_AES_256_GCM_SHA384: cipher = EVP_aes_256_gcm(); + hp = EVP_aes_256_ecb(); break; default: @@ -758,9 +763,7 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "out", out.data, out.len); sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) - != NGX_OK) - { + if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -1113,10 +1116,32 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_int_t pnl, rc; ngx_str_t in, ad; - const EVP_CIPHER *cipher; + const EVP_CIPHER *cipher, *hp; uint8_t mask[16]; + if (c->ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) { + + case NGX_AES_128_GCM_SHA256: + cipher = EVP_aes_128_gcm(); + hp = EVP_aes_128_ecb(); + break; + case NGX_AES_256_GCM_SHA384: + cipher = EVP_aes_256_gcm(); + hp = EVP_aes_256_ecb(); + break; + default: + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + } else { + /* initial packets */ + cipher = EVP_aes_128_gcm(); + hp = EVP_aes_128_ecb(); + } + p = pkt->pos; /* draft-ietf-quic-tls-23#section-5.4.2: @@ -1131,9 +1156,7 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) /* header protection */ - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample) - != NGX_OK) - { + if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -1170,25 +1193,6 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - if (c->ssl) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) { - - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - break; - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - break; - default: - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return NGX_ERROR; - } - - } else { - /* initial packets */ - cipher = EVP_aes_128_gcm(); - } - rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload, nonce, &in, &ad); -- cgit v1.2.3 From c2afb5ec8adde78add52cfe53047f02a54915836 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 10 Mar 2020 18:24:39 +0300 Subject: Generic payload handler for quic packets. - added basic parsing of ACK, PING and PADDING frames on input - added preliminary parsing of SHORT headers The ngx_quic_output() is now called after processing of each input packet. Frames are added into output queue according to their level: inital packets go ahead of handshake and application data, so they can be merged properly. The payload handler is called from both new, handshake and applicataion data handlers (latter is a stub). --- src/event/ngx_event_quic.c | 551 ++++++++++++++++++++++++++++++--------------- 1 file changed, 367 insertions(+), 184 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9d8e12d3a..6ea9d23ed 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -134,12 +134,23 @@ typedef enum ssl_encryption_level_t ngx_quic_level_t; typedef struct ngx_quic_frame_s ngx_quic_frame_t; typedef struct { - ngx_uint_t pn; - // ngx_uint_t nranges; - // ... + ngx_uint_t pn; + + // input + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ranges[20]; + /* ecn counts */ } ngx_quic_ack_frame_t; -typedef ngx_str_t ngx_quic_crypto_frame_t; +typedef struct { + size_t offset; + size_t len; + u_char *data; +} ngx_quic_crypto_frame_t; + struct ngx_quic_frame_s { ngx_uint_t type; @@ -207,6 +218,7 @@ typedef struct { static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b); static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, @@ -225,6 +237,8 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_short_header(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c, @@ -276,19 +290,15 @@ ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) { if (c->quic == NULL) { - return ngx_quic_new_connection(c, ssl, b); //TODO: change state by results + return ngx_quic_new_connection(c, ssl, b); } - switch (c->quic->state) { - case NGX_QUIC_ST_INITIAL: - case NGX_QUIC_ST_HANDSHAKE: + if (b->start[0] & NGX_QUIC_PKT_LONG) { + // TODO: check current state return ngx_quic_handshake_input(c, b); - default: - /* application data */ - break; } - return NGX_OK; + return ngx_quic_app_input(c, b); } static ngx_int_t @@ -379,7 +389,7 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) start = p; ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, crypto->offset); ngx_quic_build_int(&p, crypto->len); p = ngx_cpymem(p, crypto->data, crypto->len); @@ -453,6 +463,7 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, return NGX_OK; } + ngx_int_t ngx_quic_output(ngx_connection_t *c) { @@ -802,8 +813,13 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) return; } - for (f = qc->frames; f->next; f = f->next) { /* void */ } + for (f = qc->frames; f->next; f = f->next) { + if (f->next->level > frame->level) { + break; + } + } + frame->next = f->next; f->next = frame; } @@ -844,19 +860,6 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_queue_frame(qc, frame); - if (level == ssl_encryption_initial) { - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return 0; - } - frame->level = level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.pn = 0; - ngx_sprintf(frame->info, "ACK for PN=0 at initial, added manually from add_handshake_data"); - - ngx_quic_queue_frame(qc, frame); - } - return 1; } @@ -870,10 +873,6 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); - if (ngx_quic_output(c) != NGX_OK) { - return 0; - } - return 1; } @@ -894,6 +893,40 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, } +/* TODO: stub for short packet header processing */ +static ngx_int_t +ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *p; + + p = pkt->buf.data; + + ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + + if ((p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a short packet"); + return NGX_ERROR; + } + + pkt->flags = *p++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flags:%xi", pkt->flags); + + if (ngx_memcmp(p, c->quic->dcid.data, c->quic->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } + + pkt->dcid.data = p; + p += c->quic->dcid.len; + + pkt->pos = p; + + return NGX_OK; +} + + static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -1160,7 +1193,13 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - clearflags = pkt->flags ^ (mask[0] & 0x0f); + if (pkt->flags & NGX_QUIC_PKT_LONG) { + clearflags = pkt->flags ^ (mask[0] & 0x0f); + + } else { + clearflags = pkt->flags ^ (mask[0] & 0x1f); + } + pnl = (clearflags & 0x03) + 1; pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); @@ -1175,9 +1214,15 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) /* packet protection */ in.data = p; - in.len = pkt->buf.len - pnl; - ad.len = p - pkt->buf.data;; + if (pkt->flags & NGX_QUIC_PKT_LONG) { + in.len = pkt->buf.len - pnl; + + } else { + in.len = pkt->buf.data + pkt->buf.len - p; + } + + ad.len = p - pkt->buf.data; ad.data = ngx_pnalloc(c->pool, ad.len); if (ad.data == NULL) { return NGX_ERROR; @@ -1199,15 +1244,278 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_hexdump0(c->log, "packet payload", pkt->payload.data, pkt->payload.len); + pkt->pos = pkt->payload.data; + return rc; } +ngx_int_t +ngx_quic_read_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + u_char *p, *end; + + size_t npad; + + p = pkt->pos; + end = pkt->payload.data + pkt->payload.len; + + frame->type = *p++; + + switch (frame->type) { + + case NGX_QUIC_FT_CRYPTO: + frame->u.crypto.offset = *p++; + frame->u.crypto.len = ngx_quic_parse_int(&p); + frame->u.crypto.data = p; + p += frame->u.crypto.len; + + ngx_quic_hexdump0(c->log, "CRYPTO frame", + frame->u.crypto.data, frame->u.crypto.len); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic CRYPTO frame length: %uL off:%uL pp:%p", + frame->u.crypto.len, frame->u.crypto.offset, + frame->u.crypto.data); + break; + + case NGX_QUIC_FT_PADDING: + npad = 0; + while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX + p++; npad++; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "PADDING frame length %uL", npad); + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ACK frame"); + + frame->u.ack.largest = ngx_quic_parse_int(&p); + frame->u.ack.delay = ngx_quic_parse_int(&p); + frame->u.ack.range_count =ngx_quic_parse_int(&p); + frame->u.ack.first_range =ngx_quic_parse_int(&p); + + if (frame->u.ack.range_count) { + frame->u.ack.ranges[0] = ngx_quic_parse_int(&p); + } + + if (frame->type ==NGX_QUIC_FT_ACK_ECN) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame"); + p++; + break; + default: + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "unknown frame type %xi", frame->type); + return NGX_ERROR; + } + + pkt->pos = p; + + return NGX_OK; +} + + static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + int sslerr; + ssize_t n; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = c->ssl->connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + frame->u.crypto.data, frame->u.crypto.len)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr == SSL_ERROR_SSL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + return NGX_OK; +} + + + +static ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) { int n, sslerr; - ngx_str_t out; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + /* STUB: initial_max_streams_uni=3, active_connection_id_limit=5 */ + static const uint8_t params[12] = "\x00\x0a\x00\x0e\x00\x01\x05\x00\x09\x00\x01\x03"; + + qc = c->quic; + + if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + + if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params)) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + return NGX_OK; +} + + +/* process all payload from the current packet and generate ack if required */ +static ngx_int_t +ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end; + ngx_uint_t ack_this; + ngx_quic_frame_t frame, *ack_frame; + ngx_quic_connection_t *qc; + + qc = c->quic; + end = pkt->payload.data + pkt->payload.len; + + ack_this = 0; + + while (pkt->pos < end) { + + if (ngx_quic_read_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + + // TODO: handle ack + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", + frame.u.ack.largest, + frame.u.ack.delay, + frame.u.ack.first_range, + frame.u.ack.range_count); + + break; + + case NGX_QUIC_FT_CRYPTO: + + if (frame.u.crypto.offset != 0x0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "crypto frame with non-zero offset"); + // TODO: support packet spanning with offsets + return NGX_ERROR; + } + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + ack_this = 1; + + continue; + + case NGX_QUIC_FT_PADDING: + continue; + + case NGX_QUIC_FT_PING: + ack_this = 1; + continue; + + default: + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unexpected frame type 0x%xd in packet", frame.type); + return NGX_ERROR; + } + } + + if (ack_this == 0) { + /* do not ack packets with ACKs and PADDING */ + return NGX_OK; + } + + // packet processed, ACK it now if required + // TODO: if (ack_required) ... - currently just ack each packet + + ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (ack_frame == NULL) { + return NGX_ERROR; + } + + ack_frame->level = pkt->level; + ack_frame->type = NGX_QUIC_FT_ACK; + ack_frame->u.ack.pn = pkt->pn; + + ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler", pkt->pn); + ngx_quic_queue_frame(qc, ack_frame); + + return ngx_quic_output(c); +} + + +static ngx_int_t +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +{ ngx_quic_connection_t *qc; ngx_quic_header_t pkt = { 0 }; @@ -1240,6 +1548,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) } c->quic = qc; + qc->ssl = ssl; qc->dcid.len = pkt.dcid.len; qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len); @@ -1268,102 +1577,23 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) } pkt.secret = &qc->client_in; + pkt.level = ssl_encryption_initial; if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { return NGX_ERROR; } - out = pkt.payload; - - if (out.data[0] != NGX_QUIC_FT_CRYPTO) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unexpected frame in initial packet"); - return NGX_ERROR; - } - - if (out.data[1] != 0x00) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unexpected CRYPTO offset in initial packet"); - return NGX_ERROR; - } - - uint8_t *crypto = &out.data[2]; - uint64_t crypto_len = ngx_quic_parse_int(&crypto); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic initial packet CRYPTO length: %uL pp:%p:%p", - crypto_len, out.data, crypto); - - if (ngx_ssl_create_connection(ssl, c, NGX_SSL_BUFFER) != NGX_OK) { - return NGX_ERROR; - } - - /* STUB: initial_max_streams_uni=3, active_connection_id_limit=5 */ - static const uint8_t params[12] = "\x00\x0a\x00\x0e\x00\x01\x05\x00\x09\x00\x01\x03"; - - if (SSL_set_quic_transport_params(c->ssl->connection, params, - sizeof(params)) == 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "SSL_set_quic_transport_params() failed"); + if (ngx_quic_init_connection(c, &pkt) != NGX_OK) { return NGX_ERROR; } - n = SSL_do_handshake(c->ssl->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); - - if (!SSL_provide_quic_data(c->ssl->connection, - SSL_quic_read_level(c->ssl->connection), - crypto, crypto_len)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; - } - - n = SSL_do_handshake(c->ssl->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(c->ssl->connection, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - - if (sslerr == SSL_ERROR_SSL) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - } - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(c->ssl->connection), - (int) SSL_quic_write_level(c->ssl->connection)); - - return NGX_OK; + return ngx_quic_payload_handler(c, &pkt); } static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) { - int sslerr; - ssize_t n; - ngx_str_t out; ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; @@ -1411,89 +1641,42 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) } pkt.secret = &qc->client_hs; + pkt.level = ssl_encryption_handshake; if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { return NGX_ERROR; } - out = pkt.payload; - - if (out.data[0] != NGX_QUIC_FT_CRYPTO) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "non-CRYPTO frame in HS packet, skipping"); - return NGX_OK; - } - - if (out.data[1] != 0x00) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "not yet supported CRYPTO offset in initial packet"); - return NGX_ERROR; - } - - uint8_t *crypto = &out.data[2]; - uint64_t crypto_len = ngx_quic_parse_int(&crypto); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic Handshake packet CRYPTO length: %uL pp:%p:%p", - crypto_len, out.data, crypto); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - crypto, crypto_len)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; - } - - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(ssl_conn, n); + return ngx_quic_payload_handler(c, &pkt); +} - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - if (sslerr == SSL_ERROR_SSL) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - } - } +static ngx_int_t +ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_quic_connection_t *qc; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); + qc = c->quic; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + /* TODO: this is a stub, untested */ - // ACK Client Finished + ngx_quic_header_t pkt = { 0 }; - ngx_quic_frame_t *frame; + pkt.buf.data = b->start; + pkt.buf.len = b->last - b->pos; - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return 0; + if (ngx_quic_process_short_header(c, &pkt) != NGX_OK) { + return NGX_ERROR; } - frame->level = ssl_encryption_handshake; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.pn = pkt.pn; - - ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pkt.pn); - ngx_quic_queue_frame(qc, frame); + pkt.secret = &qc->client_ad; + pkt.level = ssl_encryption_application; - if (ngx_quic_output(c) != NGX_OK) { - return 0; + if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + return NGX_ERROR; } - return NGX_OK; + return ngx_quic_payload_handler(c, &pkt); } -- cgit v1.2.3 From 385408732e4b3934d4bf14b1628f4b21a646875c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Mar 2020 18:40:18 +0300 Subject: Fixed nonce in short packet protection. --- src/event/ngx_event_quic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6ea9d23ed..55eea0137 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -758,7 +758,9 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, } nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); - if (pkt->level == ssl_encryption_handshake) { + if (pkt->level == ssl_encryption_handshake + || pkt->level == ssl_encryption_application) + { nonce[11] ^= (*pkt->number - 1); } -- cgit v1.2.3 From 4b59999afe460316dd196930c0460515ee278acc Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Mar 2020 19:12:22 +0300 Subject: Using SSL cipher suite id to obtain cipher/digest, part 2. Ciphers negotiation handling refactored into ngx_quic_ciphers(). --- src/event/ngx_event_quic.c | 221 +++++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 107 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 55eea0137..f62789da9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -8,10 +8,18 @@ #include -#define quic_version 0xff000018 +#define quic_version 0xff000018 -#define NGX_AES_128_GCM_SHA256 0x1301 -#define NGX_AES_256_GCM_SHA384 0x1302 +#define NGX_AES_128_GCM_SHA256 0x1301 +#define NGX_AES_256_GCM_SHA384 0x1302 + +#define NGX_QUIC_IV_LEN 12 + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#else +#define ngx_quic_cipher_t EVP_CIPHER +#endif #if (NGX_HAVE_NONALIGNED) @@ -128,6 +136,11 @@ typedef struct { ngx_str_t hp; } ngx_quic_secret_t; +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; typedef enum ssl_encryption_level_t ngx_quic_level_t; @@ -261,15 +274,17 @@ static ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); static ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, - const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); static ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, - const EVP_CIPHER *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); +static ngx_int_t ngx_quic_ciphers(ngx_connection_t *c, + ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); static SSL_QUIC_METHOD quic_method = { ngx_quic_set_encryption_secrets, @@ -518,30 +533,20 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len) { - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; - ngx_connection_t *c; - ngx_quic_secret_t *client, *server; + ngx_int_t key_len; + ngx_uint_t i; + ngx_connection_t *c; + ngx_quic_secret_t *client, *server; + ngx_quic_ciphers_t ciphers; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d read", read_secret, secret_len, level); ngx_quic_hexdump(c->log, "level:%d write", write_secret, secret_len, level); - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { + key_len = ngx_quic_ciphers(c, &ciphers, level); - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - digest = EVP_sha256(); - break; - - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - digest = EVP_sha384(); - break; - - default: + if (key_len == NGX_ERROR) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); return 0; } @@ -564,14 +569,14 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return 0; } - client->key.len = EVP_CIPHER_key_length(cipher); - server->key.len = EVP_CIPHER_key_length(cipher); + client->key.len = key_len; + server->key.len = key_len; - client->iv.len = EVP_CIPHER_iv_length(cipher); - server->iv.len = EVP_CIPHER_iv_length(cipher); + client->iv.len = NGX_QUIC_IV_LEN; + server->iv.len = NGX_QUIC_IV_LEN; - client->hp.len = EVP_CIPHER_key_length(cipher); - server->hp.len = EVP_CIPHER_key_length(cipher); + client->hp.len = key_len; + server->hp.len = key_len; struct { ngx_str_t label; @@ -588,7 +593,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, + if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, seq[i].secret, secret_len) != NGX_OK) { @@ -606,9 +611,8 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, { u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; - const EVP_CIPHER *cipher, *hp; + ngx_quic_ciphers_t ciphers; ngx_quic_connection_t *qc; - u_char mask[16]; qc = c->quic; @@ -645,26 +649,8 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - if (pkt->level != ssl_encryption_initial) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - hp = EVP_aes_128_ecb(); - break; - - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - hp = EVP_aes_256_ecb(); - break; - - default: - return NGX_ERROR; - } - - } else { - cipher = EVP_aes_128_gcm(); - hp = EVP_aes_128_ecb(); + if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; } nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); @@ -675,12 +661,14 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - if (ngx_quic_tls_seal(c, cipher, pkt->secret, &out, nonce, payload, &ad) != NGX_OK) { + if (ngx_quic_tls_seal(c, ciphers.c, pkt->secret, &out, nonce, payload, &ad) + != NGX_OK) + { return NGX_ERROR; } sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -713,9 +701,8 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, { u_char *p, *pnp, *nonce, *sample, *packet; ngx_str_t ad, out; - const EVP_CIPHER *cipher, *hp; + ngx_quic_ciphers_t ciphers; ngx_quic_connection_t *qc; - u_char mask[16]; qc = c->quic; @@ -741,19 +728,7 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) { - - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - hp = EVP_aes_128_ecb(); - break; - - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - hp = EVP_aes_256_ecb(); - break; - - default: + if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } @@ -767,7 +742,7 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - if (ngx_quic_tls_seal(c, cipher, pkt->secret, &out, nonce, payload, &ad) + if (ngx_quic_tls_seal(c, ciphers.c, pkt->secret, &out, nonce, payload, &ad) != NGX_OK) { return NGX_ERROR; @@ -776,7 +751,7 @@ ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(c->log, "out", out.data, out.len); sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -1145,36 +1120,16 @@ ngx_quic_initial_secret(ngx_connection_t *c) static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) { - u_char clearflags, *p, *sample; - uint8_t *nonce; - uint64_t pn; - ngx_int_t pnl, rc; - ngx_str_t in, ad; - - const EVP_CIPHER *cipher, *hp; - - uint8_t mask[16]; + u_char clearflags, *p, *sample; + uint8_t *nonce; + uint64_t pn; + ngx_int_t pnl, rc; + ngx_str_t in, ad; + ngx_quic_ciphers_t ciphers; + uint8_t mask[16]; - if (c->ssl) { - switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) { - - case NGX_AES_128_GCM_SHA256: - cipher = EVP_aes_128_gcm(); - hp = EVP_aes_128_ecb(); - break; - case NGX_AES_256_GCM_SHA384: - cipher = EVP_aes_256_gcm(); - hp = EVP_aes_256_ecb(); - break; - default: - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return NGX_ERROR; - } - - } else { - /* initial packets */ - cipher = EVP_aes_128_gcm(); - hp = EVP_aes_128_ecb(); + if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; } p = pkt->pos; @@ -1191,7 +1146,7 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) /* header protection */ - if (ngx_quic_tls_hp(c, hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { return NGX_ERROR; } @@ -1240,7 +1195,7 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_hexdump0(c->log, "nonce", nonce, 12); ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload, + rc = ngx_quic_tls_open(c, ciphers.c, pkt->secret, &pkt->payload, nonce, &in, &ad); ngx_quic_hexdump0(c->log, "packet payload", @@ -1882,7 +1837,7 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, static ngx_int_t -ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, +ngx_quic_tls_open(ngx_connection_t *c, const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) { @@ -1892,7 +1847,7 @@ ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, return NGX_ERROR; } -#ifdef OPENSSL_IS_BORINGSSLL +#ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *ctx; ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, @@ -1987,7 +1942,7 @@ ngx_quic_tls_open(ngx_connection_t *c, const EVP_CIPHER *cipher, static ngx_int_t -ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher, +ngx_quic_tls_seal(ngx_connection_t *c, const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad) { @@ -1997,7 +1952,7 @@ ngx_quic_tls_seal(ngx_connection_t *c, const EVP_CIPHER *cipher, return NGX_ERROR; } -#ifdef OPENSSL_IS_BORINGSSLL +#ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *ctx; ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, @@ -2095,22 +2050,28 @@ ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, { int outlen; EVP_CIPHER_CTX *ctx; + u_char zero[5] = {0}; ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { return NGX_ERROR; } - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, NULL) != 1) { + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); goto failed; } - if (!EVP_EncryptUpdate(ctx, out, &outlen, in, 16)) { + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); goto failed; } + if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_Ex() failed"); + goto failed; + } + EVP_CIPHER_CTX_free(ctx); return NGX_OK; @@ -2121,3 +2082,49 @@ failed: return NGX_ERROR; } + + +static ngx_int_t +ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level) +{ + ngx_int_t id, len; + + if (level == ssl_encryption_initial) { + id = NGX_AES_128_GCM_SHA256; + + } else { + id = SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) + & 0xffff; + } + + switch (id) { + + case NGX_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case NGX_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + default: + return NGX_ERROR; + } + + return len; +} -- cgit v1.2.3 From 7e417544bbffdb05e3ef097fe7b7d49e5d85d1d8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Mar 2020 19:13:09 +0300 Subject: ChaCha20 / Poly1305 initial support. --- src/event/ngx_event_quic.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f62789da9..b7595e8bf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -12,6 +12,7 @@ #define NGX_AES_128_GCM_SHA256 0x1301 #define NGX_AES_256_GCM_SHA384 0x1302 +#define NGX_CHACHA20_POLY1305_SHA256 0x1303 #define NGX_QUIC_IV_LEN 12 @@ -2122,6 +2123,19 @@ ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, len = 32; break; + case NGX_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifndef OPENSSL_IS_BORINGSSL + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + default: return NGX_ERROR; } -- cgit v1.2.3 From df544ee47d3fe3590e5d37ef399332b74166c9b7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Mar 2020 19:15:12 +0300 Subject: Chacha20 header protection support with BoringSSL. BoringSSL lacks EVP for Chacha20. Here we use CRYPTO_chacha_20() instead. --- src/event/ngx_event_openssl.h | 1 + src/event/ngx_event_quic.c | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index b562f0f17..620a216ef 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -25,6 +25,7 @@ #include #ifdef OPENSSL_IS_BORINGSSL #include +#include #else #include #endif diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b7595e8bf..c4012687e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2053,6 +2053,17 @@ ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, EVP_CIPHER_CTX *ctx; u_char zero[5] = {0}; +#ifdef OPENSSL_IS_BORINGSSL + uint32_t counter; + + ngx_memcpy(&counter, in, sizeof(uint32_t)); + + if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); + return NGX_OK; + } +#endif + ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { return NGX_ERROR; @@ -2129,7 +2140,9 @@ ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, #else ciphers->c = EVP_chacha20_poly1305(); #endif -#ifndef OPENSSL_IS_BORINGSSL +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else ciphers->hp = EVP_chacha20(); #endif ciphers->d = EVP_sha256(); -- cgit v1.2.3 From 9311e594438f0a4e69e6a0222186603e1168f353 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 11 Mar 2020 21:53:02 +0300 Subject: Compatibility with BoringSSL revised QUIC encryption secret APIs. See for details: https://boringssl.googlesource.com/boringssl/+/1e85905%5E!/ --- src/event/ngx_event_quic.c | 147 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c4012687e..d631a1536 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -234,9 +234,18 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b); +#if BORINGSSL_API_VERSION >= 10 +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len); +#endif static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, @@ -288,7 +297,12 @@ static ngx_int_t ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); static SSL_QUIC_METHOD quic_method = { +#if BORINGSSL_API_VERSION >= 10 + ngx_quic_set_read_secret, + ngx_quic_set_write_secret, +#else ngx_quic_set_encryption_secrets, +#endif ngx_quic_add_handshake_data, ngx_quic_flush_flight, ngx_quic_send_alert, @@ -529,6 +543,137 @@ ngx_quic_output(ngx_connection_t *c) } +#if BORINGSSL_API_VERSION >= 10 + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_connection_t *c; + ngx_quic_secret_t *client; + ngx_quic_ciphers_t ciphers; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_quic_hexdump(c->log, "level:%d read", secret, secret_len, level); + + key_len = ngx_quic_ciphers(c, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return 0; + } + + switch (level) { + + case ssl_encryption_handshake: + client = &c->quic->client_hs; + break; + + case ssl_encryption_application: + client = &c->quic->client_ad; + break; + + default: + return 0; + } + + client->key.len = key_len; + client->iv.len = NGX_QUIC_IV_LEN; + client->hp.len = key_len; + + struct { + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; + } seq[] = { + { ngx_string("tls13 quic key"), &client->key, secret }, + { ngx_string("tls13 quic iv"), &client->iv, secret }, + { ngx_string("tls13 quic hp"), &client->hp, secret }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_connection_t *c; + ngx_quic_secret_t *server; + ngx_quic_ciphers_t ciphers; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_quic_hexdump(c->log, "level:%d write", secret, secret_len, level); + + key_len = ngx_quic_ciphers(c, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return 0; + } + + switch (level) { + + case ssl_encryption_handshake: + server = &c->quic->server_hs; + break; + + case ssl_encryption_application: + server = &c->quic->server_ad; + break; + + default: + return 0; + } + + server->key.len = key_len; + server->iv.len = NGX_QUIC_IV_LEN; + server->hp.len = key_len; + + struct { + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; + } seq[] = { + { ngx_string("tls13 quic key"), &server->key, secret }, + { ngx_string("tls13 quic iv"), &server->iv, secret }, + { ngx_string("tls13 quic hp"), &server->hp, secret }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + +#else + static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, @@ -605,6 +750,8 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return 1; } +#endif + static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, -- cgit v1.2.3 From 0d1c27b5801ac22cf607b06bd7d76f82a4a3a1c5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 11 Mar 2020 15:43:23 +0300 Subject: Added more transport parameters. Needed for client to start sending streams. --- src/event/ngx_event_quic.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d631a1536..dcbbc3234 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1491,8 +1491,15 @@ ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; - /* STUB: initial_max_streams_uni=3, active_connection_id_limit=5 */ - static const uint8_t params[12] = "\x00\x0a\x00\x0e\x00\x01\x05\x00\x09\x00\x01\x03"; + static const uint8_t params[] = + "\x00\x29" /* parameters length: 41 bytes */ + "\x00\x0e\x00\x01\x05" /* active connection id limit: 5 */ + "\x00\x04\x00\x04\x80\x98\x96\x80" /* initial max data = 10000000 */ + "\x00\x09\x00\x01\x03" /* initial max streams uni: 3 */ + "\x00\x08\x00\x01\x10" /* initial max streams bidi: 16 */ + "\x00\x05\x00\x02\x40\xff" /* initial max stream bidi local: 255 */ + "\x00\x06\x00\x02\x40\xff" /* initial max stream bidi remote: 255 */ + "\x00\x07\x00\x02\x40\xff"; /* initial max stream data uni: 255 */ qc = c->quic; @@ -1502,7 +1509,7 @@ ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) ssl_conn = c->ssl->connection; - if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params)) == 0) { + if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "SSL_set_quic_transport_params() failed"); return NGX_ERROR; -- cgit v1.2.3 From 0d10672137264e5ffd2e0445da867c3ff8c874fe Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 12 Mar 2020 14:43:24 +0300 Subject: Added support of multiple QUIC packets in single datagram. - now NEW_CONNECTION_ID frames can be received and parsed The packet structure is created in ngx_quic_input() and passed to all handlers (initial, handshake and application data). The UDP datagram buffer is saved as pkt->raw; The QUIC packet is stored as pkt->data and pkt->len (instead of pkt->buf) (pkt->len is adjusted after parsing headers to actual length) The pkt->pos is removed, pkt->raw->pos is used instead. --- src/event/ngx_event_quic.c | 269 ++++++++++++++++++++++++++++----------------- 1 file changed, 170 insertions(+), 99 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index dcbbc3234..9e06b3b13 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -166,6 +166,15 @@ typedef struct { } ngx_quic_crypto_frame_t; +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint64_t len; + u_char cid[20]; + u_char srt[16]; +} ngx_quic_ncid_t; + + struct ngx_quic_frame_s { ngx_uint_t type; ngx_quic_level_t level; @@ -173,6 +182,7 @@ struct ngx_quic_frame_s { union { ngx_quic_crypto_frame_t crypto; ngx_quic_ack_frame_t ack; + ngx_quic_ncid_t ncid; // more frames } u; @@ -215,8 +225,10 @@ typedef struct { ngx_quic_level_t level; /* filled in by parser */ - ngx_str_t buf; /* quic packet from wire */ - u_char *pos; /* current parser position */ + ngx_buf_t *raw; /* udp datagram from wire */ + + u_char *data; /* quic packet */ + size_t len; /* cleartext fields */ ngx_str_t dcid; @@ -230,9 +242,13 @@ typedef struct { static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_buf_t *b); -static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b); -static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b); + ngx_quic_header_t *pkt); + +static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); + #if BORINGSSL_API_VERSION >= 10 static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, @@ -319,16 +335,46 @@ ngx_quic_init_ssl_methods(SSL_CTX* ctx) ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) { + u_char *p; + ngx_quic_header_t pkt; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + pkt.raw = b; + pkt.data = b->start; + pkt.len = b->last - b->start; + if (c->quic == NULL) { - return ngx_quic_new_connection(c, ssl, b); + return ngx_quic_new_connection(c, ssl, &pkt); } - if (b->start[0] & NGX_QUIC_PKT_LONG) { - // TODO: check current state - return ngx_quic_handshake_input(c, b); - } + p = b->start; - return ngx_quic_app_input(c, b); + do { + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + + if (p[0] & NGX_QUIC_PKT_LONG) { + // TODO: check current state + if (ngx_quic_handshake_input(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + } else { + + if (ngx_quic_app_input(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + } + + /* b->pos is at header end, adjust by actual packet length */ + p = b->pos + pkt.len; + b->pos = p; /* reset b->pos to the next packet start */ + + } while (p < b->last); + + return NGX_OK; } static ngx_int_t @@ -1018,15 +1064,14 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, } -/* TODO: stub for short packet header processing */ static ngx_int_t ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *p; - p = pkt->buf.data; + p = pkt->data; - ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + ngx_quic_hexdump0(c->log, "short input", pkt->data, pkt->len); if ((p[0] & NGX_QUIC_PKT_LONG)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a short packet"); @@ -1043,10 +1088,11 @@ ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + pkt->dcid.len = c->quic->dcid.len; pkt->dcid.data = p; - p += c->quic->dcid.len; + p += pkt->dcid.len; - pkt->pos = p; + pkt->raw->pos = p; return NGX_OK; } @@ -1057,9 +1103,9 @@ ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *p; - p = pkt->buf.data; + p = pkt->data; - ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len); + ngx_quic_hexdump0(c->log, "long input", pkt->data, pkt->len); if (!(p[0] & NGX_QUIC_PKT_LONG)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet"); @@ -1087,7 +1133,7 @@ ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->scid.data = p; p += pkt->scid.len; - pkt->pos = p; + pkt->raw->pos = p; return NGX_OK; } @@ -1099,7 +1145,7 @@ ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt) u_char *p; ngx_int_t plen; - p = pkt->pos; + p = pkt->raw->pos; pkt->token.len = ngx_quic_parse_int(&p); pkt->token.data = p; @@ -1111,13 +1157,13 @@ ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); - if (plen > pkt->buf.data + pkt->buf.len - p) { + if (plen > pkt->data + pkt->len - p) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); return NGX_ERROR; } - pkt->pos = p; - pkt->buf.len = plen; + pkt->raw->pos = p; + pkt->len = plen; ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len); ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len); @@ -1135,20 +1181,20 @@ ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt) u_char *p; ngx_int_t plen; - p = pkt->pos; + p = pkt->raw->pos; plen = ngx_quic_parse_int(&p); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); - if (plen > pkt->buf.data + pkt->buf.len - p) { + if (plen > pkt->data + pkt->len - p) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); return NGX_ERROR; } - pkt->pos = p; - pkt->buf.len = plen; + pkt->raw->pos = p; + pkt->len = plen; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet length: %d", plen); @@ -1280,7 +1326,7 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - p = pkt->pos; + p = pkt->raw->pos; /* draft-ietf-quic-tls-23#section-5.4.2: * the Packet Number field is assumed to be 4 bytes long @@ -1321,19 +1367,19 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) in.data = p; if (pkt->flags & NGX_QUIC_PKT_LONG) { - in.len = pkt->buf.len - pnl; + in.len = pkt->len - pnl; } else { - in.len = pkt->buf.data + pkt->buf.len - p; + in.len = pkt->data + pkt->len - p; } - ad.len = p - pkt->buf.data; + ad.len = p - pkt->data; ad.data = ngx_pnalloc(c->pool, ad.len); if (ad.data == NULL) { return NGX_ERROR; } - ngx_memcpy(ad.data, pkt->buf.data, ad.len); + ngx_memcpy(ad.data, pkt->data, ad.len); ad.data[0] = clearflags; ad.data[ad.len - pnl] = (u_char) pn; @@ -1349,24 +1395,21 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_hexdump0(c->log, "packet payload", pkt->payload.data, pkt->payload.len); - pkt->pos = pkt->payload.data; - return rc; } -ngx_int_t -ngx_quic_read_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, +ssize_t +ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, ngx_quic_frame_t *frame) { - u_char *p, *end; + u_char *p; size_t npad; - p = pkt->pos; - end = pkt->payload.data + pkt->payload.len; + p = start; - frame->type = *p++; + frame->type = *p++; // TODO: check overflow (p < end) switch (frame->type) { @@ -1420,15 +1463,35 @@ ngx_quic_read_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame"); p++; break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "NCID frame"); + + frame->u.ncid.seqnum = ngx_quic_parse_int(&p); + frame->u.ncid.retire = ngx_quic_parse_int(&p); + frame->u.ncid.len = *p++; + ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len); + p += frame->u.ncid.len; + + ngx_memcpy(frame->u.ncid.srt, p, 16); + p += 16; + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame => NGX_ERROR"); + + // TODO: parse connection close here + return NGX_ERROR; + break; + default: ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "unknown frame type %xi", frame->type); return NGX_ERROR; } - pkt->pos = p; - - return NGX_OK; + return p - start; } @@ -1539,22 +1602,28 @@ ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { - u_char *end; + u_char *end, *p; + ssize_t len; ngx_uint_t ack_this; ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; qc = c->quic; - end = pkt->payload.data + pkt->payload.len; + + p = pkt->payload.data; + end = p + pkt->payload.len; ack_this = 0; - while (pkt->pos < end) { + while (p < end) { - if (ngx_quic_read_frame(c, pkt, &frame) != NGX_OK) { + len = ngx_quic_read_frame(c, p, end, &frame); + if (len < 0) { return NGX_ERROR; } + p += len; + switch (frame.type) { case NGX_QUIC_FT_ACK: @@ -1594,6 +1663,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; continue; + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ack_this = 1; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "NCID: { seq=%ui retire=%ui len=%ui}", + frame.u.ncid.seqnum, + frame.u.ncid.retire, + frame.u.ncid.len); + continue; + default: ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected frame type 0x%xd in packet", frame.type); @@ -1601,6 +1679,13 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } } + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "trailing garbage in payload: %ui bytes", end - p); + return NGX_ERROR; + } + + if (ack_this == 0) { /* do not ack packets with ACKs and PADDING */ return NGX_OK; @@ -1626,31 +1711,27 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; - ngx_quic_header_t pkt = { 0 }; - - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - - if (ngx_buf_size(b) < 1200) { + if (ngx_buf_size(pkt->raw) < 1200) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); return NGX_ERROR; } - if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%xi", pkt.flags); + "invalid initial packet: 0x%xi", pkt->flags); return NGX_ERROR; } - if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) { return NGX_ERROR; } @@ -1662,109 +1743,104 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) c->quic = qc; qc->ssl = ssl; - qc->dcid.len = pkt.dcid.len; - qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len); + qc->dcid.len = pkt->dcid.len; + qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); if (qc->dcid.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len); + ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); - qc->scid.len = pkt.scid.len; + qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->scid.data, pkt.scid.data, qc->scid.len); + ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - qc->token.len = pkt.token.len; + qc->token.len = pkt->token.len; qc->token.data = ngx_pnalloc(c->pool, qc->token.len); if (qc->token.data == NULL) { return NGX_ERROR; } - ngx_memcpy(qc->token.data, pkt.token.data, qc->token.len); + ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); if (ngx_quic_initial_secret(c) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_in; - pkt.level = ssl_encryption_initial; + pkt->secret = &qc->client_in; + pkt->level = ssl_encryption_initial; - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - if (ngx_quic_init_connection(c, &pkt) != NGX_OK) { + if (ngx_quic_init_connection(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); } static ngx_int_t -ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b) +ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; - ngx_quic_header_t pkt = { 0 }; - qc = c->quic; ssl_conn = c->ssl->connection; - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - /* extract cleartext data into pkt */ - if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - if (pkt.dcid.len != qc->dcid.len) { + if (pkt->dcid.len != qc->dcid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); return NGX_ERROR; } - if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); return NGX_ERROR; } - if (pkt.scid.len != qc->scid.len) { + if (pkt->scid.len != qc->scid.len) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); return NGX_ERROR; } - if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) { + if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); return NGX_ERROR; } - if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%xi", pkt.flags); + "invalid packet type: 0x%xi", pkt->flags); return NGX_ERROR; } - if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_handshake_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_hs; - pkt.level = ssl_encryption_handshake; + pkt->secret = &qc->client_hs; + pkt->level = ssl_encryption_handshake; - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); } static ngx_int_t -ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b) +ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; @@ -1772,23 +1848,18 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_buf_t *b) /* TODO: this is a stub, untested */ - ngx_quic_header_t pkt = { 0 }; - - pkt.buf.data = b->start; - pkt.buf.len = b->last - b->pos; - - if (ngx_quic_process_short_header(c, &pkt) != NGX_OK) { + if (ngx_quic_process_short_header(c, pkt) != NGX_OK) { return NGX_ERROR; } - pkt.secret = &qc->client_ad; - pkt.level = ssl_encryption_application; + pkt->secret = &qc->client_ad; + pkt->level = ssl_encryption_application; - if (ngx_quic_decrypt(c, &pkt) != NGX_OK) { + if (ngx_quic_decrypt(c, pkt) != NGX_OK) { return NGX_ERROR; } - return ngx_quic_payload_handler(c, &pkt); + return ngx_quic_payload_handler(c, pkt); } -- cgit v1.2.3 From 6bf6635d86c5e868699b45fc0f68d4bcf6a9b770 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 11 Mar 2020 15:41:35 +0300 Subject: Initial parsing of STREAM frames. --- src/event/ngx_event_quic.c | 69 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9e06b3b13..5aab6f07d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -104,7 +104,14 @@ do { \ #define NGX_QUIC_FT_STOP_SENDING 0x05 #define NGX_QUIC_FT_CRYPTO 0x06 #define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM 0x08 // - 0x0f +#define NGX_QUIC_FT_STREAM 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F #define NGX_QUIC_FT_MAX_DATA 0x10 #define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 #define NGX_QUIC_FT_MAX_STREAMS 0x12 @@ -175,6 +182,14 @@ typedef struct { } ngx_quic_ncid_t; +typedef struct { + uint64_t stream_id; + uint64_t offset; + uint64_t length; + u_char *data; +} ngx_quic_stream_frame_t; + + struct ngx_quic_frame_s { ngx_uint_t type; ngx_quic_level_t level; @@ -183,6 +198,7 @@ struct ngx_quic_frame_s { ngx_quic_crypto_frame_t crypto; ngx_quic_ack_frame_t ack; ngx_quic_ncid_t ncid; + ngx_quic_stream_frame_t stream; // more frames } u; @@ -1485,6 +1501,37 @@ ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, return NGX_ERROR; break; + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAM frame, type: 0x%xi", frame->type); + + frame->u.stream.stream_id = ngx_quic_parse_int(&p); + if (frame->type & 0x04) { + frame->u.stream.offset = ngx_quic_parse_int(&p); + } else { + frame->u.stream.offset = 0; + } + + if (frame->type & 0x02) { + frame->u.stream.length = ngx_quic_parse_int(&p); + } else { + frame->u.stream.length = end - p; /* up to packet end */ + } + + frame->u.stream.data = p; + + p += frame->u.stream.length; + + break; + default: ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "unknown frame type %xi", frame->type); @@ -1672,6 +1719,26 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.ncid.len); continue; + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAM frame 0x%xi id 0x%xi off 0x%xi len 0x%xi", + frame.type, + frame.u.stream.stream_id, + frame.u.stream.offset, + frame.u.stream.length); + + ngx_quic_hexdump0(c->log, "STREAM.data", + frame.u.stream.data, frame.u.stream.length); + break; + default: ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected frame type 0x%xd in packet", frame.type); -- cgit v1.2.3 From 4f4f56f013eb0dbe5eb66bb2f22584aec26b13e6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 12 Mar 2020 16:54:43 +0300 Subject: HTTP/QUIC interface reworked. - events handling moved into src/event/ngx_event_quic.c - http invokes once ngx_quic_run() and passes stream callback (diff to original http_request.c is now minimal) - streams are stored in rbtree using ID as a key - when a new stream is registered, appropriate callback is called - ngx_quic_stream_t type represents STREAM and stored in c->qs --- src/core/ngx_connection.h | 1 + src/core/ngx_core.h | 1 + src/event/ngx_event_quic.c | 273 ++++++++++++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic.h | 9 ++ src/http/ngx_http_request.c | 105 +++-------------- 5 files changed, 293 insertions(+), 96 deletions(-) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 0d7e2166b..b3a36cf05 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -151,6 +151,7 @@ struct ngx_connection_s { #if (NGX_SSL || NGX_COMPAT) ngx_quic_connection_t *quic; + ngx_quic_stream_t *qs; ngx_ssl_connection_t *ssl; #endif diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 549fae084..4594b54fd 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -28,6 +28,7 @@ typedef struct ngx_thread_task_s ngx_thread_task_t; typedef struct ngx_ssl_s ngx_ssl_t; typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; typedef struct ngx_quic_connection_s ngx_quic_connection_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; diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5aab6f07d..2b6d4423c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -6,6 +6,7 @@ #include #include +#include #define quic_version 0xff000018 @@ -228,9 +229,23 @@ struct ngx_quic_connection_s { ngx_quic_secret_t server_in; ngx_quic_secret_t server_hs; ngx_quic_secret_t server_ad; + + /* streams */ + ngx_rbtree_t stree; + ngx_rbtree_node_t stree_sentinel; + ngx_msec_t stream_timeout; + ngx_connection_handler_pt stream_handler; }; +typedef struct { + ngx_rbtree_node_t node; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_stream_t s; +} ngx_quic_stream_node_t; + + typedef struct { ngx_quic_secret_t *secret; ngx_uint_t type; @@ -259,7 +274,14 @@ typedef struct { static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_header_t *pkt); +static void ngx_quic_close_connection(ngx_connection_t *c); +static ngx_quic_stream_node_t *ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, + ngx_uint_t key); +static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); + +static void ngx_quic_handshake_handler(ngx_event_t *rev); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, @@ -348,20 +370,134 @@ ngx_quic_init_ssl_methods(SSL_CTX* ctx) } -ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +void +ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, + ngx_connection_handler_pt handler) { - u_char *p; + ngx_buf_t *b; ngx_quic_header_t pkt; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); + + c->log->action = "QUIC handshaking"; + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + b = c->buffer; + pkt.raw = b; pkt.data = b->start; pkt.len = b->last - b->start; + if (ngx_quic_new_connection(c, ssl, &pkt) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } + + // we don't need stream handler for initial packet processing + c->quic->stream_handler = handler; + c->quic->stream_timeout = timeout; + + ngx_add_timer(c->read, timeout); + + c->read->handler = ngx_quic_handshake_handler; + + return; +} + + +static void +ngx_quic_handshake_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_connection_t *c; + u_char buf[512]; + ngx_buf_t b; + + b.start = buf; + b.end = buf + 512; + b.pos = b.last = b.start; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_quic_close_connection(c); + return; + } + + if (c->close) { + ngx_quic_close_connection(c); + return; + } + + n = c->recv(c, b.start, b.end - b.start); + + if (n == NGX_AGAIN) { + return; + } + + if (n == NGX_ERROR) { + c->read->eof = 1; + ngx_quic_close_connection(c); + return; + } + + b.last += n; + + if (ngx_quic_input(c, NULL, &b) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } +} + + +static void +ngx_quic_close_connection(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + /* XXX wait for all streams to close */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: %d", c->fd); + + (void) ngx_ssl_shutdown(c); + +#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); +} + + +ngx_connection_t * +ngx_quic_create_uni_stream(ngx_connection_t *c) +{ + /* XXX */ + return NULL; +} + + +ngx_int_t +ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +{ + u_char *p; + ngx_quic_header_t pkt; + if (c->quic == NULL) { - return ngx_quic_new_connection(c, ssl, &pkt); + // XXX: possible? + ngx_log_error(NGX_LOG_INFO, c->log, 0, "BUG: no QUIC in connection"); + return NGX_ERROR; } p = b->start; @@ -1649,11 +1785,13 @@ ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { - u_char *end, *p; - ssize_t len; - ngx_uint_t ack_this; - ngx_quic_frame_t frame, *ack_frame; - ngx_quic_connection_t *qc; + u_char *end, *p; + ssize_t len; + ngx_buf_t *b; + ngx_uint_t ack_this; + ngx_quic_frame_t frame, *ack_frame; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; qc = c->quic; @@ -1735,6 +1873,55 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.stream.offset, frame.u.stream.length); + + sn = ngx_quic_stream_lookup(&qc->stree, frame.u.stream.stream_id); + if (sn == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); + + sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); + if (sn == NULL) { + return NGX_ERROR; + } + + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination + if (sn->c == NULL) { + return NGX_ERROR; + } + + sn->node.key = frame.u.stream.stream_id; + sn->b = ngx_create_temp_buf(c->pool, 16 * 1024); // XXX enough for everyone + if (sn->b == NULL) { + return NGX_ERROR; + } + b = sn->b; + + ngx_memcpy(b->start, frame.u.stream.data, frame.u.stream.length); + b->last = b->start + frame.u.stream.length; + + ngx_rbtree_insert(&qc->stree, &sn->node); + + sn->s.id = frame.u.stream.stream_id; + sn->s.parent = c; + sn->c->qs = &sn->s; + + qc->stream_handler(sn->c); + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); + b = sn->b; + + if ((size_t) (b->end - b->pos) < frame.u.stream.length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "no space in stream buffer"); + return NGX_ERROR; + } + + ngx_memcpy(b->pos, frame.u.stream.data, frame.u.stream.length); + b->pos += frame.u.stream.length; + + // TODO: ngx_post_event(&c->read, &ngx_posted_events) ??? + } + ngx_quic_hexdump0(c->log, "STREAM.data", frame.u.stream.data, frame.u.stream.length); break; @@ -1777,6 +1964,71 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_node_t *qn, *qnt; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + qn = (ngx_quic_stream_node_t *) &node->color; + qnt = (ngx_quic_stream_node_t *) &temp->color; + + if (qn->c < qnt->c) { + p = &temp->left; + } else { + p = &temp->right; + } + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +static ngx_quic_stream_node_t * +ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, ngx_uint_t key) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + + if (key == node->key) { + return (ngx_quic_stream_node_t *) node; + } + + node = (key < node->key) ? node->left : node->right; + } + + return NULL; +} + + static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_header_t *pkt) @@ -1807,6 +2059,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } + ngx_rbtree_init(&qc->stree, &qc->stree_sentinel, + ngx_quic_rbtree_insert_stream); + c->quic = qc; qc->ssl = ssl; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index edc9d8078..f3ff3da77 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -10,11 +10,20 @@ #include +struct ngx_quic_stream_s { + uint64_t id; + ngx_uint_t unidirectional:1; + ngx_connection_t *parent; +}; + /* TODO: get rid somehow of ssl argument? */ ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b); ngx_int_t ngx_quic_output(ngx_connection_t *c); void ngx_quic_init_ssl_methods(SSL_CTX* ctx); +void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, + ngx_connection_handler_pt handler); +ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 6d89fef24..7a2c78046 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -62,11 +62,9 @@ static u_char *ngx_http_log_error_handler(ngx_http_request_t *r, #if (NGX_HTTP_SSL) static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); - -static void ngx_http_quic_handshake(ngx_event_t *rev); -static void ngx_http_quic_handshake_handler(ngx_event_t *rev); #endif +static void ngx_http_quic_stream_handler(ngx_connection_t *c); static char *ngx_http_client_errors[] = { @@ -333,9 +331,15 @@ ngx_http_init_connection(ngx_connection_t *c) #if (NGX_HTTP_SSL) if (hc->addr_conf->http3) { + ngx_http_ssl_srv_conf_t *sscf; + hc->quic = 1; - c->log->action = "QUIC handshaking"; - rev->handler = ngx_http_quic_handshake; + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); + + ngx_quic_run(c, &sscf->ssl, c->listening->post_accept_timeout, + ngx_http_quic_stream_handler); + return; } #endif @@ -386,6 +390,15 @@ ngx_http_init_connection(ngx_connection_t *c) } +static void +ngx_http_quic_stream_handler(ngx_connection_t *c) +{ + ngx_quic_stream_t *qs = c->qs; + + printf("quic stream: 0x%lx\n", qs->id); +} + + static void ngx_http_wait_request_handler(ngx_event_t *rev) { @@ -401,10 +414,6 @@ ngx_http_wait_request_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http wait request handler"); - if (c->shared) { - goto request; - } - if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_http_close_connection(c); @@ -505,8 +514,6 @@ ngx_http_wait_request_handler(ngx_event_t *rev) } } -request: - c->log->action = "reading client request line"; ngx_reusable_connection(c, 0); @@ -658,82 +665,6 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_SSL) -static void -ngx_http_quic_handshake(ngx_event_t *rev) -{ - ngx_connection_t *c; - ngx_http_connection_t *hc; - ngx_http_ssl_srv_conf_t *sscf; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake"); - - c = rev->data; - hc = c->data; - - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - - if (ngx_quic_input(c, &sscf->ssl, c->buffer) != NGX_OK) { - ngx_http_close_connection(c); - return; - } - - if (!rev->timer_set) { - ngx_add_timer(rev, c->listening->post_accept_timeout); - } - - rev->handler = ngx_http_quic_handshake_handler; - return; -} - - -static void -ngx_http_quic_handshake_handler(ngx_event_t *rev) -{ - ssize_t n; - ngx_connection_t *c; - u_char buf[512]; - ngx_buf_t b; - - b.start = buf; - b.end = buf + 512; - b.pos = b.last = b.start; - - c = rev->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake handler"); - - if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); - ngx_http_close_connection(c); - return; - } - - if (c->close) { - ngx_http_close_connection(c); - return; - } - - n = c->recv(c, b.start, b.end - b.start); - - if (n == NGX_AGAIN) { - return; - } - - if (n == NGX_ERROR) { - c->read->eof = 1; - ngx_http_close_connection(c); - return; - } - - b.last += n; - - if (ngx_quic_input(c, NULL, &b) != NGX_OK) { - ngx_http_close_connection(c); - return; - } -} - - static void ngx_http_ssl_handshake(ngx_event_t *rev) { -- cgit v1.2.3 From 21eaac9a3e771af3b230198b44c199f1345eb1cc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 12 Mar 2020 14:23:27 +0300 Subject: Removed hardcoded CRYPTO and ACK frame sizes. --- src/event/ngx_event_quic.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2b6d4423c..9849d07d2 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -591,8 +591,18 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { + size_t len; + + /* minimal ACK packet */ + if (p == NULL) { - return 5; /* minimal ACK */ + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->pn); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->pn); + + return len; } ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); @@ -608,10 +618,16 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) { + size_t len; u_char *start; if (p == NULL) { - return 2 + ngx_quic_varint_len(crypto->len) + crypto->len; + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->len); + len += crypto->len; + + return len; } start = p; -- cgit v1.2.3 From 5bc8cd4044fe49d672607c365929459969a338fc Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 12 Mar 2020 18:08:26 +0300 Subject: Fix build. --- src/http/ngx_http_request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7a2c78046..f463e674e 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -395,7 +395,8 @@ ngx_http_quic_stream_handler(ngx_connection_t *c) { ngx_quic_stream_t *qs = c->qs; - printf("quic stream: 0x%lx\n", qs->id); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic stream: 0x%uXL", qs->id); } -- cgit v1.2.3 From 05d1464c68a269e2f6ed08b5559edb90dfd3ab1f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 13 Mar 2020 14:39:23 +0300 Subject: Stream "connection" read/write methods. --- src/event/ngx_event_quic.c | 234 ++++++++++++++++++++++++++++++++++++++++++-- src/http/ngx_http_request.c | 31 ++++++ 2 files changed, 256 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9849d07d2..ee77d9153 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -60,7 +60,7 @@ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) #define ngx_quic_varint_len(value) \ - ((value) <= 63 ? 1 : (value) <= 16383 ? 2 : (value) <= 1073741823 ? 4 : 8) + ((value) <= 63 ? 1 : ((uint32_t)value) <= 16383 ? 2 : ((uint64_t)value) <= 1073741823 ? 4 : 8) #if (NGX_DEBUG) @@ -105,7 +105,7 @@ do { \ #define NGX_QUIC_FT_STOP_SENDING 0x05 #define NGX_QUIC_FT_CRYPTO 0x06 #define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM 0x08 +#define NGX_QUIC_FT_STREAM0 0x08 #define NGX_QUIC_FT_STREAM1 0x09 #define NGX_QUIC_FT_STREAM2 0x0A #define NGX_QUIC_FT_STREAM3 0x0B @@ -130,6 +130,12 @@ do { \ #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1e +#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) +#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) +#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) + + + /* TODO: real states, these are stubs */ typedef enum { NGX_QUIC_ST_INITIAL, @@ -184,6 +190,7 @@ typedef struct { typedef struct { + uint8_t type; uint64_t stream_id; uint64_t offset; uint64_t length; @@ -350,6 +357,13 @@ static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, static ngx_int_t ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); +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 ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 ngx_quic_set_read_secret, @@ -640,6 +654,57 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) return p - start; } + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) +{ + size_t len; + u_char *start; + + if (!ngx_quic_stream_bit_len(sf->type)) { +#if 0 + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "attempt to generate a stream frame without length"); +#endif + // XXX: handle error in caller + return NGX_ERROR; + } + + if (p == NULL) { + len = ngx_quic_varint_len(sf->type); + + if (ngx_quic_stream_bit_off(sf->type)) { + len += ngx_quic_varint_len(sf->offset); + } + + len += ngx_quic_varint_len(sf->stream_id); + + /* length is always present in generated frames */ + len += ngx_quic_varint_len(sf->length); + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, sf->type); + ngx_quic_build_int(&p, sf->stream_id); + + if (ngx_quic_stream_bit_off(sf->type)) { + ngx_quic_build_int(&p, sf->offset); + } + + /* length is always present in generated frames */ + ngx_quic_build_int(&p, sf->length); + + p = ngx_cpymem(p, sf->data, sf->length); + + return p - start; +} + + size_t ngx_quic_frame_len(ngx_quic_frame_t *frame) { @@ -648,6 +713,16 @@ ngx_quic_frame_len(ngx_quic_frame_t *frame) return ngx_quic_create_ack(NULL, &frame->u.ack); case NGX_QUIC_FT_CRYPTO: return ngx_quic_create_crypto(NULL, &frame->u.crypto); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(NULL, &frame->u.stream); default: /* BUG: unsupported frame type generated */ return 0; @@ -687,6 +762,17 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, p += ngx_quic_create_crypto(p, &f->u.crypto); break; + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + p += ngx_quic_create_stream(p, &f->u.stream); + break; + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -1653,7 +1739,7 @@ ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, return NGX_ERROR; break; - case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: case NGX_QUIC_FT_STREAM3: @@ -1665,6 +1751,8 @@ ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "STREAM frame, type: 0x%xi", frame->type); + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = ngx_quic_parse_int(&p); if (frame->type & 0x04) { frame->u.stream.offset = ngx_quic_parse_int(&p); @@ -1797,6 +1885,109 @@ ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qs = c->qs; + qc = qs->parent->quic; + + // XXX: get direct pointer from stream structure? + sn = ngx_quic_stream_lookup(&qc->stree, qs->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + // XXX: how to return EOF? + + b = sn->b; + + if (b->last - b->pos == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv() not ready"); + return NGX_AGAIN; // ? + } + + len = ngx_min(b->last - b->pos, (ssize_t) size); + + ngx_memcpy(buf, b->pos, len); + + b->pos += len; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv: %z of %uz", len, size); + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + u_char *p; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); + + qs = c->qs; + qc = qs->parent->quic; + + + // XXX: get direct pointer from stream structure? + sn = ngx_quic_stream_lookup(&qc->stree, qs->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; + } + + p = ngx_pnalloc(c->pool, size); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, buf, size); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM2; /* OFF=0 LEN=1 FIN=0 */ + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = 0; + frame->u.stream.length = size; + frame->u.stream.data = p; + + ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", + qs->id, size, frame->level); + + ngx_quic_queue_frame(qc, frame); + + return size; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit) +{ + // TODO + return NULL; +} + + /* process all payload from the current packet and generate ack if required */ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) @@ -1805,6 +1996,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ssize_t len; ngx_buf_t *b; ngx_uint_t ack_this; + ngx_pool_t *pool; + ngx_event_t *rev, *wev; ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; ngx_quic_stream_node_t *sn; @@ -1873,7 +2066,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.ncid.len); continue; - case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: case NGX_QUIC_FT_STREAM3: @@ -1882,12 +2075,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAM frame 0x%xi id 0x%xi off 0x%xi len 0x%xi", + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAM frame 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d", frame.type, frame.u.stream.stream_id, frame.u.stream.offset, - frame.u.stream.length); + frame.u.stream.length, + ngx_quic_stream_bit_off(frame.u.stream.type), + ngx_quic_stream_bit_len(frame.u.stream.type), + ngx_quic_stream_bit_fin(frame.u.stream.type)); sn = ngx_quic_stream_lookup(&qc->stree, frame.u.stream.stream_id); @@ -1899,13 +2095,28 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + return NGX_ERROR; + } + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination if (sn->c == NULL) { return NGX_ERROR; } + sn->c->pool = pool; + + rev = sn->c->read; + wev = sn->c->write; + + rev->log = c->log; + wev->log = c->log; + + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sn->node.key = frame.u.stream.stream_id; - sn->b = ngx_create_temp_buf(c->pool, 16 * 1024); // XXX enough for everyone + sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone if (sn->b == NULL) { return NGX_ERROR; } @@ -1917,9 +2128,14 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_rbtree_insert(&qc->stree, &sn->node); sn->s.id = frame.u.stream.stream_id; + sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; sn->s.parent = c; sn->c->qs = &sn->s; + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; + qc->stream_handler(sn->c); } else { @@ -1973,7 +2189,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_frame->type = NGX_QUIC_FT_ACK; ack_frame->u.ack.pn = pkt->pn; - ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler", pkt->pn); + ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); ngx_quic_queue_frame(qc, ack_frame); return ngx_quic_output(c); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f463e674e..55af26bf8 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -395,8 +395,39 @@ ngx_http_quic_stream_handler(ngx_connection_t *c) { ngx_quic_stream_t *qs = c->qs; + // STUB for stream read/write + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "quic stream: 0x%uXL", qs->id); + ssize_t n; + ngx_buf_t b; + + u_char buf[512]; + + b.start = buf; + b.end = buf + 512; + b.pos = b.last = b.start; + + n = c->recv(c, b.pos, b.end - b.start); + if (n < 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream read failed"); + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic stream: 0x%uXL %ui bytes read", qs->id, n); + + b.last += n; + + n = c->send(c, b.start, n); + + if (n < 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream write failed"); + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "quic stream: 0x%uXL %ui bytes written", qs->id, n); } -- cgit v1.2.3 From dcb6aab4609f440940d1aac1fb55cf211c760c62 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 15:56:10 +0300 Subject: Implemented ngx_quic_stream_send_chain() method. - just call send in a loop --- src/event/ngx_event_quic.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ee77d9153..e4d4f88e9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1983,7 +1983,41 @@ static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - // TODO + size_t len; + ssize_t n; + ngx_buf_t *b; + + while (in) { + b = in->buf; + + if (!ngx_buf_in_memory(b)) { + continue; + } + + if (ngx_buf_size(b) == 0) { + continue; + } + + len = b->last - b->pos; + + n = ngx_quic_stream_send(c, b->pos, len); + + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (n == NGX_AGAIN) { + return in; + } + + if (n != (ssize_t) len) { + b->pos += n; + return in; + } + + in = in->next; + } + return NULL; } -- cgit v1.2.3 From a1ac82ca0f1675fe20c6827349fff224905a3516 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 18:29:50 +0300 Subject: Implemented tracking offset in STREAM frames. --- src/event/ngx_event_quic.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e4d4f88e9..e005b4e6d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1962,14 +1962,16 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_memcpy(p, buf, size); frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM2; /* OFF=0 LEN=1 FIN=0 */ + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ frame->u.stream.type = frame->type; frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = 0; + frame->u.stream.offset = c->sent; frame->u.stream.length = size; frame->u.stream.data = p; + c->sent += size; + ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", qs->id, size, frame->level); -- cgit v1.2.3 From 365b77b58732a708168c995c7956f50d110fee33 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 18:30:37 +0300 Subject: Fixed infinite loop in ngx_quic_stream_send_chain(). --- src/event/ngx_event_quic.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e005b4e6d..880dda023 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1989,7 +1989,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, ssize_t n; ngx_buf_t *b; - while (in) { + for ( /* void */; in; in = in->next) { b = in->buf; if (!ngx_buf_in_memory(b)) { @@ -2016,8 +2016,6 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, b->pos += n; return in; } - - in = in->next; } return NULL; -- cgit v1.2.3 From 7739b6073b11086d9a3dc4b9744418070e182c33 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 19:36:33 +0300 Subject: HTTP/3. --- auto/make | 4 +- auto/modules | 21 + auto/options | 1 + src/event/ngx_event.c | 36 +- src/event/ngx_event_quic.c | 23 +- src/event/ngx_event_quic.h | 1 + src/http/ngx_http.h | 3 + src/http/ngx_http_core_module.c | 6 +- src/http/ngx_http_header_filter_module.c | 15 + src/http/ngx_http_parse.c | 3 +- src/http/ngx_http_request.c | 178 +++-- src/http/ngx_http_request.h | 13 + src/http/v3/ngx_http_v3.c | 176 +++++ src/http/v3/ngx_http_v3.h | 89 +++ src/http/v3/ngx_http_v3_module.c | 46 ++ src/http/v3/ngx_http_v3_request.c | 971 ++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 1097 ++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_tables.c | 385 +++++++++++ 18 files changed, 3006 insertions(+), 62 deletions(-) create mode 100644 src/http/v3/ngx_http_v3.c create mode 100644 src/http/v3/ngx_http_v3.h create mode 100644 src/http/v3/ngx_http_v3_module.c create mode 100644 src/http/v3/ngx_http_v3_request.c create mode 100644 src/http/v3/ngx_http_v3_streams.c create mode 100644 src/http/v3/ngx_http_v3_tables.c diff --git a/auto/make b/auto/make index 34c40cdd5..be60c4323 100644 --- a/auto/make +++ b/auto/make @@ -7,8 +7,8 @@ echo "creating $NGX_MAKEFILE" mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ - $NGX_OBJS/src/http/modules/perl \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ $NGX_OBJS/src/misc diff --git a/auto/modules b/auto/modules index b612e6f9f..0232fba5e 100644 --- a/auto/modules +++ b/auto/modules @@ -403,6 +403,27 @@ if [ $HTTP = YES ]; then ngx_module_type=HTTP + if [ $HTTP_V3 = YES ]; then + have=NGX_HTTP_V3 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + # XXX for Huffman + HTTP_V2=YES + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps=src/http/v3/ngx_http_v3.h + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_tables.c \ + src/http/v3/ngx_http_v3_streams.c \ + src/http/v3/ngx_http_v3_request.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if [ $HTTP_V2 = YES ]; then have=NGX_HTTP_V2 . auto/have have=NGX_HTTP_HEADERS . auto/have diff --git a/auto/options b/auto/options index 521c9768d..de1634462 100644 --- a/auto/options +++ b/auto/options @@ -59,6 +59,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V3=YES HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index 402a7f5e2..f0ab73afe 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -268,6 +268,22 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle) ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { + ngx_connection_t *c; + + c = rev->data; + + if (c->qs) { + + if (!rev->active && !rev->ready) { + rev->active = 1; + + } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) { + rev->active = 0; + } + + return NGX_OK; + } + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -338,14 +354,26 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) { ngx_connection_t *c; - if (lowat) { - c = wev->data; + c = wev->data; + if (lowat) { if (ngx_send_lowat(c, lowat) == NGX_ERROR) { return NGX_ERROR; } } + if (c->qs) { + + if (!wev->active && !wev->ready) { + wev->active = 1; + + } else if (wev->active && wev->ready) { + wev->active = 0; + } + + return NGX_OK; + } + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -916,6 +944,10 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat) { int sndlowat; + if (c->qs) { + return NGX_OK; + } + #if (NGX_HAVE_LOWAT_EVENT) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 880dda023..248cc9087 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1909,6 +1909,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) b = sn->b; if (b->last - b->pos == 0) { + c->read->ready = 0; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv() not ready"); return NGX_AGAIN; // ? @@ -2029,6 +2030,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) u_char *end, *p; ssize_t len; ngx_buf_t *b; + ngx_log_t *log; ngx_uint_t ack_this; ngx_pool_t *pool; ngx_event_t *rev, *wev; @@ -2129,21 +2131,38 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination + if (sn->c == NULL) { + return NGX_ERROR; + } + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); if (pool == NULL) { + /* XXX free connection */ return NGX_ERROR; } - sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination - if (sn->c == NULL) { + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + /* XXX free pool and connection */ return NGX_ERROR; } + *log = *c->log; + pool->log = log; + + sn->c->log = log; sn->c->pool = pool; + sn->c->listening = c->listening; + sn->c->sockaddr = c->sockaddr; + sn->c->local_sockaddr = c->local_sockaddr; + rev = sn->c->read; wev = sn->c->write; + rev->ready = 1; + rev->log = c->log; wev->log = c->log; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index f3ff3da77..6a60c8703 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -14,6 +14,7 @@ struct ngx_quic_stream_s { uint64_t id; ngx_uint_t unidirectional:1; ngx_connection_t *parent; + void *data; }; /* TODO: get rid somehow of ssl argument? */ diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 8b43857ee..8772001c0 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -38,6 +38,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r, #if (NGX_HTTP_V2) #include #endif +#if (NGX_HTTP_V3) +#include +#endif #if (NGX_HTTP_CACHE) #include #endif diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 5c210bebd..576c679d7 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -809,7 +809,7 @@ ngx_http_handler(ngx_http_request_t *r) if (!r->internal) { switch (r->headers_in.connection_type) { case 0: - r->keepalive = (r->http_version > NGX_HTTP_VERSION_10); + r->keepalive = (r->http_version == NGX_HTTP_VERSION_11); break; case NGX_HTTP_CONNECTION_CLOSE: @@ -4000,14 +4000,14 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (ngx_strcmp(value[n].data, "http3") == 0) { -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) lsopt.http3 = 1; lsopt.type = SOCK_DGRAM; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"http3\" parameter requires " - "ngx_http_ssl_module"); + "ngx_http_v3_module"); return NGX_CONF_ERROR; #endif } diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index 9b8940590..02e251f79 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -179,6 +179,21 @@ ngx_http_header_filter(ngx_http_request_t *r) return NGX_OK; } +#if (NGX_HTTP_V3) + + if (r->http_version == NGX_HTTP_VERSION_30) { + ngx_chain_t *cl; + + cl = ngx_http_v3_create_header(r); + if (cl == NULL) { + return NGX_ERROR; + } + + return ngx_http_write_filter(r, cl); + } + +#endif + if (r->http_version < NGX_HTTP_VERSION_10) { return NGX_OK; } diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index cfc42f9dd..28aa8b0dd 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -144,6 +144,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) /* HTTP methods: GET, HEAD, POST */ case sw_start: r->request_start = p; + r->method_start = p; if (ch == CR || ch == LF) { break; @@ -158,7 +159,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case sw_method: if (ch == ' ') { - r->method_end = p - 1; + r->method_end = p; m = r->request_start; switch (p - m) { diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 55af26bf8..ef305faf4 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -64,7 +64,9 @@ static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); #endif +#if (NGX_HTTP_V3) static void ngx_http_quic_stream_handler(ngx_connection_t *c); +#endif static char *ngx_http_client_errors[] = { @@ -219,7 +221,15 @@ ngx_http_init_connection(ngx_connection_t *c) ngx_http_in6_addr_t *addr6; #endif - hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); +#if (NGX_HTTP_V3) + if (c->type == SOCK_DGRAM) { + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + hc->quic = 1; + + } else +#endif + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { ngx_http_close_connection(c); return; @@ -329,11 +339,9 @@ ngx_http_init_connection(ngx_connection_t *c) rev->ready = 1; } -#if (NGX_HTTP_SSL) - if (hc->addr_conf->http3) { - ngx_http_ssl_srv_conf_t *sscf; - - hc->quic = 1; +#if (NGX_HTTP_V3) + if (hc->quic) { + ngx_http_ssl_srv_conf_t *sscf; sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); @@ -390,46 +398,63 @@ ngx_http_init_connection(ngx_connection_t *c) } +#if (NGX_HTTP_V3) + static void ngx_http_quic_stream_handler(ngx_connection_t *c) { - ngx_quic_stream_t *qs = c->qs; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_v3_connection_t *h3c; - // STUB for stream read/write + pc = c->qs->parent; + h3c = pc->data; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic stream: 0x%uXL", qs->id); - ssize_t n; - ngx_buf_t b; + if (c->qs->unidirectional) { + ngx_http_v3_handle_client_uni_stream(c); + return; + } - u_char buf[512]; + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + ngx_http_close_connection(c); + return; + } - b.start = buf; - b.end = buf + 512; - b.pos = b.last = b.start; + ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); + c->data = hc; - n = c->recv(c, b.pos, b.end - b.start); - if (n < 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream read failed"); + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + ngx_http_close_connection(c); return; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic stream: 0x%uXL %ui bytes read", qs->id, n); + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; - b.last += n; + c->log->connection = c->number; + c->log->handler = ngx_http_log_error; + c->log->data = ctx; + c->log->action = "waiting for request"; - n = c->send(c, b.start, n); + c->log_error = NGX_ERROR_INFO; - if (n < 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream write failed"); - return; - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new stream id:0x%uXL", c->qs->id); - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "quic stream: 0x%uXL %ui bytes written", qs->id, n); + rev = c->read; + rev->handler = ngx_http_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + + rev->handler(rev); } +#endif + static void ngx_http_wait_request_handler(ngx_event_t *rev) @@ -679,6 +704,13 @@ ngx_http_alloc_request(ngx_connection_t *c) r->method = NGX_HTTP_UNKNOWN; r->http_version = NGX_HTTP_VERSION_10; +#if (NGX_HTTP_V3) + if (hc->quic) { + r->http_version = NGX_HTTP_VERSION_30; + r->filter_need_in_memory = 1; + } +#endif + r->headers_in.content_length_n = -1; r->headers_in.keep_alive_n = -1; r->headers_out.content_length_n = -1; @@ -1128,7 +1160,16 @@ ngx_http_process_request_line(ngx_event_t *rev) } } - rc = ngx_http_parse_request_line(r, r->header_in); + switch (r->http_version) { +#if (NGX_HTTP_V3) + case NGX_HTTP_VERSION_30: + rc = ngx_http_v3_parse_header(r, r->header_in, 1); + break; +#endif + + default: /* HTTP/1.x */ + rc = ngx_http_parse_request_line(r, r->header_in); + } if (rc == NGX_OK) { @@ -1141,8 +1182,8 @@ ngx_http_process_request_line(ngx_event_t *rev) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); - r->method_name.len = r->method_end - r->request_start + 1; - r->method_name.data = r->request_line.data; + r->method_name.len = r->method_end - r->method_start; + r->method_name.data = r->method_start; if (r->http_protocol.data) { r->http_protocol.len = r->request_end - r->http_protocol.data; @@ -1213,6 +1254,15 @@ ngx_http_process_request_line(ngx_event_t *rev) break; } + if (rc == NGX_DONE) { + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + break; + } + if (rc != NGX_AGAIN) { /* there was error while a request line parsing */ @@ -1403,7 +1453,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - rc = NGX_AGAIN; + rc = NGX_OK; for ( ;; ) { @@ -1457,11 +1507,21 @@ ngx_http_process_request_headers(ngx_event_t *rev) /* the host header could change the server configuration context */ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - rc = ngx_http_parse_header_line(r, r->header_in, - cscf->underscores_in_headers); + switch (r->http_version) { +#if (NGX_HTTP_V3) + case NGX_HTTP_VERSION_30: + rc = ngx_http_v3_parse_header(r, r->header_in, 0); + break; +#endif + + default: /* HTTP/1.x */ + rc = ngx_http_parse_header_line(r, r->header_in, + cscf->underscores_in_headers); + } if (rc == NGX_OK) { + /* XXX */ r->request_length += r->header_in->pos - r->header_name_start; if (r->invalid_header && cscf->ignore_invalid_headers) { @@ -1487,11 +1547,11 @@ ngx_http_process_request_headers(ngx_event_t *rev) h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; - h->key.data[h->key.len] = '\0'; + //h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; - h->value.data[h->value.len] = '\0'; + //h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { @@ -1642,7 +1702,7 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, return NGX_OK; } - old = request_line ? r->request_start : r->header_name_start; + old = request_line ? r->request_start : r->header_name_start; /* XXX */ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); @@ -1721,45 +1781,59 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->request_end = new + (r->request_end - old); } - r->method_end = new + (r->method_end - old); + if (r->method_start >= old && r->method_start < r->header_in->pos) { + r->method_start = new + (r->method_start - old); + r->method_end = new + (r->method_end - old); + } - r->uri_start = new + (r->uri_start - old); - r->uri_end = new + (r->uri_end - old); + if (r->uri_start >= old && r->uri_start < r->header_in->pos) { + r->uri_start = new + (r->uri_start - old); + r->uri_end = new + (r->uri_end - old); + } - if (r->schema_start) { + if (r->schema_start >= old && r->schema_start < r->header_in->pos) { r->schema_start = new + (r->schema_start - old); r->schema_end = new + (r->schema_end - old); } - if (r->host_start) { + if (r->host_start >= old && r->host_start < r->header_in->pos) { r->host_start = new + (r->host_start - old); if (r->host_end) { r->host_end = new + (r->host_end - old); } } - if (r->port_start) { + if (r->port_start >= old && r->port_start < r->header_in->pos) { r->port_start = new + (r->port_start - old); r->port_end = new + (r->port_end - old); } - if (r->uri_ext) { + if (r->uri_ext >= old && r->uri_ext < r->header_in->pos) { r->uri_ext = new + (r->uri_ext - old); } - if (r->args_start) { + if (r->args_start >= old && r->args_start < r->header_in->pos) { r->args_start = new + (r->args_start - old); } - if (r->http_protocol.data) { + if (r->http_protocol.data >= old + && r->http_protocol.data < r->header_in->pos) + { r->http_protocol.data = new + (r->http_protocol.data - old); } } else { - r->header_name_start = new; - r->header_name_end = new + (r->header_name_end - old); - r->header_start = new + (r->header_start - old); - r->header_end = new + (r->header_end - old); + if (r->header_name_start >= old + && r->header_name_start < r->header_in->pos) + { + r->header_name_start = new; + r->header_name_end = new + (r->header_name_end - old); + } + + if (r->header_start >= old && r->header_start < r->header_in->pos) { + r->header_start = new + (r->header_start - old); + r->header_end = new + (r->header_end - old); + } } r->header_in = b; @@ -1984,7 +2058,7 @@ ngx_http_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } - if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) { + if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_11) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent HTTP/1.1 request without \"Host\" header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 8cc5d6432..04d88db7b 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_30 3000 #define NGX_HTTP_UNKNOWN 0x0001 #define NGX_HTTP_GET 0x0002 @@ -584,6 +585,7 @@ struct ngx_http_request_s { u_char *args_start; u_char *request_start; u_char *request_end; + u_char *method_start; u_char *method_end; u_char *schema_start; u_char *schema_end; @@ -592,6 +594,17 @@ struct ngx_http_request_s { u_char *port_start; u_char *port_end; +#if (NGX_HTTP_V3) + ngx_uint_t h3_length; + ngx_uint_t h3_index; + ngx_uint_t h3_insert_count; + ngx_uint_t h3_sign; + ngx_uint_t h3_delta_base; + ngx_uint_t h3_huffman; + ngx_uint_t h3_dynamic; + ngx_uint_t h3_offset; +#endif + unsigned http_minor:16; unsigned http_major:16; }; diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c new file mode 100644 index 000000000..e804cf6f5 --- /dev/null +++ b/src/http/v3/ngx_http_v3.c @@ -0,0 +1,176 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +uintptr_t +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) +{ + if (value <= 63) { + if (p == NULL) { + return 1; + } + + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 16383) { + if (p == NULL) { + return 2; + } + + *p++ = 0x40 | (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 1073741823) { + if (p == NULL) { + return 3; + } + + *p++ = 0x80 | (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; + + } + + if (p == NULL) { + return 4; + } + + *p++ = 0xc0 | (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) +{ + ngx_uint_t thresh, n; + + thresh = (1 << prefix) - 1; + + if (value < thresh) { + if (p == NULL) { + return 1; + } + + *p++ |= value; + return (uintptr_t) p; + } + + value -= thresh; + + for (n = 10; n > 1; n--) { + if (value >> (7 * (n - 1))) { + break; + } + } + + if (p == NULL) { + return n + 1; + } + + *p++ |= thresh; + + for ( /* void */ ; n > 1; n--) { + *p++ = 0x80 | (value >> 7 * (n - 1)); + } + + *p++ = value & 0x7f; + + return (uintptr_t) p; +} + + +uint64_t +ngx_http_v3_decode_varlen_int(u_char *p) +{ + uint64_t value; + ngx_uint_t len; + + len = *p >> 6; + value = *p & 0x3f; + + while (len--) { + value = (value << 8) + *p++; + } + + return value; +} + + +int64_t +ngx_http_v3_decode_prefix_int(u_char **src, size_t len, ngx_uint_t prefix) +{ + u_char *p; + int64_t value, thresh; + + if (len == 0) { + return NGX_ERROR; + } + + p = *src; + + thresh = (1 << prefix) - 1; + value = *p++ & thresh; + + if (value != thresh) { + *src = p; + return value; + } + + value = 0; + + /* XXX handle overflows */ + + while (--len) { + value = (value << 7) + (*p & 0x7f); + if ((*p++ & 0x80) == 0) { + *src = p; + return value + thresh; + } + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_decode_huffman(ngx_connection_t *c, ngx_str_t *s) +{ + u_char state, *p, *data; + + state = 0; + + p = ngx_pnalloc(c->pool, s->len * 8 / 5); + if (p == NULL) { + return NGX_ERROR; + } + + data = p; + + if (ngx_http_v2_huff_decode(&state, s->data, s->len, &p, 1, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + s->len = p - data; + s->data = data; + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h new file mode 100644 index 000000000..8fa54d8e5 --- /dev/null +++ b/src/http/v3/ngx_http_v3.h @@ -0,0 +1,89 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_HTTP_V3_STREAM 0x48335354 /* "H3ST" */ + + +#define NGX_HTTP_V3_VARLEN_INT_LEN 4 +#define NGX_HTTP_V3_PREFIX_INT_LEN 11 + + +typedef struct { + ngx_http_connection_t hc; + + ngx_array_t *dynamic; + + ngx_connection_t *client_encoder; + ngx_connection_t *client_decoder; + ngx_connection_t *server_encoder; + ngx_connection_t *server_decoder; +} ngx_http_v3_connection_t; + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; + + +ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, + ngx_uint_t pseudo); +ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); + + +uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); +uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, + ngx_uint_t prefix); +uint64_t ngx_http_v3_decode_varlen_int(u_char *p); +int64_t ngx_http_v3_decode_prefix_int(u_char **src, size_t len, + ngx_uint_t prefix); +ngx_int_t ngx_http_v3_decode_huffman(ngx_connection_t *c, ngx_str_t *s); + +void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c); + +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_http_v3_header_t *ngx_http_v3_lookup_table(ngx_connection_t *c, + ngx_uint_t dynamic, ngx_uint_t index); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); + +ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_client_set_capacity(ngx_connection_t *c, + ngx_uint_t capacity); +ngx_int_t ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_client_ack_header(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_client_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c new file mode 100644 index 000000000..af310bd44 --- /dev/null +++ b/src/http/v3/ngx_http_v3_module.c @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + + +static ngx_command_t ngx_http_v3_commands[] = { + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_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_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* 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 +}; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c new file mode 100644 index 000000000..b34eed98e --- /dev/null +++ b/src/http/v3/ngx_http_v3_request.c @@ -0,0 +1,971 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + + +static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); + + +struct { + ngx_str_t name; + ngx_uint_t method; +} ngx_http_v3_methods[] = { + + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, + { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, + { ngx_string("PUT"), NGX_HTTP_PUT }, + { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, + { ngx_string("DELETE"), NGX_HTTP_DELETE }, + { ngx_string("COPY"), NGX_HTTP_COPY }, + { ngx_string("MOVE"), NGX_HTTP_MOVE }, + { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, + { ngx_string("LOCK"), NGX_HTTP_LOCK }, + { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, + { ngx_string("PATCH"), NGX_HTTP_PATCH }, + { ngx_string("TRACE"), NGX_HTTP_TRACE } +}; + + +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) +{ + u_char *p, ch; + ngx_str_t name, value; + ngx_int_t rc; + ngx_uint_t length, index, insert_count, sign, base, delta_base, + huffman, dynamic, offset; + ngx_connection_t *c; + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_length, + sw_length_1, + sw_length_2, + sw_length_3, + sw_header_block, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base, + sw_header, + sw_old_header, + sw_header_ri, + sw_header_pbi, + sw_header_lri, + sw_header_lpbi, + sw_header_l_name_len, + sw_header_l_name, + sw_header_value_len, + sw_header_read_value_len, + sw_header_value + } state; + + c = r->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header, pseudo:%ui", pseudo); + + if (r->state == sw_old_header) { + r->state = sw_header; + return NGX_OK; + } + + length = r->h3_length; + index = r->h3_index; + insert_count = r->h3_insert_count; + sign = r->h3_sign; + delta_base = r->h3_delta_base; + huffman = r->h3_huffman; + dynamic = r->h3_dynamic; + offset = r->h3_offset; + + name.data = r->header_name_start; + name.len = r->header_name_end - r->header_name_start; + value.data = r->header_start; + value.len = r->header_end - r->header_start; + + if (r->state == sw_start) { + length = 1; + } + +again: + + state = r->state; + + if (state == sw_header && length == 0) { + r->state = sw_start; + return NGX_HTTP_PARSE_HEADER_DONE; + } + + for (p = b->pos; p < b->last; p++) { + + if (state >= sw_header_block && length-- == 0) { + goto failed; + } + + ch = *p; + + switch (state) { + + case sw_start: + + if (ch != NGX_HTTP_V3_FRAME_HEADERS) { + goto failed; + } + + r->request_start = p; + state = sw_length; + break; + + case sw_length: + + length = ch; + if (length & 0xc0) { + state = sw_length_1; + break; + } + + state = sw_header_block; + break; + + case sw_length_1: + + length = (length << 8) + ch; + if ((length & 0xc000) != 0x4000) { + state = sw_length_2; + break; + } + + length &= 0x3fff; + state = sw_header_block; + break; + + case sw_length_2: + + length = (length << 8) + ch; + if ((length & 0xc00000) != 0x800000) { + state = sw_length_3; + break; + } + + /* fall through */ + + case sw_length_3: + + length &= 0x3fffff; + state = sw_header_block; + break; + + case sw_header_block: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block length:%ui", length); + + if (ch != 0xff) { + insert_count = ch; + state = sw_delta_base; + break; + } + + insert_count = 0; + state = sw_req_insert_count; + break; + + case sw_req_insert_count: + + insert_count = (insert_count << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + insert_count += 0xff; + state = sw_delta_base; + break; + + case sw_delta_base: + + sign = (ch & 0x80) ? 1 : 0; + delta_base = ch & 0x7f; + + if (delta_base != 0x7f) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block " + "insert_count:%ui, sign:%ui, delta_base:%ui", + insert_count, sign, delta_base); + goto done; + } + + delta_base = 0; + state = sw_read_delta_base; + break; + + case sw_read_delta_base: + + delta_base = (delta_base << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + delta_base += 0x7f; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block " + "insert_count:%ui, sign:%ui, delta_base:%ui", + insert_count, sign, delta_base); + goto done; + + case sw_header: + + index = 0; + huffman = 0; + ngx_str_null(&name); + ngx_str_null(&value); + + if (ch & 0x80) { + /* Indexed Header Field */ + + dynamic = (ch & 0x40) ? 0 : 1; + index = ch & 0x3f; + + if (index != 0x3f) { + goto done; + } + + index = 0; + state = sw_header_ri; + break; + } + + if (ch & 0x40) { + /* Literal Header Field With Name Reference */ + + dynamic = (ch & 0x10) ? 0 : 1; + index = ch & 0x0f; + + if (index != 0x0f) { + state = sw_header_value_len; + break; + } + + index = 0; + state = sw_header_lri; + break; + } + + if (ch & 0x20) { + /* Literal Header Field Without Name Reference */ + + huffman = (ch & 0x08) ? 1 : 0; + name.len = ch & 0x07; + + if (name.len == 0) { + goto failed; + } + + if (name.len != 0x07) { + offset = 0; + state = sw_header_l_name; + break; + } + + name.len = 0; + state = sw_header_l_name_len; + break; + } + + if (ch & 10) { + /* Indexed Header Field With Post-Base Index */ + + dynamic = 2; + index = ch & 0x0f; + + if (index != 0x0f) { + goto done; + } + + index = 0; + state = sw_header_pbi; + break; + } + + /* Literal Header Field With Post-Base Name Reference */ + + dynamic = 2; + index = ch & 0x07; + + if (index != 0x07) { + state = sw_header_value_len; + break; + } + + index = 0; + state = sw_header_lpbi; + break; + + case sw_header_ri: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x3f; + goto done; + + case sw_header_pbi: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x0f; + goto done; + + case sw_header_lri: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x0f; + state = sw_header_value_len; + break; + + case sw_header_lpbi: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x07; + state = sw_header_value_len; + break; + + + case sw_header_l_name_len: + + name.len = (name.len << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + name.len += 0x07; + offset = 0; + state = sw_header_l_name; + break; + + case sw_header_l_name: + if (offset++ == 0) { + name.data = p; + } + + if (offset != name.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(c, &name) != NGX_OK) { + goto failed; + } + } + + state = sw_header_value_len; + break; + + case sw_header_value_len: + + huffman = (ch & 0x80) ? 1 : 0; + value.len = ch & 0x7f; + + if (value.len == 0) { + value.data = p; + goto done; + } + + if (value.len != 0x7f) { + offset = 0; + state = sw_header_value; + break; + } + + value.len = 0; + state = sw_header_read_value_len; + break; + + case sw_header_read_value_len: + + value.len = (value.len << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + value.len += 0x7f; + offset = 0; + state = sw_header_value; + break; + + case sw_header_value: + + if (offset++ == 0) { + value.data = p; + } + + if (offset != value.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(c, &value) != NGX_OK) { + goto failed; + } + } + + goto done; + + case sw_old_header: + + break; + } + } + + b->pos = p; + r->state = state; + r->h3_length = length; + r->h3_index = index; + r->h3_insert_count = insert_count; + r->h3_sign = sign; + r->h3_delta_base = delta_base; + r->h3_huffman = huffman; + r->h3_dynamic = dynamic; + r->h3_offset = offset; + + /* XXX fix large reallocations */ + r->header_name_start = name.data; + r->header_name_end = name.data + name.len; + r->header_start = value.data; + r->header_end = value.data + value.len; + + /* XXX r->lowcase_index = i; */ + + return NGX_AGAIN; + +done: + + b->pos = p + 1; + r->state = sw_header; + r->h3_length = length; + r->h3_insert_count = insert_count; + r->h3_sign = sign; + r->h3_delta_base = delta_base; + + if (state < sw_header) { + if (ngx_http_v3_check_insert_count(c, insert_count) != NGX_OK) { + return NGX_DONE; + } + + goto again; + } + + if (sign == 0) { + base = insert_count + delta_base; + } else { + base = insert_count - delta_base - 1; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header %s[%ui], base:%ui, \"%V\":\"%V\"", + dynamic ? "dynamic" : "static", index, base, &name, &value); + + if (name.data == NULL) { + + if (dynamic == 2) { + index = base - index - 1; + } else if (dynamic == 1) { + index += base; + } + + h = ngx_http_v3_lookup_table(c, dynamic, index); + if (h == NULL) { + goto failed; + } + + name = h->name; + + if (value.data == NULL) { + value = h->value; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header \"%V\":\"%V\"", &name, &value); + + if (pseudo) { + rc = ngx_http_v3_process_pseudo_header(r, &name, &value); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_OK) { + r->request_end = p + 1; + goto again; + } + + /* rc == NGX_DONE */ + + r->state = sw_old_header; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header left:%ui", length); + + r->header_name_start = name.data; + r->header_name_end = name.data + name.len; + r->header_start = value.data; + r->header_end = value.data + value.len; + r->header_hash = ngx_hash_key(name.data, name.len); /* XXX */ + + /* XXX r->lowcase_index = i; */ + + return NGX_OK; + +failed: + + return NGX_HTTP_PARSE_INVALID_REQUEST; +} + + +static ngx_int_t +ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_uint_t i; + ngx_connection_t *c; + + c = r->connection; + + if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + r->method_start = value->data; + r->method_end = value->data + value->len; + + for (i = 0; i < sizeof(ngx_http_v3_methods) + / sizeof(ngx_http_v3_methods[0]); i++) + { + if (value->len == ngx_http_v3_methods[i].name.len + && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data, + value->len) == 0) + { + r->method = ngx_http_v3_methods[i].method; + break; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 method \"%V\" %ui", value, r->method); + return NGX_OK; + } + + if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid :path header: \"%V\"", value); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 path \"%V\"", value); + + return NGX_OK; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + r->schema_start = value->data; + r->schema_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 schema \"%V\"", value); + + return NGX_OK; + } + + if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + r->host_start = value->data; + r->host_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 authority \"%V\"", value); + + return NGX_OK; + } + + if (name->len && name->data[0] == ':') { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 unknown pseudo header \"%V\" \"%V\"", + name, value); + return NGX_OK; + } + + return NGX_DONE; +} + + +ngx_chain_t * +ngx_http_v3_create_header(ngx_http_request_t *r) +{ + u_char *p; + size_t len, hlen, n; + ngx_buf_t *b; + ngx_uint_t i, j; + ngx_chain_t *hl, *cl, *bl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); + + /* XXX support chunked body in the chunked filter */ + if (r->headers_out.content_length_n == -1) { + return NULL; + } + + len = 0; + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); + + } else { + len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4) + + ngx_http_v3_encode_prefix_int(NULL, 3, 7); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_prefix_int(NULL, 92, 4) + + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_prefix_int(NULL, 6, 4) + + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len, + 7) + + ngx_cached_http_time.len; + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_prefix_int(NULL, 53, 4) + + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + } + + if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); + + } else { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_prefix_int(NULL, 10, 4) + 1 + + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT"); + } + + /* XXX location */ + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + /* Vary: Accept-Encoding */ + len += ngx_http_v3_encode_prefix_int(NULL, 59, 6); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.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 (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_prefix_int(NULL, header[i].key.len, 3) + + header[i].key.len + + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 ) + + header[i].value.len; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + *b->last++ = 0; + *b->last++ = 0; + + if (r->headers_out.status == NGX_HTTP_OK) { + /* :status: 200 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6); + + } else { + /* :status: 200 */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); + b->last = ngx_sprintf(b->last, "%03ui ", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + /* server */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + b->last = ngx_cpymem(b->last, p, n); + } + + if (r->headers_out.date == NULL) { + /* date */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + ngx_cached_http_time.len, 7); + b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + /* content-type: text/plain */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + + p = b->last; + b->last = ngx_copy(b->last, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + b->last = ngx_cpymem(b->last, "; charset=", + sizeof("; charset=") - 1); + b->last = ngx_copy(b->last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = b->last - p; + r->headers_out.content_type.data = p; + } + } + + if (r->headers_out.content_length_n == 0) { + /* content-length: 0 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); + + } else if (r->headers_out.content_length_n > 0) { + /* content-length: 0 */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); + p = b->last++; + b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + *p = b->last - p - 1; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + /* last-modified */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4); + p = b->last++; + b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); + *p = b->last - p - 1; + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + /* vary: accept-encoding */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6); + } +#endif + + part = &r->headers_out.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 (header[i].hash == 0) { + continue; + } + + *b->last = 0x30; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + header[i].key.len, + 3); + for (j = 0; j < header[i].key.len; j++) { + *b->last++ = ngx_tolower(header[i].key.data[j]); + } + + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + header[i].value.len, + 7); + b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = 1 + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(c->pool, len); + if (b == NULL) { + return NULL; + } + + *b->last++ = NGX_HTTP_V3_FRAME_HEADERS; + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(c->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + hlen = 1 + ngx_http_v3_encode_varlen_int(NULL, len); + + if (r->headers_out.content_length_n >= 0) { + len = 1 + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(c->pool, len); + if (b == NULL) { + NULL; + } + + *b->last++ = NGX_HTTP_V3_FRAME_DATA; + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + r->headers_out.content_length_n); + + bl = ngx_alloc_chain_link(c->pool); + if (bl == NULL) { + return NULL; + } + + bl->buf = b; + bl->next = NULL; + cl->next = bl; + } + + return hl; +} diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c new file mode 100644 index 000000000..2b757d81f --- /dev/null +++ b/src/http/v3/ngx_http_v3_streams.c @@ -0,0 +1,1097 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_HTTP_V3_CONTROL_STREAM 0x00 +#define NGX_HTTP_V3_PUSH_STREAM 0x01 +#define NGX_HTTP_V3_ENCODER_STREAM 0x02 +#define NGX_HTTP_V3_DECODER_STREAM 0x03 + + +typedef struct { + uint32_t signature; /* QSTR */ + u_char buf[4]; + + ngx_uint_t len; + ngx_uint_t type; + ngx_uint_t state; + ngx_uint_t index; + ngx_uint_t offset; + + ngx_str_t name; + ngx_str_t value; + + unsigned client:1; + unsigned dynamic:1; + unsigned huffman:1; +} ngx_http_v3_uni_stream_t; + + +static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); +static void ngx_http_v3_uni_stream_cleanup(void *data); +static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); +static void ngx_http_v3_dummy_stream_handler(ngx_event_t *rev); +static void ngx_http_v3_client_encoder_handler(ngx_event_t *rev); +static void ngx_http_v3_client_decoder_handler(ngx_event_t *rev); + +static ngx_connection_t *ngx_http_v3_create_uni_stream(ngx_connection_t *c, + ngx_uint_t type); +static ngx_connection_t *ngx_http_v3_get_server_encoder(ngx_connection_t *c); +static ngx_connection_t *ngx_http_v3_get_server_decoder(ngx_connection_t *c); + + +void +ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) +{ + ngx_pool_cleanup_t *cln; + ngx_http_v3_uni_stream_t *us; + + c->log->connection = c->number; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new uni stream id:0x%uXL", c->qs->id); + + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + ngx_http_v3_close_uni_stream(c); + return; + } + + us->signature = NGX_HTTP_V3_STREAM; + us->client = 1; + us->type = (ngx_uint_t) -1; + + c->data = us; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_v3_close_uni_stream(c); + return; + } + + cln->handler = ngx_http_v3_uni_stream_cleanup; + cln->data = c; + + c->read->handler = ngx_http_v3_read_uni_stream_type; + c->read->handler(c->read); +} + + +static void +ngx_http_v3_close_uni_stream(ngx_connection_t *c) +{ + ngx_pool_t *pool; + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +static void +ngx_http_v3_uni_stream_cleanup(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_v3_connection_t *h3c; + ngx_http_v3_uni_stream_t *us; + + us = c->data; + h3c = c->qs->parent->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); + + switch (us->type) { + + case NGX_HTTP_V3_ENCODER_STREAM: + + if (us->client) { + h3c->client_encoder = NULL; + } else { + h3c->server_encoder = NULL; + } + + break; + + case NGX_HTTP_V3_DECODER_STREAM: + + if (us->client) { + h3c->client_decoder = NULL; + } else { + h3c->server_decoder = NULL; + } + + break; + } +} + + +static void +ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) +{ + u_char *p; + ssize_t n, len; + ngx_connection_t *c; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_uni_stream_t *us; + + c = rev->data; + us = c->data; + h3c = c->qs->parent->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); + + while (rev->ready) { + + p = &us->buf[us->len]; + + if (us->len == 0) { + len = 1; + } else { + len = (us->buf[0] >> 6) + 1 - us->len; + } + + n = c->recv(c, p, len); + + if (n == NGX_ERROR) { + goto failed; + } + + if (n == NGX_AGAIN) { + break; + } + + us->len += n; + + if (n != len) { + break; + } + + if ((us->buf[0] >> 6) + 1 == us->len) { + us->type = ngx_http_v3_decode_varlen_int(us->buf); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream type:%ui", us->type); + + switch (us->type) { + + case NGX_HTTP_V3_ENCODER_STREAM: + if (h3c->client_encoder) { + goto failed; + } + + h3c->client_encoder = c; + rev->handler = ngx_http_v3_client_encoder_handler; + break; + + case NGX_HTTP_V3_DECODER_STREAM: + if (h3c->client_decoder) { + goto failed; + } + + h3c->client_decoder = c; + rev->handler = ngx_http_v3_client_decoder_handler; + break; + + case NGX_HTTP_V3_CONTROL_STREAM: + case NGX_HTTP_V3_PUSH_STREAM: + + /* ignore these */ + + default: + rev->handler = ngx_http_v3_dummy_stream_handler; + } + + rev->handler(rev); + return; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + goto failed; + } + + return; + +failed: + + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_dummy_stream_handler(ngx_event_t *rev) +{ + u_char buf[128]; + ngx_connection_t *c; + + /* read out and ignore */ + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy stream reader"); + + while (rev->ready) { + if (c->recv(c, buf, sizeof(buf)) == NGX_ERROR) { + goto failed; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + goto failed; + } + + return; + +failed: + + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_client_encoder_handler(ngx_event_t *rev) +{ + u_char v; + ssize_t n; + ngx_str_t name, value; + ngx_uint_t dynamic, huffman, index, offset; + ngx_connection_t *c, *pc; + ngx_http_v3_uni_stream_t *st; + enum { + sw_start = 0, + sw_inr_name_index, + sw_inr_value_length, + sw_inr_read_value_length, + sw_inr_value, + sw_iwnr_name_length, + sw_iwnr_name, + sw_iwnr_value_length, + sw_iwnr_read_value_length, + sw_iwnr_value, + sw_capacity, + sw_duplicate + } state; + + c = rev->data; + st = c->data; + pc = c->qs->parent; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client encoder"); + + state = st->state; + dynamic = st->dynamic; + huffman = st->huffman; + index = st->index; + offset = st->offset; + name = st->name; + value = st->value; + + while (rev->ready) { + + /* XXX limit checks */ + /* XXX buffer input */ + + n = c->recv(c, &v, 1); + + if (n == NGX_ERROR || n == 0) { + goto failed; + } + + if (n != 1) { + break; + } + + /* XXX v -> ch */ + + switch (state) { + + case sw_start: + + if (v & 0x80) { + /* Insert With Name Reference */ + + dynamic = (v & 0x40) ? 0 : 1; + index = v & 0x3f; + + if (index != 0x3f) { + state = sw_inr_value_length; + break; + } + + index = 0; + state = sw_inr_name_index; + break; + } + + if (v & 0x40) { + /* Insert Without Name Reference */ + + huffman = (v & 0x20) ? 1 : 0; + name.len = v & 0x1f; + + if (name.len != 0x1f) { + offset = 0; + state = sw_iwnr_name; + break; + } + + name.len = 0; + state = sw_iwnr_name_length; + break; + } + + if (v & 0x20) { + /* Set Dynamic Table Capacity */ + + index = v & 0x1f; + + if (index != 0x1f) { + if (ngx_http_v3_set_capacity(c, index) != NGX_OK) { + goto failed; + } + + break; + } + + index = 0; + state = sw_capacity; + break; + } + + /* Duplicate */ + + index = v & 0x1f; + + if (index != 0x1f) { + if (ngx_http_v3_duplicate(c, index) != NGX_OK) { + goto failed; + } + + break; + } + + index = 0; + state = sw_duplicate; + break; + + case sw_inr_name_index: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x3f; + state = sw_inr_value_length; + break; + + case sw_inr_value_length: + + huffman = (v & 0x80) ? 1 : 0; + value.len = v & 0x7f; + + if (value.len == 0) { + value.data = NULL; + + if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK) + { + goto failed; + } + + state = sw_start; + break; + } + + if (value.len != 0x7f) { + value.data = ngx_pnalloc(pc->pool, value.len); + if (value.data == NULL) { + goto failed; + } + + state = sw_inr_value; + offset = 0; + break; + } + + value.len = 0; + state = sw_inr_read_value_length; + break; + + case sw_inr_read_value_length: + + value.len = (value.len << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + value.len += 0x7f; + + value.data = ngx_pnalloc(pc->pool, value.len); + if (value.data == NULL) { + goto failed; + } + + state = sw_inr_value; + offset = 0; + break; + + case sw_inr_value: + + value.data[offset++] = v; + if (offset != value.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) { + goto failed; + } + } + + if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + + case sw_iwnr_name_length: + + name.len = (name.len << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + name.len += 0x1f; + + name.data = ngx_pnalloc(pc->pool, name.len); + if (name.data == NULL) { + goto failed; + } + + offset = 0; + state = sw_iwnr_name; + break; + + case sw_iwnr_name: + + name.data[offset++] = v; + if (offset != name.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(pc, &name) != NGX_OK) { + goto failed; + } + } + + state = sw_iwnr_value_length; + break; + + case sw_iwnr_value_length: + + huffman = (v & 0x80) ? 1 : 0; + value.len = v & 0x7f; + + if (value.len == 0) { + value.data = NULL; + + if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + } + + if (value.len != 0x7f) { + value.data = ngx_pnalloc(pc->pool, value.len); + if (value.data == NULL) { + goto failed; + } + + offset = 0; + state = sw_iwnr_value; + break; + } + + state = sw_iwnr_read_value_length; + break; + + case sw_iwnr_read_value_length: + + value.len = (value.len << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + value.data = ngx_pnalloc(pc->pool, value.len); + if (value.data == NULL) { + goto failed; + } + + offset = 0; + state = sw_iwnr_value; + break; + + case sw_iwnr_value: + + value.data[offset++] = v; + if (offset != value.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) { + goto failed; + } + } + + if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + + + case sw_capacity: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x1f; + + if (ngx_http_v3_set_capacity(c, index) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + + case sw_duplicate: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x1f; + + if (ngx_http_v3_duplicate(c, index) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + } + } + + st->state = state; + st->dynamic = dynamic; + st->huffman = huffman; + st->index = index; + st->offset = offset; + st->name = name; + st->value = value; + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + goto failed; + } + + return; + +failed: + + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_client_decoder_handler(ngx_event_t *rev) +{ + u_char v; + ssize_t n; + ngx_uint_t index; + ngx_connection_t *c; + ngx_http_v3_uni_stream_t *st; + enum { + sw_start = 0, + sw_ack_header, + sw_cancel_stream, + sw_inc_insert_count + } state; + + c = rev->data; + st = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client decoder"); + + state = st->state; + index = st->index; + + while (rev->ready) { + + /* XXX limit checks */ + /* XXX buffer input */ + + n = c->recv(c, &v, 1); + + if (n == NGX_ERROR || n == 0) { + goto failed; + } + + if (n != 1) { + break; + } + + switch (state) { + + case sw_start: + + if (v & 0x80) { + /* Header Acknowledgement */ + + index = v & 0x7f; + + if (index != 0x7f) { + if (ngx_http_v3_ack_header(c, index) != NGX_OK) { + goto failed; + } + + break; + } + + index = 0; + state = sw_ack_header; + break; + } + + if (v & 0x40) { + /* Stream Cancellation */ + + index = v & 0x3f; + + if (index != 0x3f) { + if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) { + goto failed; + } + + break; + } + + index = 0; + state = sw_cancel_stream; + break; + } + + /* Insert Count Increment */ + + index = v & 0x3f; + + if (index != 0x3f) { + if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) { + goto failed; + } + + break; + } + + index = 0; + state = sw_inc_insert_count; + break; + + case sw_ack_header: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x7f; + + if (ngx_http_v3_ack_header(c, index) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + + case sw_cancel_stream: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x3f; + + if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + + case sw_inc_insert_count: + + index = (index << 7) + (v & 0x7f); + if (v & 0x80) { + break; + } + + index += 0x3f; + + if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) { + goto failed; + } + + state = sw_start; + break; + } + } + + st->state = state; + st->index = index; + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + goto failed; + } + + return; + +failed: + + ngx_http_v3_close_uni_stream(c); +} + + +/* XXX async & buffered stream writes */ + +static ngx_connection_t * +ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type) +{ + u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; + size_t n; + ngx_connection_t *sc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_uni_stream_t *us; + + sc = ngx_quic_create_uni_stream(c->qs->parent); + if (sc == NULL) { + return NULL; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create uni stream, type:%ui", type); + + us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + goto failed; + } + + us->signature = NGX_HTTP_V3_STREAM; + us->type = type; + sc->data = us; + + cln = ngx_pool_cleanup_add(sc->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_v3_uni_stream_cleanup; + cln->data = sc; + + n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + return sc; + +failed: + + ngx_http_v3_close_uni_stream(sc); + + return NULL; +} + + +static ngx_connection_t * +ngx_http_v3_get_server_encoder(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + + if (h3c->server_encoder == NULL) { + h3c->server_encoder = ngx_http_v3_create_uni_stream(c, + NGX_HTTP_V3_ENCODER_STREAM); + } + + return h3c->server_encoder; +} + + +static ngx_connection_t * +ngx_http_v3_get_server_decoder(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + + if (h3c->server_decoder == NULL) { + h3c->server_decoder = ngx_http_v3_create_uni_stream(c, + NGX_HTTP_V3_DECODER_STREAM); + } + + return h3c->server_decoder; +} + + +ngx_int_t +ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + u_char *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2]; + size_t n; + ngx_connection_t *ec; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client ref insert, %s[%ui] \"%V\"", + dynamic ? "dynamic" : "static", index, value); + + ec = ngx_http_v3_get_server_encoder(c); + if (ec == NULL) { + return NGX_ERROR; + } + + p = buf; + + *p = (dynamic ? 0x80 : 0xc0); + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 6); + + /* XXX option for huffman? */ + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + n = p - buf; + + if (ec->send(ec, buf, n) != (ssize_t) n) { + goto failed; + } + + if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_http_v3_close_uni_stream(ec); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *ec; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client insert \"%V\":\"%V\"", name, value); + + ec = ngx_http_v3_get_server_encoder(c); + if (ec == NULL) { + return NGX_ERROR; + } + + /* XXX option for huffman? */ + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, name->len, 5) - buf; + + if (ec->send(ec, buf, n) != (ssize_t) n) { + goto failed; + } + + if (ec->send(ec, name->data, name->len) != (ssize_t) name->len) { + goto failed; + } + + /* XXX option for huffman? */ + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, value->len, 7) - buf; + + if (ec->send(ec, buf, n) != (ssize_t) n) { + goto failed; + } + + if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_http_v3_close_uni_stream(ec); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *ec; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client set capacity %ui", capacity); + + ec = ngx_http_v3_get_server_encoder(c); + if (ec == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x20; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, capacity, 5) - buf; + + if (ec->send(ec, buf, n) != (ssize_t) n) { + ngx_http_v3_close_uni_stream(ec); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *ec; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client duplicate %ui", index); + + ec = ngx_http_v3_get_server_encoder(c); + if (ec == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, index, 5) - buf; + + if (ec->send(ec, buf, n) != (ssize_t) n) { + ngx_http_v3_close_uni_stream(ec); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client ack header %ui", stream_id); + + dc = ngx_http_v3_get_server_decoder(c); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x80; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + ngx_http_v3_close_uni_stream(dc); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client cancel stream %ui", stream_id); + + dc = ngx_http_v3_get_server_decoder(c); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + ngx_http_v3_close_uni_stream(dc); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client increment insert count %ui", inc); + + dc = ngx_http_v3_get_server_decoder(c); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + ngx_http_v3_close_uni_stream(dc); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c new file mode 100644 index 000000000..1c1d8c051 --- /dev/null +++ b/src/http/v3/ngx_http_v3_tables.c @@ -0,0 +1,385 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static ngx_array_t *ngx_http_v3_get_dynamic_table(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c); + + +static ngx_http_v3_header_t ngx_http_v3_static_table[] = { + + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string("age"), ngx_string("0") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("0") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string(":method"), ngx_string("CONNECT") }, + { ngx_string(":method"), ngx_string("DELETE") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("HEAD") }, + { ngx_string(":method"), ngx_string("OPTIONS") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":method"), ngx_string("PUT") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("103") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("503") }, + { ngx_string("accept"), ngx_string("*/*") }, + { ngx_string("accept"), + ngx_string("application/dns-message ") }, + { ngx_string("accept-encoding"), ngx_string("gzip,") }, + { ngx_string("accept-ranges"), ngx_string("bytes") }, + { ngx_string("access-control-allow-headers"), + ngx_string("cache-control") }, + { ngx_string("access-control-allow-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-allow-origin"), + ngx_string("*") }, + { ngx_string("cache-control"), ngx_string("max-age=0") }, + { ngx_string("cache-control"), ngx_string("max-age=2592000") }, + { ngx_string("cache-control"), ngx_string("max-age=604800") }, + { ngx_string("cache-control"), ngx_string("no-cache") }, + { ngx_string("cache-control"), ngx_string("no-store") }, + { ngx_string("cache-control"), + ngx_string("public, max-age=31536000 ") }, + { ngx_string("content-encoding"), ngx_string("br") }, + { ngx_string("content-encoding"), ngx_string("gzip") }, + { ngx_string("content-type"), + ngx_string("application/dns-message") }, + { ngx_string("content-type"), + ngx_string("application/javascript") }, + { ngx_string("content-type"), ngx_string("application/json") }, + { ngx_string("content-type"), + ngx_string("application/x-www-form-urlencoded") }, + { ngx_string("content-type"), ngx_string("image/gif") }, + { ngx_string("content-type"), ngx_string("image/jpeg") }, + { ngx_string("content-type"), ngx_string("image/png") }, + { ngx_string("content-type"), ngx_string("text/css") }, + { ngx_string("content-type"), + ngx_string("text/html;charset=utf-8") }, + { ngx_string("content-type"), ngx_string("text/plain") }, + { ngx_string("content-type"), + ngx_string("text/plain;charset=utf-8") }, + { ngx_string("range"), ngx_string("bytes=0-") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains;preload") }, + { ngx_string("vary"), ngx_string("accept-encoding") }, + { ngx_string("vary"), ngx_string("origin") }, + { ngx_string("x-content-type-options"),ngx_string("nosniff") }, + { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, + { ngx_string(":status"), ngx_string("100") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("302") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("403") }, + { ngx_string(":status"), ngx_string("421") }, + { ngx_string(":status"), ngx_string("425") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("FALSE") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("TRUE") }, + { ngx_string("access-control-allow-headers"), + ngx_string("*") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get, post, options") }, + { ngx_string("access-control-allow-methods"), + ngx_string("options") }, + { ngx_string("access-control-expose-headers"), + ngx_string("content-length") }, + { ngx_string("access-control-request-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-request-method"), + ngx_string("get") }, + { ngx_string("access-control-request-method"), + ngx_string("post") }, + { ngx_string("alt-svc"), ngx_string("clear") }, + { ngx_string("horization"), ngx_string("") }, + { ngx_string("content-security-policy"), + ngx_string("script-src") }, + { ngx_string("early-data"), ngx_string("1") }, + { ngx_string("expect-ct"), ngx_string("") }, + { ngx_string("forwarded"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("origin"), ngx_string("") }, + { ngx_string("purpose"), ngx_string("prefetch") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("timing-allow-origin"), ngx_string("*") }, + { ngx_string("upgrade-insecure-requests"), + ngx_string("1") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("x-forwarded-for"), ngx_string("") }, + { ngx_string("x-frame-options"), ngx_string("deny") }, + { ngx_string("x-frame-options"), ngx_string("sameorigin") } +}; + + +ngx_int_t +ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + ngx_array_t *dt; + ngx_http_v3_header_t *ref, *h; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert %s[$ui] \"%V\"", + dynamic ? "dynamic" : "static", index, value); + + ref = ngx_http_v3_lookup_table(c, dynamic, index); + if (ref == NULL) { + return NGX_ERROR; + } + + dt = ngx_http_v3_get_dynamic_table(c); + if (dt == NULL) { + return NGX_ERROR; + } + + h = ngx_array_push(dt); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = ref->name; + h->value = *value; + + if (ngx_http_v3_new_header(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_array_t *dt; + ngx_http_v3_header_t *h; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert \"%V\":\"%V\"", name, value); + + dt = ngx_http_v3_get_dynamic_table(c); + if (dt == NULL) { + return NGX_ERROR; + } + + h = ngx_array_push(dt); + if (h == NULL) { + return NGX_ERROR; + } + + h->name = *name; + h->value = *value; + + if (ngx_http_v3_new_header(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 set capacity %ui", capacity); + + /* XXX ignore capacity */ + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_array_t *dt; + ngx_http_v3_header_t *ref, *h; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + ref = ngx_http_v3_lookup_table(c, 1, index); + if (ref == NULL) { + return NGX_ERROR; + } + + dt = ngx_http_v3_get_dynamic_table(c); + if (dt == NULL) { + return NGX_ERROR; + } + + h = ngx_array_push(dt); + if (h == NULL) { + return NGX_ERROR; + } + + *h = *ref; + + if (ngx_http_v3_new_header(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ack header %ui", stream_id); + + /* XXX */ + + return NGX_OK; +} + + +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, + "http3 cancel stream %ui", stream_id); + + /* XXX */ + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 increment insert count %ui", inc); + + /* XXX */ + + return NGX_OK; +} + + +static ngx_array_t * +ngx_http_v3_get_dynamic_table(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_http_v3_connection_t *h3c; + + pc = c->qs->parent; + h3c = pc->data; + + if (h3c->dynamic) { + return h3c->dynamic; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create dynamic table"); + + h3c->dynamic = ngx_array_create(pc->pool, 1, sizeof(ngx_http_v3_header_t)); + + return h3c->dynamic; +} + + +ngx_http_v3_header_t * +ngx_http_v3_lookup_table(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index) +{ + ngx_uint_t nelts; + ngx_array_t *dt; + ngx_http_v3_header_t *table; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup %s[%ui]", + dynamic ? "dynamic" : "static", index); + + if (dynamic) { + dt = ngx_http_v3_get_dynamic_table(c); + if (dt == NULL) { + return NULL; + } + + table = dt->elts; + nelts = dt->nelts; + + } else { + table = ngx_http_v3_static_table; + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); + } + + if (index >= nelts) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 lookup out of bounds: %ui", nelts); + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup \"%V\":\"%V\"", + &table[index].name, &table[index].value); + + return &table[index]; +} + + +ngx_int_t +ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) +{ + size_t n; + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + n = h3c->dynamic ? h3c->dynamic->nelts : 0; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 check insert count %ui/%ui", insert_count, n); + + if (n < insert_count) { + /* XXX how to get notified? */ + /* XXX wake all streams on any arrival to the encoder stream? */ + return NGX_AGAIN; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_new_header(ngx_connection_t *c) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header"); + + /* XXX report all waiting streams of a new header */ + + return NGX_OK; +} -- cgit v1.2.3 From 8f35d300ed0abfd1a0988181de182fe042a6b4e2 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 13 Mar 2020 18:55:58 +0300 Subject: Added check for initialized c->ssl before calling SSL shutdown. --- src/event/ngx_event_quic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 248cc9087..f290cbe95 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -478,7 +478,9 @@ ngx_quic_close_connection(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection: %d", c->fd); - (void) ngx_ssl_shutdown(c); + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); -- cgit v1.2.3 From 11dfc1c9439a79881c2d2e73ab1e2c5c71f88499 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 20:44:32 +0300 Subject: Fixed sanitizer errors. --- src/core/ngx_connection.c | 12 ++++++------ src/event/ngx_event_quic.c | 9 +++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 33682532a..9ec1cd7ac 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1178,11 +1178,6 @@ ngx_close_connection(ngx_connection_t *c) ngx_uint_t log_error, level; ngx_socket_t fd; - if (c->fd == (ngx_socket_t) -1) { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed"); - return; - } - if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1191,7 +1186,7 @@ ngx_close_connection(ngx_connection_t *c) ngx_del_timer(c->write); } - if (!c->shared) { + if (!c->shared && c->fd != (ngx_socket_t) -1) { if (ngx_del_conn) { ngx_del_conn(c, NGX_CLOSE_EVENT); @@ -1223,6 +1218,11 @@ ngx_close_connection(ngx_connection_t *c) ngx_free_connection(c); + if (c->fd == (ngx_socket_t) -1) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "connection has no fd"); + return; + } + fd = c->fd; c->fd = (ngx_socket_t) -1; diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f290cbe95..7819e3f51 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1934,6 +1934,7 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) { u_char *p; + ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1942,8 +1943,8 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); qs = c->qs; - qc = qs->parent->quic; - + pc = qs->parent; + qc = pc->quic; // XXX: get direct pointer from stream structure? sn = ngx_quic_stream_lookup(&qc->stree, qs->id); @@ -1952,12 +1953,12 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return 0; } - p = ngx_pnalloc(c->pool, size); + p = ngx_pnalloc(pc->pool, size); if (p == NULL) { return 0; } -- cgit v1.2.3 From 1ac31c01b4a2df79a512a7b6bde67002b3e5259e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 14 Mar 2020 03:15:09 +0300 Subject: Fixed header protection application with pn length > 1. --- src/event/ngx_event_quic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7819e3f51..199284162 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1637,7 +1637,10 @@ ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_memcpy(ad.data, pkt->data, ad.len); ad.data[0] = clearflags; - ad.data[ad.len - pnl] = (u_char) pn; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); nonce[11] ^= pn; -- cgit v1.2.3 From 5399670fcc51b440f00a9a584654f7bcc52d3f88 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 14 Mar 2020 13:18:55 +0300 Subject: Temporary fix for header null-termination in HTTP/3. --- src/http/ngx_http_request.c | 4 ++-- src/http/v3/ngx_http_v3_request.c | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ef305faf4..85b090d5f 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1547,11 +1547,11 @@ ngx_http_process_request_headers(ngx_event_t *rev) h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; - //h->key.data[h->key.len] = '\0'; + h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; - //h->value.data[h->value.len] = '\0'; + h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b34eed98e..9cb351c2d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -518,6 +518,18 @@ done: } } + /* XXX ugly reallocation for the trailing '\0' */ + + p = ngx_pnalloc(c->pool, name.len + value.len + 2); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, name.data, name.len); + name.data = p; + ngx_memcpy(p + name.len + 1, value.data, value.len); + value.data = p + name.len + 1; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header \"%V\":\"%V\"", &name, &value); -- cgit v1.2.3 From 0d8984083b2296af349c412985ff8f4a0a1b7686 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 16 Mar 2020 13:06:43 +0300 Subject: Added processing of CONNECTION CLOSE frames. Contents is parsed and debug is output. No actions are taken. --- src/event/ngx_event_quic.c | 76 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 199284162..7227f4500 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -129,12 +129,48 @@ do { \ #define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1d // XXX #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1e - #define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) #define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) #define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) +#define NGX_QUIC_ERR_NO_ERROR 0x0 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x1 +#define NGX_QUIC_ERR_SERVER_BUSY 0x2 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x3 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x4 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x5 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x6 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x7 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x8 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x9 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0xA +#define NGX_QUIC_ERR_INVALID_TOKEN 0xB +/* 0xC is not defined */ +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0xD +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 + +#define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR + +/* literal errors indexed by corresponding value */ +static char *ngx_quic_errors[] = { + "NO_ERROR", + "INTERNAL_ERROR", + "SERVER_BUSY", + "FLOW_CONTROL_ERROR", + "STREAM_LIMIT_ERROR", + "STREAM_STATE_ERROR", + "FINAL_SIZE_ERROR", + "FRAME_ENCODING_ERROR", + "TRANSPORT_PARAMETER_ERROR", + "CONNECTION_ID_LIMIT_ERROR", + "PROTOCOL_VIOLATION", + "INVALID_TOKEN", + "", + "CRYPTO_BUFFER_EXCEEDED", + "CRYPTO_ERROR", +}; + /* TODO: real states, these are stubs */ typedef enum { @@ -198,6 +234,13 @@ typedef struct { } ngx_quic_stream_frame_t; +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; +} ngx_quic_close_frame_t; + + struct ngx_quic_frame_s { ngx_uint_t type; ngx_quic_level_t level; @@ -207,6 +250,7 @@ struct ngx_quic_frame_s { ngx_quic_ack_frame_t ack; ngx_quic_ncid_t ncid; ngx_quic_stream_frame_t stream; + ngx_quic_close_frame_t close; // more frames } u; @@ -1738,10 +1782,17 @@ ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, break; case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame => NGX_ERROR"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame"); - // TODO: parse connection close here - return NGX_ERROR; + frame->u.close.error_code = ngx_quic_parse_int(&p); + frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC + frame->u.close.reason.len = ngx_quic_parse_int(&p); + frame->u.close.reason.data = p; + p += frame->u.close.reason.len; + + if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) { + frame->u.close.error_code = NGX_QUIC_ERR_LAST; + } break; case NGX_QUIC_FT_STREAM0: @@ -2037,7 +2088,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ssize_t len; ngx_buf_t *b; ngx_log_t *log; - ngx_uint_t ack_this; + ngx_uint_t ack_this, do_close; ngx_pool_t *pool; ngx_event_t *rev, *wev; ngx_quic_frame_t frame, *ack_frame; @@ -2050,6 +2101,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) end = p + pkt->payload.len; ack_this = 0; + do_close = 0; while (p < end) { @@ -2108,6 +2160,17 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.ncid.len); continue; + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + ngx_quic_errors[frame.u.close.error_code], + frame.u.close.error_code, + frame.u.close.frame_type, + &frame.u.close.reason); + + do_close = 1; + break; + case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: @@ -2230,6 +2293,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + if (do_close) { + // TODO: handle stream close + } if (ack_this == 0) { /* do not ack packets with ACKs and PADDING */ -- cgit v1.2.3 From d0ebfa4cb97dc212aac4cde740286bc4e4cde30e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 16 Mar 2020 19:00:47 +0300 Subject: Split transport and crypto parts into separate files. New files: src/event/ngx_event_quic_protection.h src/event/ngx_event_quic_protection.c The protection.h header provides interface to the crypto part of the QUIC: 2 functions to initialize corresponding secrets: ngx_quic_set_initial_secret() ngx_quic_set_encryption_secret() and 2 functions to deal with packet processing: ngx_quic_encrypt() ngx_quic_decrypt() Also, structures representing secrets are defined there. All functions require SSL connection and a pool, only crypto operations inside, no access to nginx connections or events. Currently pool->log is used for the logging (instead of original c->log). --- auto/modules | 6 +- src/core/ngx_core.h | 1 + src/event/ngx_event_quic.c | 1303 ++------------------------------- src/event/ngx_event_quic.h | 114 ++- src/event/ngx_event_quic_protection.c | 987 +++++++++++++++++++++++++ src/event/ngx_event_quic_protection.h | 46 ++ 6 files changed, 1222 insertions(+), 1235 deletions(-) create mode 100644 src/event/ngx_event_quic_protection.c create mode 100644 src/event/ngx_event_quic_protection.h diff --git a/auto/modules b/auto/modules index 0232fba5e..7e7affb2e 100644 --- a/auto/modules +++ b/auto/modules @@ -1264,10 +1264,12 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_name=ngx_openssl_module ngx_module_incs= ngx_module_deps="src/event/ngx_event_openssl.h \ - src/event/ngx_event_quic.h" + src/event/ngx_event_quic.h \ + src/event/ngx_event_quic_protection.h" ngx_module_srcs="src/event/ngx_event_openssl.c src/event/ngx_event_openssl_stapling.c - src/event/ngx_event_quic.c" + src/event/ngx_event_quic.c + src/event/ngx_event_quic_protection.c" ngx_module_libs= ngx_module_link=YES ngx_module_order= diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 4594b54fd..a5f7b7af5 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -85,6 +85,7 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #if (NGX_OPENSSL) #include #include +#include #endif #include #include diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7227f4500..6114b6f18 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,93 +9,6 @@ #include -#define quic_version 0xff000018 - -#define NGX_AES_128_GCM_SHA256 0x1301 -#define NGX_AES_256_GCM_SHA384 0x1302 -#define NGX_CHACHA20_POLY1305_SHA256 0x1303 - -#define NGX_QUIC_IV_LEN 12 - -#ifdef OPENSSL_IS_BORINGSSL -#define ngx_quic_cipher_t EVP_AEAD -#else -#define ngx_quic_cipher_t EVP_CIPHER -#endif - - -#if (NGX_HAVE_NONALIGNED) - -#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) -#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) - -#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned -#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned - -#else - -#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) -#define ngx_quic_parse_uint32(p) \ - ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) - -#define ngx_quic_write_uint16(p, s) \ - ((p)[0] = (u_char) ((s) >> 8), \ - (p)[1] = (u_char) (s), \ - (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32(p, s) \ - ((p)[0] = (u_char) ((s) >> 24), \ - (p)[1] = (u_char) ((s) >> 16), \ - (p)[2] = (u_char) ((s) >> 8), \ - (p)[3] = (u_char) (s), \ - (p) + sizeof(uint32_t)) - -#endif - - -#define ngx_quic_write_uint16_aligned(p, s) \ - (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32_aligned(p, s) \ - (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) - -#define ngx_quic_varint_len(value) \ - ((value) <= 63 ? 1 : ((uint32_t)value) <= 16383 ? 2 : ((uint64_t)value) <= 1073741823 ? 4 : 8) - - -#if (NGX_DEBUG) - -#define ngx_quic_hexdump(log, fmt, data, len, ...) \ -do { \ - ngx_int_t m; \ - u_char buf[2048]; \ - \ - if (log->log_level & NGX_LOG_DEBUG_EVENT) { \ - m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \ - ngx_log_debug(NGX_LOG_DEBUG_EVENT, log, 0, \ - "%s: " fmt " %*s%s, len: %uz", \ - __FUNCTION__, __VA_ARGS__, m, buf, \ - len < 2048 ? "" : "...", len); \ - } \ -} while (0) - -#else - -#define ngx_quic_hexdump(log, fmt, data, len, ...) - -#endif - -#define ngx_quic_hexdump0(log, fmt, data, len) \ - ngx_quic_hexdump(log, fmt "%s", data, len, "") \ - - -/* 17.2. Long Header Packets */ - -#define NGX_QUIC_PKT_LONG 0x80 - -#define NGX_QUIC_PKT_INITIAL 0xc0 -#define NGX_QUIC_PKT_HANDSHAKE 0xe0 - /* 12.4. Frames and Frame Types */ #define NGX_QUIC_FT_PADDING 0x00 #define NGX_QUIC_FT_PING 0x01 @@ -180,21 +93,6 @@ typedef enum { } ngx_quic_state_t; -typedef struct { - ngx_str_t secret; - ngx_str_t key; - ngx_str_t iv; - ngx_str_t hp; -} ngx_quic_secret_t; - -typedef struct { - const ngx_quic_cipher_t *c; - const EVP_CIPHER *hp; - const EVP_MD *d; -} ngx_quic_ciphers_t; - -typedef enum ssl_encryption_level_t ngx_quic_level_t; - typedef struct ngx_quic_frame_s ngx_quic_frame_t; typedef struct { @@ -274,12 +172,7 @@ struct ngx_quic_connection_s { ngx_uint_t handshake_pn; ngx_uint_t appdata_pn; - ngx_quic_secret_t client_in; - ngx_quic_secret_t client_hs; - ngx_quic_secret_t client_ad; - ngx_quic_secret_t server_in; - ngx_quic_secret_t server_hs; - ngx_quic_secret_t server_ad; + ngx_quic_secrets_t secrets; /* streams */ ngx_rbtree_t stree; @@ -297,31 +190,8 @@ typedef struct { } ngx_quic_stream_node_t; -typedef struct { - ngx_quic_secret_t *secret; - ngx_uint_t type; - ngx_uint_t *number; - ngx_uint_t flags; - uint32_t version; - ngx_str_t token; - ngx_quic_level_t level; - - /* filled in by parser */ - ngx_buf_t *raw; /* udp datagram from wire */ - - u_char *data; /* quic packet */ - size_t len; - - /* cleartext fields */ - ngx_str_t dcid; - ngx_str_t scid; - - uint64_t pn; - - ngx_str_t payload; /* decrypted payload */ - -} ngx_quic_header_t; - +static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_header_t *pkt); @@ -353,12 +223,6 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, #endif static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); -static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c, - ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, - ngx_str_t *res); -static ngx_int_t ngx_quic_create_short_packet(ngx_connection_t *c, - ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, - ngx_str_t *res); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); @@ -371,35 +235,8 @@ static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c); -static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt); -static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); static uint64_t ngx_quic_parse_int(u_char **pos); -static void ngx_quic_build_int(u_char **pos, uint64_t value); - -static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, - const EVP_MD *digest, const u_char *secret, size_t secret_len, - const u_char *salt, size_t salt_len); -static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, - const EVP_MD *digest, const u_char *prk, size_t prk_len, - const u_char *info, size_t info_len); - -static ngx_int_t ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, - ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); - -static ngx_int_t ngx_quic_tls_open(ngx_connection_t *c, - const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, - u_char *nonce, ngx_str_t *in, ngx_str_t *ad); -static ngx_int_t ngx_quic_tls_seal(ngx_connection_t *c, - const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, - u_char *nonce, ngx_str_t *in, ngx_str_t *ad); - -static ngx_int_t ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in); - -static ngx_int_t ngx_quic_ciphers(ngx_connection_t *c, - ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -505,7 +342,7 @@ ngx_quic_handshake_handler(ngx_event_t *rev) b.last += n; - if (ngx_quic_input(c, NULL, &b) != NGX_OK) { + if (ngx_quic_input(c, &b) != NGX_OK) { ngx_quic_close_connection(c); return; } @@ -548,8 +385,8 @@ ngx_quic_create_uni_stream(ngx_connection_t *c) } -ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b) +static ngx_int_t +ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) { u_char *p; ngx_quic_header_t pkt; @@ -602,48 +439,37 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); pkt.level = level; + pkt.dcid = qc->dcid; + pkt.scid = qc->scid; if (level == ssl_encryption_initial) { pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->server_in; + pkt.secret = &qc->secrets.server.in; pkt.token = initial_token; - if (ngx_quic_create_long_packet(c, c->ssl->connection, - &pkt, payload, &res) - != NGX_OK) - { - return NGX_ERROR; - } - } else if (level == ssl_encryption_handshake) { pkt.number = &qc->handshake_pn; pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->server_hs; - - if (ngx_quic_create_long_packet(c, c->ssl->connection, - &pkt, payload, &res) - != NGX_OK) - { - return NGX_ERROR; - } + pkt.secret = &qc->secrets.server.hs; } else { pkt.number = &qc->appdata_pn; - pkt.secret = &qc->server_ad; + pkt.secret = &qc->secrets.server.ad; + } - if (ngx_quic_create_short_packet(c, c->ssl->connection, - &pkt, payload, &res) - != NGX_OK) - { - return NGX_ERROR; - } + if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) + != NGX_OK) + { + return NGX_ERROR; } ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); c->send(c, res.data, res.len); // TODO: err handling + (*pkt.number)++; + return NGX_OK; } @@ -840,7 +666,7 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, } -ngx_int_t +static ngx_int_t ngx_quic_output(ngx_connection_t *c) { size_t len; @@ -894,386 +720,68 @@ ngx_quic_output(ngx_connection_t *c) static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len) + const uint8_t *rsecret, size_t secret_len) { - ngx_int_t key_len; - ngx_uint_t i; - ngx_connection_t *c; - ngx_quic_secret_t *client; - ngx_quic_ciphers_t ciphers; + ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - ngx_quic_hexdump(c->log, "level:%d read", secret, secret_len, level); - - key_len = ngx_quic_ciphers(c, &ciphers, level); - - if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return 0; - } - - switch (level) { - - case ssl_encryption_handshake: - client = &c->quic->client_hs; - break; - - case ssl_encryption_application: - client = &c->quic->client_ad; - break; - - default: - return 0; - } - - client->key.len = key_len; - client->iv.len = NGX_QUIC_IV_LEN; - client->hp.len = key_len; - - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &client->key, secret }, - { ngx_string("tls13 quic iv"), &client->iv, secret }, - { ngx_string("tls13 quic hp"), &client->hp, secret }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { - return 0; - } - } + ngx_quic_hexdump(c->log, "level:%d read secret", + rsecret, secret_len, level); - return 1; + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + rsecret, secret_len, + &c->quic->secrets.client); } static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len) + const uint8_t *wsecret, size_t secret_len) { - ngx_int_t key_len; - ngx_uint_t i; - ngx_connection_t *c; - ngx_quic_secret_t *server; - ngx_quic_ciphers_t ciphers; + ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - ngx_quic_hexdump(c->log, "level:%d write", secret, secret_len, level); - - key_len = ngx_quic_ciphers(c, &ciphers, level); - - if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return 0; - } - - switch (level) { - - case ssl_encryption_handshake: - server = &c->quic->server_hs; - break; - - case ssl_encryption_application: - server = &c->quic->server_ad; - break; - - default: - return 0; - } - - server->key.len = key_len; - server->iv.len = NGX_QUIC_IV_LEN; - server->hp.len = key_len; - - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &server->key, secret }, - { ngx_string("tls13 quic iv"), &server->iv, secret }, - { ngx_string("tls13 quic hp"), &server->hp, secret }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { - return 0; - } - } + ngx_quic_hexdump(c->log, "level:%d write secret", + wsecret, secret_len, level); - return 1; + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + wsecret, secret_len, + &c->quic->secrets.server); } #else static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *read_secret, - const uint8_t *write_secret, size_t secret_len) + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) { - ngx_int_t key_len; - ngx_uint_t i; - ngx_connection_t *c; - ngx_quic_secret_t *client, *server; - ngx_quic_ciphers_t ciphers; + ngx_int_t rc; + ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - ngx_quic_hexdump(c->log, "level:%d read", read_secret, secret_len, level); - ngx_quic_hexdump(c->log, "level:%d write", write_secret, secret_len, level); - - key_len = ngx_quic_ciphers(c, &ciphers, level); - - if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); - return 0; - } - - switch (level) { - - case ssl_encryption_handshake: - client = &c->quic->client_hs; - server = &c->quic->server_hs; - - break; - - case ssl_encryption_application: - client = &c->quic->client_ad; - server = &c->quic->server_ad; - - break; - - default: - return 0; - } - - client->key.len = key_len; - server->key.len = key_len; - - client->iv.len = NGX_QUIC_IV_LEN; - server->iv.len = NGX_QUIC_IV_LEN; + ngx_quic_hexdump(c->log, "level:%d read", recret, secret_len, level); + ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); - client->hp.len = key_len; - server->hp.len = key_len; - - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &client->key, read_secret }, - { ngx_string("tls13 quic iv"), &client->iv, read_secret }, - { ngx_string("tls13 quic hp"), &client->hp, read_secret }, - { ngx_string("tls13 quic key"), &server->key, write_secret }, - { ngx_string("tls13 quic iv"), &server->iv, write_secret }, - { ngx_string("tls13 quic hp"), &server->hp, write_secret }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { - return 0; - } + rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + rsecret, secret_len, + &c->quic->secrets.client); + if (rc != 1) { + return rc; } - return 1; + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + wsecret, secret_len, + &c->quic->secrets.server); } #endif -static ngx_int_t -ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) -{ - u_char *p, *pnp, *nonce, *sample, *packet; - ngx_str_t ad, out; - ngx_quic_ciphers_t ciphers; - ngx_quic_connection_t *qc; - u_char mask[16]; - - qc = c->quic; - - out.len = payload->len + EVP_GCM_TLS_TAG_LEN; - - ad.data = ngx_alloc(346 /*max header*/, c->log); - if (ad.data == 0) { - return NGX_ERROR; - } - - p = ad.data; - - *p++ = pkt->flags; - - p = ngx_quic_write_uint32(p, quic_version); - - *p++ = qc->scid.len; - p = ngx_cpymem(p, qc->scid.data, qc->scid.len); - - *p++ = qc->dcid.len; - p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len); - - if (pkt->level == ssl_encryption_initial) { - ngx_quic_build_int(&p, pkt->token.len); - } - - ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) - pnp = p; - - *p++ = (*pkt->number)++; - - ad.len = p - ad.data; - - ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - - if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { - return NGX_ERROR; - } - - nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); - if (pkt->level == ssl_encryption_handshake) { - nonce[11] ^= (*pkt->number - 1); - } - - ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - - if (ngx_quic_tls_seal(c, ciphers.c, pkt->secret, &out, nonce, payload, &ad) - != NGX_OK) - { - return NGX_ERROR; - } - - sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { - return NGX_ERROR; - } - - ngx_quic_hexdump0(c->log, "sample", sample, 16); - ngx_quic_hexdump0(c->log, "mask", mask, 16); - ngx_quic_hexdump0(c->log, "hp_key", pkt->secret->hp.data, 16); - - // header protection, pnl = 0 - ad.data[0] ^= mask[0] & 0x0f; - *pnp ^= mask[1]; - - packet = ngx_alloc(ad.len + out.len, c->log); - if (packet == 0) { - return NGX_ERROR; - } - - p = ngx_cpymem(packet, ad.data, ad.len); - p = ngx_cpymem(p, out.data, out.len); - - res->data = packet; - res->len = p - packet; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) -{ - u_char *p, *pnp, *nonce, *sample, *packet; - ngx_str_t ad, out; - ngx_quic_ciphers_t ciphers; - ngx_quic_connection_t *qc; - u_char mask[16]; - - qc = c->quic; - - out.len = payload->len + EVP_GCM_TLS_TAG_LEN; - - ad.data = ngx_alloc(25 /*max header*/, c->log); - if (ad.data == 0) { - return NGX_ERROR; - } - - p = ad.data; - - *p++ = 0x40; - - p = ngx_cpymem(p, qc->scid.data, qc->scid.len); - - pnp = p; - - *p++ = (*pkt->number)++; - - ad.len = p - ad.data; - - ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - - if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { - return NGX_ERROR; - } - - nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); - if (pkt->level == ssl_encryption_handshake - || pkt->level == ssl_encryption_application) - { - nonce[11] ^= (*pkt->number - 1); - } - - ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - - if (ngx_quic_tls_seal(c, ciphers.c, pkt->secret, &out, nonce, payload, &ad) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_quic_hexdump0(c->log, "out", out.data, out.len); - - sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { - return NGX_ERROR; - } - - ngx_quic_hexdump0(c->log, "sample", sample, 16); - ngx_quic_hexdump0(c->log, "mask", mask, 16); - ngx_quic_hexdump0(c->log, "hp_key", pkt->secret->hp.data, 16); - - // header protection, pnl = 0 - ad.data[0] ^= mask[0] & 0x1f; - *pnp ^= mask[1]; - - packet = ngx_alloc(ad.len + out.len, c->log); - if (packet == 0) { - return NGX_ERROR; - } - - p = ngx_cpymem(packet, ad.data, ad.len); - p = ngx_cpymem(p, out.data, out.len); - - ngx_quic_hexdump0(c->log, "packet", packet, p - packet); - - res->data = packet; - res->len = p - packet; - - return NGX_OK; -} - - static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { @@ -1503,227 +1011,28 @@ ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static ngx_int_t -ngx_quic_initial_secret(ngx_connection_t *c) +ssize_t +ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, + ngx_quic_frame_t *frame) { - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; - ngx_quic_connection_t *qc; - - static const uint8_t salt[20] = - "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" - "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; + u_char *p; - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + size_t npad; - cipher = EVP_aes_128_gcm(); - digest = EVP_sha256(); + p = start; - qc = c->quic; + frame->type = *p++; // TODO: check overflow (p < end) - if (ngx_hkdf_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, - salt, sizeof(salt)) - != NGX_OK) - { - return NGX_ERROR; - } + switch (frame->type) { - ngx_str_t iss = { - .data = is, - .len = is_len - }; - - ngx_quic_hexdump0(c->log, "salt", salt, sizeof(salt)); - ngx_quic_hexdump0(c->log, "initial secret", is, is_len); - - /* draft-ietf-quic-tls-23#section-5.2 */ - qc->client_in.secret.len = SHA256_DIGEST_LENGTH; - qc->server_in.secret.len = SHA256_DIGEST_LENGTH; - - qc->client_in.key.len = EVP_CIPHER_key_length(cipher); - qc->server_in.key.len = EVP_CIPHER_key_length(cipher); - - qc->client_in.hp.len = EVP_CIPHER_key_length(cipher); - qc->server_in.hp.len = EVP_CIPHER_key_length(cipher); - - qc->client_in.iv.len = EVP_CIPHER_iv_length(cipher); - qc->server_in.iv.len = EVP_CIPHER_iv_length(cipher); - - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *prk; - } seq[] = { - - /* draft-ietf-quic-tls-23#section-5.2 */ - { ngx_string("tls13 client in"), &qc->client_in.secret, &iss }, - { - ngx_string("tls13 quic key"), - &qc->client_in.key, - &qc->client_in.secret, - }, - { - ngx_string("tls13 quic iv"), - &qc->client_in.iv, - &qc->client_in.secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &qc->client_in.hp, - &qc->client_in.secret, - }, - { ngx_string("tls13 server in"), &qc->server_in.secret, &iss }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - ngx_string("tls13 quic key"), - &qc->server_in.key, - &qc->server_in.secret, - }, - { - ngx_string("tls13 quic iv"), - &qc->server_in.iv, - &qc->server_in.secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &qc->server_in.hp, - &qc->server_in.secret, - }, - - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c, digest, seq[i].key, &seq[i].label, - seq[i].prk->data, seq[i].prk->len) - != NGX_OK) - { - return NGX_ERROR; - } - } + case NGX_QUIC_FT_CRYPTO: + frame->u.crypto.offset = *p++; + frame->u.crypto.len = ngx_quic_parse_int(&p); + frame->u.crypto.data = p; + p += frame->u.crypto.len; - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char clearflags, *p, *sample; - uint8_t *nonce; - uint64_t pn; - ngx_int_t pnl, rc; - ngx_str_t in, ad; - ngx_quic_ciphers_t ciphers; - uint8_t mask[16]; - - if (ngx_quic_ciphers(c, &ciphers, pkt->level) == NGX_ERROR) { - return NGX_ERROR; - } - - p = pkt->raw->pos; - - /* draft-ietf-quic-tls-23#section-5.4.2: - * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.[34]: - * AES-Based and ChaCha20-Based header protections sample 16 bytes - */ - - sample = p + 4; - - ngx_quic_hexdump0(c->log, "sample", sample, 16); - - /* header protection */ - - if (ngx_quic_tls_hp(c, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { - return NGX_ERROR; - } - - if (pkt->flags & NGX_QUIC_PKT_LONG) { - clearflags = pkt->flags ^ (mask[0] & 0x0f); - - } else { - clearflags = pkt->flags ^ (mask[0] & 0x1f); - } - - pnl = (clearflags & 0x03) + 1; - pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); - - pkt->pn = pn; - - ngx_quic_hexdump0(c->log, "mask", mask, 5); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear flags: %xi", clearflags); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); - - /* packet protection */ - - in.data = p; - - if (pkt->flags & NGX_QUIC_PKT_LONG) { - in.len = pkt->len - pnl; - - } else { - in.len = pkt->data + pkt->len - p; - } - - ad.len = p - pkt->data; - ad.data = ngx_pnalloc(c->pool, ad.len); - if (ad.data == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(ad.data, pkt->data, ad.len); - ad.data[0] = clearflags; - - do { - ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; - } while (--pnl); - - nonce = ngx_pstrdup(c->pool, &pkt->secret->iv); - nonce[11] ^= pn; - - ngx_quic_hexdump0(c->log, "nonce", nonce, 12); - ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len); - - rc = ngx_quic_tls_open(c, ciphers.c, pkt->secret, &pkt->payload, - nonce, &in, &ad); - - ngx_quic_hexdump0(c->log, "packet payload", - pkt->payload.data, pkt->payload.len); - - return rc; -} - - -ssize_t -ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, - ngx_quic_frame_t *frame) -{ - u_char *p; - - size_t npad; - - p = start; - - frame->type = *p++; // TODO: check overflow (p < end) - - switch (frame->type) { - - case NGX_QUIC_FT_CRYPTO: - frame->u.crypto.offset = *p++; - frame->u.crypto.len = ngx_quic_parse_int(&p); - frame->u.crypto.data = p; - p += frame->u.crypto.len; - - ngx_quic_hexdump0(c->log, "CRYPTO frame", - frame->u.crypto.data, frame->u.crypto.len); + ngx_quic_hexdump0(c->log, "CRYPTO frame", + frame->u.crypto.data, frame->u.crypto.len); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic CRYPTO frame length: %uL off:%uL pp:%p", @@ -1891,7 +1200,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t -ngx_quic_init_connection(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_init_connection(ngx_connection_t *c) { int n, sslerr; ngx_ssl_conn_t *ssl_conn; @@ -2444,18 +1753,20 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); - if (ngx_quic_initial_secret(c) != NGX_OK) { + if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) + != NGX_OK) + { return NGX_ERROR; } - pkt->secret = &qc->client_in; + pkt->secret = &qc->secrets.client.in; pkt->level = ssl_encryption_initial; - if (ngx_quic_decrypt(c, pkt) != NGX_OK) { + if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { return NGX_ERROR; } - if (ngx_quic_init_connection(c, pkt) != NGX_OK) { + if (ngx_quic_init_connection(c) != NGX_OK) { return NGX_ERROR; } @@ -2507,10 +1818,10 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - pkt->secret = &qc->client_hs; + pkt->secret = &qc->secrets.client.hs; pkt->level = ssl_encryption_handshake; - if (ngx_quic_decrypt(c, pkt) != NGX_OK) { + if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { return NGX_ERROR; } @@ -2531,10 +1842,10 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - pkt->secret = &qc->client_ad; + pkt->secret = &qc->secrets.client.ad; pkt->level = ssl_encryption_application; - if (ngx_quic_decrypt(c, pkt) != NGX_OK) { + if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { return NGX_ERROR; } @@ -2591,471 +1902,3 @@ ngx_quic_build_int(u_char **pos, uint64_t value) // return len2; } - -static uint64_t -ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) -{ - u_char *p; - uint64_t value; - - p = *pos; - value = *p++ ^ *mask++; - - while (--len) { - value = (value << 8) + (*p++ ^ *mask++); - } - - *pos = p; - return value; -} - - -static ngx_int_t -ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, - const u_char *secret, size_t secret_len, const u_char *salt, - size_t salt_len) -{ -#ifdef OPENSSL_IS_BORINGSSL - if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, - salt_len) - == 0) - { - return NGX_ERROR; - } -#else - - EVP_PKEY_CTX *pctx; - - pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); - - if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { - return NGX_ERROR; - } - -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_hkdf_expand(ngx_connection_t *c, const EVP_MD *digest, ngx_str_t *out, - ngx_str_t *label, const uint8_t *prk, size_t prk_len) -{ - uint8_t *p; - size_t info_len; - uint8_t info[20]; - - out->data = ngx_pnalloc(c->pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } - - info_len = 2 + 1 + label->len + 1; - - info[0] = 0; - info[1] = out->len; - info[2] = label->len; - p = ngx_cpymem(&info[3], label->data, label->len); - *p = '\0'; - - if (ngx_hkdf_expand(out->data, out->len, digest, - prk, prk_len, info, info_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(%V) failed", label); - return NGX_ERROR; - } - - ngx_quic_hexdump(c->log, "%V info", info, info_len, label); - ngx_quic_hexdump(c->log, "%V key", out->data, out->len, label); - - return NGX_OK; -} - - -static ngx_int_t -ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, - const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) -{ -#ifdef OPENSSL_IS_BORINGSSL - if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) - == 0) - { - return NGX_ERROR; - } -#else - - EVP_PKEY_CTX *pctx; - - pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); - - if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { - return NGX_ERROR; - } - -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_open(ngx_connection_t *c, const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad) -{ - out->len = in->len - EVP_GCM_TLS_TAG_LEN; - out->data = ngx_pnalloc(c->pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *ctx; - - ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) - != 1) - { - EVP_AEAD_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_open() failed"); - return NGX_ERROR; - } - - EVP_AEAD_CTX_free(ctx); -#else - int len; - u_char *tag; - EVP_CIPHER_CTX *ctx; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - return NGX_ERROR; - } - - if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptUpdate() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, - in->len - EVP_GCM_TLS_TAG_LEN) - != 1) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptUpdate() failed"); - return NGX_ERROR; - } - - out->len = len; - tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); - return NGX_ERROR; - } - - if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_DecryptFinal_ex failed"); - return NGX_ERROR; - } - - out->len += len; - - EVP_CIPHER_CTX_free(ctx); -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_seal(ngx_connection_t *c, const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad) -{ - out->len = in->len + EVP_GCM_TLS_TAG_LEN; - out->data = ngx_pnalloc(c->pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *ctx; - - ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) - != 1) - { - EVP_AEAD_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_AEAD_CTX_seal() failed"); - return NGX_ERROR; - } - - EVP_AEAD_CTX_free(ctx); -#else - int len; - EVP_CIPHER_CTX *ctx; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_CIPHER_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); - return NGX_ERROR; - } - - out->len = len; - - if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_ex failed"); - return NGX_ERROR; - } - - out->len += len; - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, - out->data + in->len) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); - return NGX_ERROR; - } - - EVP_CIPHER_CTX_free(ctx); - - out->len += EVP_GCM_TLS_TAG_LEN; -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_hp(ngx_connection_t *c, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in) -{ - int outlen; - EVP_CIPHER_CTX *ctx; - u_char zero[5] = {0}; - -#ifdef OPENSSL_IS_BORINGSSL - uint32_t counter; - - ngx_memcpy(&counter, in, sizeof(uint32_t)); - - if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { - CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); - return NGX_OK; - } -#endif - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptInit_ex() failed"); - goto failed; - } - - if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptUpdate() failed"); - goto failed; - } - - if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "EVP_EncryptFinal_Ex() failed"); - goto failed; - } - - EVP_CIPHER_CTX_free(ctx); - - return NGX_OK; - -failed: - - EVP_CIPHER_CTX_free(ctx); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_ciphers(ngx_connection_t *c, ngx_quic_ciphers_t *ciphers, - enum ssl_encryption_level_t level) -{ - ngx_int_t id, len; - - if (level == ssl_encryption_initial) { - id = NGX_AES_128_GCM_SHA256; - - } else { - id = SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) - & 0xffff; - } - - switch (id) { - - case NGX_AES_128_GCM_SHA256: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_aes_128_gcm(); -#else - ciphers->c = EVP_aes_128_gcm(); -#endif - ciphers->hp = EVP_aes_128_ctr(); - ciphers->d = EVP_sha256(); - len = 16; - break; - - case NGX_AES_256_GCM_SHA384: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_aes_256_gcm(); -#else - ciphers->c = EVP_aes_256_gcm(); -#endif - ciphers->hp = EVP_aes_256_ctr(); - ciphers->d = EVP_sha384(); - len = 32; - break; - - case NGX_CHACHA20_POLY1305_SHA256: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_chacha20_poly1305(); -#else - ciphers->c = EVP_chacha20_poly1305(); -#endif -#ifdef OPENSSL_IS_BORINGSSL - ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); -#else - ciphers->hp = EVP_chacha20(); -#endif - ciphers->d = EVP_sha256(); - len = 32; - break; - - default: - return NGX_ERROR; - } - - return len; -} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 6a60c8703..faa35c9ab 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -10,6 +10,59 @@ #include + +#define quic_version 0xff000018 /* draft-24 */ + +/* 17.2. Long Header Packets */ + +#define NGX_QUIC_PKT_LONG 0x80 + +#define NGX_QUIC_PKT_INITIAL 0xc0 +#define NGX_QUIC_PKT_HANDSHAKE 0xe0 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_varint_len(value) \ + ((value) <= 63 ? 1 \ + : ((uint32_t) value) <= 16383 ? 2 \ + : ((uint64_t) value) <= 1073741823 ? 4 \ + : 8) + + struct ngx_quic_stream_s { uint64_t id; ngx_uint_t unidirectional:1; @@ -17,9 +70,35 @@ struct ngx_quic_stream_s { void *data; }; -/* TODO: get rid somehow of ssl argument? */ -ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b); -ngx_int_t ngx_quic_output(ngx_connection_t *c); +typedef struct ngx_quic_secret_s ngx_quic_secret_t; +typedef enum ssl_encryption_level_t ngx_quic_level_t; + +typedef struct { + ngx_quic_secret_t *secret; + ngx_uint_t type; + ngx_uint_t *number; + ngx_uint_t flags; + uint32_t version; + ngx_str_t token; + ngx_quic_level_t level; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram from wire */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t dcid; + ngx_str_t scid; + + uint64_t pn; + + ngx_str_t payload; /* decrypted payload */ + +} ngx_quic_header_t; + +void ngx_quic_build_int(u_char **pos, uint64_t value); void ngx_quic_init_ssl_methods(SSL_CTX* ctx); @@ -27,4 +106,33 @@ void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); + +/********************************* DEBUG *************************************/ + +#if (NGX_DEBUG) + +#define ngx_quic_hexdump(log, fmt, data, len, ...) \ +do { \ + ngx_int_t m; \ + u_char buf[2048]; \ + \ + if (log->log_level & NGX_LOG_DEBUG_EVENT) { \ + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \ + ngx_log_debug(NGX_LOG_DEBUG_EVENT, log, 0, \ + "%s: " fmt " %*s%s, len: %uz", \ + __FUNCTION__, __VA_ARGS__, m, buf, \ + len < 2048 ? "" : "...", len); \ + } \ +} while (0) + +#else + +#define ngx_quic_hexdump(log, fmt, data, len, ...) + +#endif + +#define ngx_quic_hexdump0(log, fmt, data, len) \ + ngx_quic_hexdump(log, fmt "%s", data, len, "") \ + + #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c new file mode 100644 index 000000000..544adaace --- /dev/null +++ b/src/event/ngx_event_quic_protection.c @@ -0,0 +1,987 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_QUIC_IV_LEN 12 + +#define NGX_AES_128_GCM_SHA256 0x1301 +#define NGX_AES_256_GCM_SHA384 0x1302 +#define NGX_CHACHA20_POLY1305_SHA256 0x1303 + + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#else +#define ngx_quic_cipher_t EVP_CIPHER +#endif + +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); +static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, + ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); + +static ngx_int_t ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad); +static ngx_int_t ngx_quic_tls_seal(ngx_pool_t *pool, + const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad); +static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in); +static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, + ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); + +static ngx_int_t ngx_quic_create_long_packet(ngx_pool_t *pool, + ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, + ngx_str_t *res); + +static ngx_int_t ngx_quic_create_short_packet(ngx_pool_t *pool, + ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, + ngx_str_t *res); + + +static ngx_int_t +ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level) +{ + ngx_int_t id, len; + + if (level == ssl_encryption_initial) { + id = NGX_AES_128_GCM_SHA256; + + } else { + id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff; + } + + switch (id) { + + case NGX_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case NGX_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + case NGX_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + + default: + return NGX_ERROR; + } + + return len; +} + + +ngx_int_t +ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secrets_t *qsec, + ngx_str_t *secret) +{ + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + + static const uint8_t salt[20] = + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" + "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + cipher = EVP_aes_128_gcm(); + digest = EVP_sha256(); + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_str_t iss = { + .data = is, + .len = is_len + }; + + ngx_quic_hexdump0(pool->log, "salt", salt, sizeof(salt)); + ngx_quic_hexdump0(pool->log, "initial secret", is, is_len); + + /* draft-ietf-quic-tls-23#section-5.2 */ + qsec->client.in.secret.len = SHA256_DIGEST_LENGTH; + qsec->server.in.secret.len = SHA256_DIGEST_LENGTH; + + qsec->client.in.key.len = EVP_CIPHER_key_length(cipher); + qsec->server.in.key.len = EVP_CIPHER_key_length(cipher); + + qsec->client.in.hp.len = EVP_CIPHER_key_length(cipher); + qsec->server.in.hp.len = EVP_CIPHER_key_length(cipher); + + qsec->client.in.iv.len = EVP_CIPHER_iv_length(cipher); + qsec->server.in.iv.len = EVP_CIPHER_iv_length(cipher); + + struct { + ngx_str_t label; + ngx_str_t *key; + ngx_str_t *prk; + } seq[] = { + + /* draft-ietf-quic-tls-23#section-5.2 */ + { ngx_string("tls13 client in"), &qsec->client.in.secret, &iss }, + { + ngx_string("tls13 quic key"), + &qsec->client.in.key, + &qsec->client.in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qsec->client.in.iv, + &qsec->client.in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qsec->client.in.hp, + &qsec->client.in.secret, + }, + { ngx_string("tls13 server in"), &qsec->server.in.secret, &iss }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + ngx_string("tls13 quic key"), + &qsec->server.in.key, + &qsec->server.in.secret, + }, + { + ngx_string("tls13 quic iv"), + &qsec->server.in.iv, + &qsec->server.in.secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &qsec->server.in.hp, + &qsec->server.in.secret, + }, + + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label, + seq[i].prk->data, seq[i].prk->len) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, + ngx_str_t *label, const uint8_t *prk, size_t prk_len) +{ + size_t info_len; + uint8_t *p; + uint8_t info[20]; + + out->data = ngx_pnalloc(pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + + info_len = 2 + 1 + label->len + 1; + + info[0] = 0; + info[1] = out->len; + info[2] = label->len; + p = ngx_cpymem(&info[3], label->data, label->len); + *p = '\0'; + + if (ngx_hkdf_expand(out->data, out->len, digest, + prk, prk_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, + "ngx_hkdf_expand(%V) failed", label); + return NGX_ERROR; + } + + ngx_quic_hexdump(pool->log, "%V info", info, info_len, label); + ngx_quic_hexdump(pool->log, "%V key", out->data, out->len, label); + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad) +{ + ngx_log_t *log; + + log = pool->log; // TODO: pass log ? + + out->len = in->len - EVP_GCM_TLS_TAG_LEN; + out->data = ngx_pnalloc(pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, + in->len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + EVP_CIPHER_CTX_free(ctx); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_seal(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad) +{ + ngx_log_t *log; + + log = pool->log; // TODO: pass log ? + + out->len = in->len + EVP_GCM_TLS_TAG_LEN; + out->data = ngx_pnalloc(pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, + out->data + in->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + return NGX_ERROR; + } + + EVP_CIPHER_CTX_free(ctx); + + out->len += EVP_GCM_TLS_TAG_LEN; +#endif + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[5] = {0}; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t counter; + + ngx_memcpy(&counter, in, sizeof(uint32_t)); + + if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); + return NGX_OK; + } +#endif + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + goto failed; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + goto failed; + } + + if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); + goto failed; + } + + EVP_CIPHER_CTX_free(ctx); + + return NGX_OK; + +failed: + + EVP_CIPHER_CTX_free(ctx); + + return NGX_ERROR; +} + + +int +ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *secret, + size_t secret_len, ngx_quic_peer_secrets_t *qsec) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + key_len = ngx_quic_ciphers(ssl_conn, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); + return 0; + } + + switch (level) { + + case ssl_encryption_handshake: + peer_secret= &qsec->hs; + break; + + case ssl_encryption_application: + peer_secret = &qsec->ad; + break; + + default: + return 0; + } + + peer_secret->key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + peer_secret->hp.len = key_len; + + struct { + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; + } seq[] = { + { ngx_string("tls13 quic key"), &peer_secret->key, secret }, + { ngx_string("tls13 quic iv"), &peer_secret->iv, secret }, + { ngx_string("tls13 quic hp"), &peer_secret->hp, secret }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + + +static ngx_int_t +ngx_quic_create_long_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +{ + u_char *p, *pnp, *nonce, *sample, *packet; + uint64_t pn; + ngx_log_t *log; + ngx_str_t ad, out; + ngx_quic_ciphers_t ciphers; + u_char mask[16]; + + log = pool->log; + + out.len = payload->len + EVP_GCM_TLS_TAG_LEN; + + ad.data = ngx_alloc(346 /*max header*/, log); + if (ad.data == 0) { + return NGX_ERROR; + } + + p = ad.data; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, quic_version); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, pkt->token.len); + } + + ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) + pnp = p; + + pn = *pkt->number; + + *p++ = pn; + + ad.len = p - ad.data; + + ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + + if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + nonce = ngx_pstrdup(pool, &pkt->secret->iv); + if (pkt->level == ssl_encryption_handshake) { + nonce[11] ^= pn; + } + + ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(log, "nonce", nonce, 12); + + if (ngx_quic_tls_seal(pool, ciphers.c, pkt->secret, &out, + nonce, payload, &ad) + != NGX_OK) + { + return NGX_ERROR; + } + + sample = &out.data[3]; // pnl=0 + if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_hexdump0(log, "sample", sample, 16); + ngx_quic_hexdump0(log, "mask", mask, 16); + ngx_quic_hexdump0(log, "hp_key", pkt->secret->hp.data, 16); + + // header protection, pnl = 0 + ad.data[0] ^= mask[0] & 0x0f; + *pnp ^= mask[1]; + + packet = ngx_alloc(ad.len + out.len, log); + if (packet == 0) { + return NGX_ERROR; + } + + p = ngx_cpymem(packet, ad.data, ad.len); + p = ngx_cpymem(p, out.data, out.len); + + res->data = packet; + res->len = p - packet; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_short_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +{ + u_char *p, *pnp, *nonce, *sample, *packet; + ngx_log_t *log; + ngx_str_t ad, out; + ngx_quic_ciphers_t ciphers; + u_char mask[16]; + + log = pool->log; + + out.len = payload->len + EVP_GCM_TLS_TAG_LEN; + + ad.data = ngx_alloc(25 /*max header*/, log); + if (ad.data == 0) { + return NGX_ERROR; + } + + p = ad.data; + + *p++ = 0x40; + + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + pnp = p; + + *p++ = (*pkt->number); + + ad.len = p - ad.data; + + ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + + if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + nonce = ngx_pstrdup(pool, &pkt->secret->iv); + if (pkt->level == ssl_encryption_handshake + || pkt->level == ssl_encryption_application) + { + nonce[11] ^= *pkt->number; + } + + ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(log, "nonce", nonce, 12); + + if (ngx_quic_tls_seal(pool, ciphers.c, pkt->secret, &out, + nonce, payload, &ad) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_hexdump0(log, "out", out.data, out.len); + + sample = &out.data[3]; // pnl=0 + if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_hexdump0(log, "sample", sample, 16); + ngx_quic_hexdump0(log, "mask", mask, 16); + ngx_quic_hexdump0(log, "hp_key", pkt->secret->hp.data, 16); + + // header protection, pnl = 0 + ad.data[0] ^= mask[0] & 0x1f; + *pnp ^= mask[1]; + + packet = ngx_alloc(ad.len + out.len, log); + if (packet == 0) { + return NGX_ERROR; + } + + p = ngx_cpymem(packet, ad.data, ad.len); + p = ngx_cpymem(p, out.data, out.len); + + ngx_quic_hexdump0(log, "packet", packet, p - packet); + + res->data = packet; + res->len = p - packet; + + return NGX_OK; +} + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) +{ + u_char *p; + uint64_t value; + + p = *pos; + value = *p++ ^ *mask++; + + while (--len) { + value = (value << 8) + (*p++ ^ *mask++); + } + + *pos = p; + return value; +} + + +ngx_int_t +ngx_quic_encrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +{ + if (pkt->level == ssl_encryption_application) { + return ngx_quic_create_short_packet(pool, ssl_conn, pkt, payload, res); + } + + return ngx_quic_create_long_packet(pool, ssl_conn, pkt, payload, res); +} + + +ngx_int_t +ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt) +{ + u_char clearflags, *p, *sample; + uint8_t *nonce; + uint64_t pn; + ngx_log_t *log; + ngx_int_t pnl, rc; + ngx_str_t in, ad; + ngx_quic_ciphers_t ciphers; + uint8_t mask[16]; + + log = pool->log; + + if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + p = pkt->raw->pos; + + /* draft-ietf-quic-tls-23#section-5.4.2: + * the Packet Number field is assumed to be 4 bytes long + * draft-ietf-quic-tls-23#section-5.4.[34]: + * AES-Based and ChaCha20-Based header protections sample 16 bytes + */ + + sample = p + 4; + + ngx_quic_hexdump0(log, "sample", sample, 16); + + /* header protection */ + + if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + return NGX_ERROR; + } + + if (pkt->flags & NGX_QUIC_PKT_LONG) { + clearflags = pkt->flags ^ (mask[0] & 0x0f); + + } else { + clearflags = pkt->flags ^ (mask[0] & 0x1f); + } + + pnl = (clearflags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); + + pkt->pn = pn; + + ngx_quic_hexdump0(log, "mask", mask, 5); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic clear flags: %xi", clearflags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + + /* packet protection */ + + in.data = p; + + if (pkt->flags & NGX_QUIC_PKT_LONG) { + in.len = pkt->len - pnl; + + } else { + in.len = pkt->data + pkt->len - p; + } + + ad.len = p - pkt->data; + ad.data = ngx_pnalloc(pool, ad.len); + if (ad.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(ad.data, pkt->data, ad.len); + ad.data[0] = clearflags; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + nonce = ngx_pstrdup(pool, &pkt->secret->iv); + nonce[11] ^= pn; + + ngx_quic_hexdump0(log, "nonce", nonce, 12); + ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + + rc = ngx_quic_tls_open(pool, ciphers.c, pkt->secret, &pkt->payload, + nonce, &in, &ad); + + ngx_quic_hexdump0(log, "packet payload", + pkt->payload.data, pkt->payload.len); + + return rc; +} + diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h new file mode 100644 index 000000000..80dcf110e --- /dev/null +++ b/src/event/ngx_event_quic_protection.h @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ + + +struct ngx_quic_secret_s { + ngx_str_t secret; + ngx_str_t key; + ngx_str_t iv; + ngx_str_t hp; +}; + + +typedef struct { + ngx_quic_secret_t in; + ngx_quic_secret_t hs; + ngx_quic_secret_t ad; +} ngx_quic_peer_secrets_t; + + +typedef struct { + ngx_quic_peer_secrets_t client; + ngx_quic_peer_secrets_t server; +} ngx_quic_secrets_t; + + +ngx_int_t ngx_quic_set_initial_secret(ngx_pool_t *pool, + ngx_quic_secrets_t *secrets, ngx_str_t *secret); + +int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len, + ngx_quic_peer_secrets_t *qsec); + +ngx_int_t ngx_quic_encrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res); + +ngx_int_t ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, + ngx_quic_header_t *pkt); + + +#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ -- cgit v1.2.3 From ca7943393ef1d355c1bde7df91e3f652a4400424 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 16 Mar 2020 19:42:57 +0300 Subject: Fixed a typo with OpenSSL. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6114b6f18..ffa54bd15 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -764,7 +764,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - ngx_quic_hexdump(c->log, "level:%d read", recret, secret_len, level); + ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, -- cgit v1.2.3 From cd54c1cab72bf6fa4a2c935d31f739c0fc4a265c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 17 Mar 2020 14:10:37 +0300 Subject: Firefox fixes. + support for more than one initial packet + workaround for trailing zeroes in packet + ignore application data packet if no keys yet (issue in draft 27/ff nightly) + fixed PING frame parser + STREAM frames need to be acknowledged The following HTTP configuration is used for firefox (v74): http { ssl_certificate_key localhost.key; ssl_certificate localhost.crt; ssl_protocols TLSv1.2 TLSv1.3; server { listen 127.0.0.1:10368 reuseport http3; ssl_quic on; server_name localhost; location / { return 200 "This-is-QUICK\n"; } } server { listen 127.0.0.1:5555 ssl; # point the browser here server_name localhost; location / { add_header Alt-Svc 'h3-24=":10368";ma=100'; return 200 "ALT-SVC"; } } } --- src/event/ngx_event_quic.c | 67 +++++++++++++++++++++++++++++++++++++++++----- src/event/ngx_event_quic.h | 3 ++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ffa54bd15..410fa0fcd 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -205,6 +205,8 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, static void ngx_quic_handshake_handler(ngx_event_t *rev); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt); @@ -389,6 +391,7 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) { u_char *p; + ngx_int_t rc; ngx_quic_header_t pkt; if (c->quic == NULL) { @@ -405,16 +408,33 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.data = p; pkt.len = b->last - p; + if (p[0] == 0) { + /* XXX: no idea WTF is this, just ignore */ + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES"); + break; + } + + // TODO: check current state if (p[0] & NGX_QUIC_PKT_LONG) { - // TODO: check current state - if (ngx_quic_handshake_input(c, &pkt) != NGX_OK) { + + if ((p[0] & 0xf0) == NGX_QUIC_PKT_INITIAL) { + rc = ngx_quic_initial_input(c, &pkt); + + } else if ((p[0] & 0xf0) == NGX_QUIC_PKT_HANDSHAKE) { + rc = ngx_quic_handshake_input(c, &pkt); + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "BUG: unknown quic state"); return NGX_ERROR; } + } else { + rc = ngx_quic_app_input(c, &pkt); + } - if (ngx_quic_app_input(c, &pkt) != NGX_OK) { - return NGX_ERROR; - } + if (rc == NGX_ERROR) { + return NGX_ERROR; } /* b->pos is at header end, adjust by actual packet length */ @@ -1073,7 +1093,6 @@ ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, case NGX_QUIC_FT_PING: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame"); - p++; break; case NGX_QUIC_FT_NEW_CONNECTION_ID: @@ -1489,6 +1508,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: + ack_this = 1; + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, "STREAM frame 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d", frame.type, @@ -1774,6 +1795,34 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, } +static ngx_int_t +ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = c->quic; + ssl_conn = c->ssl->connection; + + if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + pkt->secret = &qc->secrets.client.in; + pkt->level = ssl_encryption_initial; + + if (ngx_quic_decrypt(c->pool, ssl_conn, pkt) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_quic_payload_handler(c, pkt); +} + + static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -1836,7 +1885,11 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; - /* TODO: this is a stub, untested */ + if (qc->secrets.client.ad.key.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "no read keys yet, packet ignored"); + return NGX_DECLINED; + } if (ngx_quic_process_short_header(c, pkt) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index faa35c9ab..252875569 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,7 +11,8 @@ #include -#define quic_version 0xff000018 /* draft-24 */ +#define quic_version 0xff000018 /* draft-24 (ngtcp2) */ +//#define quic_version 0xff00001b /* draft-27 (FFN 76) */ /* 17.2. Long Header Packets */ -- cgit v1.2.3 From 23dc6a68a4c94300896c76e2161d18d11d89d3ee Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 18 Mar 2020 12:58:27 +0300 Subject: Extracted transport part of the code into separate file. All code dealing with serializing/deserializing is moved int srv/event/ngx_event_quic_transport.c/h file. All macros for dealing with data are internal to source file. The header file exposes frame types and error codes. The exported functions are currently packet header parsers and writers and frames parser/writer. The ngx_quic_header_t structure is updated with 'log' member. This avoids passing extra argument to parsing functions that need to report errors. --- auto/modules | 8 +- src/core/ngx_core.h | 1 + src/event/ngx_event_quic.c | 670 ++-------------------------------- src/event/ngx_event_quic.h | 82 +---- src/event/ngx_event_quic_protection.c | 30 +- src/event/ngx_event_quic_protection.h | 4 +- src/event/ngx_event_quic_transport.c | 588 +++++++++++++++++++++++++++++ src/event/ngx_event_quic_transport.h | 177 +++++++++ 8 files changed, 813 insertions(+), 747 deletions(-) create mode 100644 src/event/ngx_event_quic_transport.c create mode 100644 src/event/ngx_event_quic_transport.h diff --git a/auto/modules b/auto/modules index 7e7affb2e..24bbcb2ae 100644 --- a/auto/modules +++ b/auto/modules @@ -1265,10 +1265,12 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_incs= ngx_module_deps="src/event/ngx_event_openssl.h \ src/event/ngx_event_quic.h \ + src/event/ngx_event_quic_transport.h \ src/event/ngx_event_quic_protection.h" - ngx_module_srcs="src/event/ngx_event_openssl.c - src/event/ngx_event_openssl_stapling.c - src/event/ngx_event_quic.c + ngx_module_srcs="src/event/ngx_event_openssl.c \ + src/event/ngx_event_openssl_stapling.c \ + src/event/ngx_event_quic.c \ + src/event/ngx_event_quic_transport.c \ src/event/ngx_event_quic_protection.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index a5f7b7af5..eb3acd663 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -85,6 +85,7 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #if (NGX_OPENSSL) #include #include +#include #include #endif #include diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 410fa0fcd..000b8be8b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,82 +9,6 @@ #include -/* 12.4. Frames and Frame Types */ -#define NGX_QUIC_FT_PADDING 0x00 -#define NGX_QUIC_FT_PING 0x01 -#define NGX_QUIC_FT_ACK 0x02 -#define NGX_QUIC_FT_ACK_ECN 0x03 -#define NGX_QUIC_FT_RESET_STREAM 0x04 -#define NGX_QUIC_FT_STOP_SENDING 0x05 -#define NGX_QUIC_FT_CRYPTO 0x06 -#define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM0 0x08 -#define NGX_QUIC_FT_STREAM1 0x09 -#define NGX_QUIC_FT_STREAM2 0x0A -#define NGX_QUIC_FT_STREAM3 0x0B -#define NGX_QUIC_FT_STREAM4 0x0C -#define NGX_QUIC_FT_STREAM5 0x0D -#define NGX_QUIC_FT_STREAM6 0x0E -#define NGX_QUIC_FT_STREAM7 0x0F -#define NGX_QUIC_FT_MAX_DATA 0x10 -#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 -#define NGX_QUIC_FT_MAX_STREAMS 0x12 -#define NGX_QUIC_FT_MAX_STREAMS2 0x13 // XXX -#define NGX_QUIC_FT_DATA_BLOCKED 0x14 -#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 -#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 -#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 // XXX -#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 -#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 -#define NGX_QUIC_FT_PATH_CHALLENGE 0x1a -#define NGX_QUIC_FT_PATH_RESPONSE 0x1b -#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1c -#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1d // XXX -#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1e - -#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) -#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) -#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) - - -#define NGX_QUIC_ERR_NO_ERROR 0x0 -#define NGX_QUIC_ERR_INTERNAL_ERROR 0x1 -#define NGX_QUIC_ERR_SERVER_BUSY 0x2 -#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x3 -#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x4 -#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x5 -#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x6 -#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x7 -#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x8 -#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x9 -#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0xA -#define NGX_QUIC_ERR_INVALID_TOKEN 0xB -/* 0xC is not defined */ -#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0xD -#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 - -#define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR - -/* literal errors indexed by corresponding value */ -static char *ngx_quic_errors[] = { - "NO_ERROR", - "INTERNAL_ERROR", - "SERVER_BUSY", - "FLOW_CONTROL_ERROR", - "STREAM_LIMIT_ERROR", - "STREAM_STATE_ERROR", - "FINAL_SIZE_ERROR", - "FRAME_ENCODING_ERROR", - "TRANSPORT_PARAMETER_ERROR", - "CONNECTION_ID_LIMIT_ERROR", - "PROTOCOL_VIOLATION", - "INVALID_TOKEN", - "", - "CRYPTO_BUFFER_EXCEEDED", - "CRYPTO_ERROR", -}; - - /* TODO: real states, these are stubs */ typedef enum { NGX_QUIC_ST_INITIAL, @@ -93,69 +17,6 @@ typedef enum { } ngx_quic_state_t; -typedef struct ngx_quic_frame_s ngx_quic_frame_t; - -typedef struct { - ngx_uint_t pn; - - // input - uint64_t largest; - uint64_t delay; - uint64_t range_count; - uint64_t first_range; - uint64_t ranges[20]; - /* ecn counts */ -} ngx_quic_ack_frame_t; - -typedef struct { - size_t offset; - size_t len; - u_char *data; -} ngx_quic_crypto_frame_t; - - -typedef struct { - uint64_t seqnum; - uint64_t retire; - uint64_t len; - u_char cid[20]; - u_char srt[16]; -} ngx_quic_ncid_t; - - -typedef struct { - uint8_t type; - uint64_t stream_id; - uint64_t offset; - uint64_t length; - u_char *data; -} ngx_quic_stream_frame_t; - - -typedef struct { - uint64_t error_code; - uint64_t frame_type; - ngx_str_t reason; -} ngx_quic_close_frame_t; - - -struct ngx_quic_frame_s { - ngx_uint_t type; - ngx_quic_level_t level; - ngx_quic_frame_t *next; - union { - ngx_quic_crypto_frame_t crypto; - ngx_quic_ack_frame_t ack; - ngx_quic_ncid_t ncid; - ngx_quic_stream_frame_t stream; - ngx_quic_close_frame_t close; - // more frames - } u; - - u_char info[128]; // for debug purposes -}; - - struct ngx_quic_connection_s { ngx_quic_state_t state; @@ -229,16 +90,6 @@ static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); -static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_short_header(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c, - ngx_quic_header_t *pkt); - -static uint64_t ngx_quic_parse_int(u_char **pos); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -282,6 +133,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, b = c->buffer; + pkt.log = c->log; pkt.raw = b; pkt.data = b->start; pkt.len = b->last - b->start; @@ -407,6 +259,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.raw = b; pkt.data = p; pkt.len = b->last - p; + pkt.log = c->log; if (p[0] == 0) { /* XXX: no idea WTF is this, just ignore */ @@ -448,11 +301,13 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_quic_level_t level, ngx_str_t *payload) + enum ssl_encryption_level_t level, ngx_str_t *payload) { ngx_str_t res; ngx_quic_header_t pkt; + pkt.log = c->log; + static ngx_str_t initial_token = ngx_null_string; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -494,139 +349,12 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, } -static size_t -ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) -{ - size_t len; - - /* minimal ACK packet */ - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); - len += ngx_quic_varint_len(ack->pn); - len += ngx_quic_varint_len(0); - len += ngx_quic_varint_len(0); - len += ngx_quic_varint_len(ack->pn); - - return len; - } - - ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); - ngx_quic_build_int(&p, ack->pn); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, ack->pn); - - return 5; -} - - -static size_t -ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); - len += ngx_quic_varint_len(crypto->offset); - len += ngx_quic_varint_len(crypto->len); - len += crypto->len; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); - ngx_quic_build_int(&p, crypto->offset); - ngx_quic_build_int(&p, crypto->len); - p = ngx_cpymem(p, crypto->data, crypto->len); - - return p - start; -} - - -static size_t -ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) -{ - size_t len; - u_char *start; - - if (!ngx_quic_stream_bit_len(sf->type)) { -#if 0 - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "attempt to generate a stream frame without length"); -#endif - // XXX: handle error in caller - return NGX_ERROR; - } - - if (p == NULL) { - len = ngx_quic_varint_len(sf->type); - - if (ngx_quic_stream_bit_off(sf->type)) { - len += ngx_quic_varint_len(sf->offset); - } - - len += ngx_quic_varint_len(sf->stream_id); - - /* length is always present in generated frames */ - len += ngx_quic_varint_len(sf->length); - - len += sf->length; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, sf->type); - ngx_quic_build_int(&p, sf->stream_id); - - if (ngx_quic_stream_bit_off(sf->type)) { - ngx_quic_build_int(&p, sf->offset); - } - - /* length is always present in generated frames */ - ngx_quic_build_int(&p, sf->length); - - p = ngx_cpymem(p, sf->data, sf->length); - - return p - start; -} - - -size_t -ngx_quic_frame_len(ngx_quic_frame_t *frame) -{ - switch (frame->type) { - case NGX_QUIC_FT_ACK: - return ngx_quic_create_ack(NULL, &frame->u.ack); - case NGX_QUIC_FT_CRYPTO: - return ngx_quic_create_crypto(NULL, &frame->u.crypto); - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - return ngx_quic_create_stream(NULL, &frame->u.stream); - default: - /* BUG: unsupported frame type generated */ - return 0; - } -} - - /* pack a group of frames [start; end) into memory p and send as single packet */ ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_quic_frame_t *end, size_t total) { + ssize_t len; u_char *p; ngx_str_t out; ngx_quic_frame_t *f; @@ -645,30 +373,12 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); - switch (f->type) { - case NGX_QUIC_FT_ACK: - p += ngx_quic_create_ack(p, &f->u.ack); - break; - - case NGX_QUIC_FT_CRYPTO: - p += ngx_quic_create_crypto(p, &f->u.crypto); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - p += ngx_quic_create_stream(p, &f->u.stream); - break; - - default: - /* BUG: unsupported frame type generated */ + len = ngx_quic_create_frame(p, p + total, f); + if (len == -1) { return NGX_ERROR; } + + p += len; } out.len = p - out.data; @@ -892,279 +602,6 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, } -static ngx_int_t -ngx_quic_process_short_header(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *p; - - p = pkt->data; - - ngx_quic_hexdump0(c->log, "short input", pkt->data, pkt->len); - - if ((p[0] & NGX_QUIC_PKT_LONG)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a short packet"); - return NGX_ERROR; - } - - pkt->flags = *p++; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flags:%xi", pkt->flags); - - if (ngx_memcmp(p, c->quic->dcid.data, c->quic->dcid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); - return NGX_ERROR; - } - - pkt->dcid.len = c->quic->dcid.len; - pkt->dcid.data = p; - p += pkt->dcid.len; - - pkt->raw->pos = p; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *p; - - p = pkt->data; - - ngx_quic_hexdump0(c->log, "long input", pkt->data, pkt->len); - - if (!(p[0] & NGX_QUIC_PKT_LONG)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet"); - return NGX_ERROR; - } - - pkt->flags = *p++; - - pkt->version = ngx_quic_parse_uint32(p); - p += sizeof(uint32_t); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flags:%xi version:%xD", pkt->flags, pkt->version); - - if (pkt->version != quic_version) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version"); - return NGX_ERROR; - } - - pkt->dcid.len = *p++; - pkt->dcid.data = p; - p += pkt->dcid.len; - - pkt->scid.len = *p++; - pkt->scid.data = p; - p += pkt->scid.len; - - pkt->raw->pos = p; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *p; - ngx_int_t plen; - - p = pkt->raw->pos; - - pkt->token.len = ngx_quic_parse_int(&p); - pkt->token.data = p; - - p += pkt->token.len; - - plen = ngx_quic_parse_int(&p); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); - - if (plen > pkt->data + pkt->len - p) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - pkt->len = plen; - - ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len); - ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len); - ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); - - return NGX_OK; -} - -static ngx_int_t -ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *p; - ngx_int_t plen; - - p = pkt->raw->pos; - - plen = ngx_quic_parse_int(&p); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); - - if (plen > pkt->data + pkt->len - p) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - pkt->len = plen; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet length: %d", plen); - - return NGX_OK; -} - - -ssize_t -ngx_quic_read_frame(ngx_connection_t *c, u_char *start, u_char *end, - ngx_quic_frame_t *frame) -{ - u_char *p; - - size_t npad; - - p = start; - - frame->type = *p++; // TODO: check overflow (p < end) - - switch (frame->type) { - - case NGX_QUIC_FT_CRYPTO: - frame->u.crypto.offset = *p++; - frame->u.crypto.len = ngx_quic_parse_int(&p); - frame->u.crypto.data = p; - p += frame->u.crypto.len; - - ngx_quic_hexdump0(c->log, "CRYPTO frame", - frame->u.crypto.data, frame->u.crypto.len); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic CRYPTO frame length: %uL off:%uL pp:%p", - frame->u.crypto.len, frame->u.crypto.offset, - frame->u.crypto.data); - break; - - case NGX_QUIC_FT_PADDING: - npad = 0; - while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX - p++; npad++; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "PADDING frame length %uL", npad); - - break; - - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ACK frame"); - - frame->u.ack.largest = ngx_quic_parse_int(&p); - frame->u.ack.delay = ngx_quic_parse_int(&p); - frame->u.ack.range_count =ngx_quic_parse_int(&p); - frame->u.ack.first_range =ngx_quic_parse_int(&p); - - if (frame->u.ack.range_count) { - frame->u.ack.ranges[0] = ngx_quic_parse_int(&p); - } - - if (frame->type ==NGX_QUIC_FT_ACK_ECN) { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_PING: - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "PING frame"); - break; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "NCID frame"); - - frame->u.ncid.seqnum = ngx_quic_parse_int(&p); - frame->u.ncid.retire = ngx_quic_parse_int(&p); - frame->u.ncid.len = *p++; - ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len); - p += frame->u.ncid.len; - - ngx_memcpy(frame->u.ncid.srt, p, 16); - p += 16; - - break; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "connection close frame"); - - frame->u.close.error_code = ngx_quic_parse_int(&p); - frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC - frame->u.close.reason.len = ngx_quic_parse_int(&p); - frame->u.close.reason.data = p; - p += frame->u.close.reason.len; - - if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) { - frame->u.close.error_code = NGX_QUIC_ERR_LAST; - } - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAM frame, type: 0x%xi", frame->type); - - frame->u.stream.type = frame->type; - - frame->u.stream.stream_id = ngx_quic_parse_int(&p); - if (frame->type & 0x04) { - frame->u.stream.offset = ngx_quic_parse_int(&p); - } else { - frame->u.stream.offset = 0; - } - - if (frame->type & 0x02) { - frame->u.stream.length = ngx_quic_parse_int(&p); - } else { - frame->u.stream.length = end - p; /* up to packet end */ - } - - frame->u.stream.data = p; - - p += frame->u.stream.length; - - break; - - default: - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "unknown frame type %xi", frame->type); - return NGX_ERROR; - } - - return p - start; -} - static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, @@ -1349,6 +786,9 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; frame->u.stream.type = frame->type; frame->u.stream.stream_id = qs->id; @@ -1433,8 +873,11 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) while (p < end) { - len = ngx_quic_read_frame(c, p, end, &frame); + len = ngx_quic_parse_frame(p, end, &frame); if (len < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "unknown frame type %xi", frame.type); + // XXX: log here return NGX_ERROR; } @@ -1456,6 +899,13 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_CRYPTO: + ngx_quic_hexdump0(c->log, "CRYPTO frame", + frame.u.crypto.data, frame.u.crypto.len); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic CRYPTO frame length: %uL off:%uL pp:%p", + frame.u.crypto.len, frame.u.crypto.offset, + frame.u.crypto.data); if (frame.u.crypto.offset != 0x0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1491,7 +941,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_CONNECTION_CLOSE: ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", - ngx_quic_errors[frame.u.close.error_code], + ngx_quic_error_text(frame.u.close.error_code), frame.u.close.error_code, frame.u.close.frame_type, &frame.u.close.reason); @@ -1516,10 +966,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.stream.stream_id, frame.u.stream.offset, frame.u.stream.length, - ngx_quic_stream_bit_off(frame.u.stream.type), - ngx_quic_stream_bit_len(frame.u.stream.type), - ngx_quic_stream_bit_fin(frame.u.stream.type)); - + frame.u.stream.off, + frame.u.stream.len, + frame.u.stream.fin); sn = ngx_quic_stream_lookup(&qc->stree, frame.u.stream.stream_id); if (sn == NULL) { @@ -1727,7 +1176,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1737,7 +1186,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1804,11 +1253,11 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; ssl_conn = c->ssl->connection; - if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; } - if (ngx_quic_process_initial_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1833,7 +1282,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ssl_conn = c->ssl->connection; /* extract cleartext data into pkt */ - if (ngx_quic_process_long_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1863,7 +1312,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (ngx_quic_process_handshake_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1891,7 +1340,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } - if (ngx_quic_process_short_header(c, pkt) != NGX_OK) { + if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { return NGX_ERROR; } @@ -1906,52 +1355,3 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } -uint64_t -ngx_quic_parse_int(u_char **pos) -{ - u_char *p; - uint64_t value; - ngx_uint_t len; - - p = *pos; - len = 1 << ((*p & 0xc0) >> 6); - value = *p++ & 0x3f; - - while (--len) { - value = (value << 8) + *p++; - } - - *pos = p; - return value; -} - - -void -ngx_quic_build_int(u_char **pos, uint64_t value) -{ - u_char *p; - ngx_uint_t len;//, len2; - - p = *pos; - len = 0; - - while (value >> ((1 << len) * 8 - 2)) { - len++; - } - - *p = len << 6; - -// len2 = - len = (1 << len); - len--; - *p |= value >> (len * 8); - p++; - - while (len) { - *p++ = value >> ((len-- - 1) * 8); - } - - *pos = p; -// return len2; -} - diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 252875569..b3b86d99b 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,57 +11,8 @@ #include -#define quic_version 0xff000018 /* draft-24 (ngtcp2) */ -//#define quic_version 0xff00001b /* draft-27 (FFN 76) */ - -/* 17.2. Long Header Packets */ - -#define NGX_QUIC_PKT_LONG 0x80 - -#define NGX_QUIC_PKT_INITIAL 0xc0 -#define NGX_QUIC_PKT_HANDSHAKE 0xe0 - - -#if (NGX_HAVE_NONALIGNED) - -#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) -#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) - -#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned -#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned - -#else - -#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) -#define ngx_quic_parse_uint32(p) \ - ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) - -#define ngx_quic_write_uint16(p, s) \ - ((p)[0] = (u_char) ((s) >> 8), \ - (p)[1] = (u_char) (s), \ - (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32(p, s) \ - ((p)[0] = (u_char) ((s) >> 24), \ - (p)[1] = (u_char) ((s) >> 16), \ - (p)[2] = (u_char) ((s) >> 8), \ - (p)[3] = (u_char) (s), \ - (p) + sizeof(uint32_t)) - -#endif - - -#define ngx_quic_write_uint16_aligned(p, s) \ - (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32_aligned(p, s) \ - (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) - -#define ngx_quic_varint_len(value) \ - ((value) <= 63 ? 1 \ - : ((uint32_t) value) <= 16383 ? 2 \ - : ((uint64_t) value) <= 1073741823 ? 4 \ - : 8) +#define quic_version 0xff000018 /* draft-24 (ngtcp2) */ +//#define quic_version 0xff00001b /* draft-27 (FFN 76) */ struct ngx_quic_stream_s { @@ -71,35 +22,6 @@ struct ngx_quic_stream_s { void *data; }; -typedef struct ngx_quic_secret_s ngx_quic_secret_t; -typedef enum ssl_encryption_level_t ngx_quic_level_t; - -typedef struct { - ngx_quic_secret_t *secret; - ngx_uint_t type; - ngx_uint_t *number; - ngx_uint_t flags; - uint32_t version; - ngx_str_t token; - ngx_quic_level_t level; - - /* filled in by parser */ - ngx_buf_t *raw; /* udp datagram from wire */ - - u_char *data; /* quic packet */ - size_t len; - - /* cleartext fields */ - ngx_str_t dcid; - ngx_str_t scid; - - uint64_t pn; - - ngx_str_t payload; /* decrypted payload */ - -} ngx_quic_header_t; - -void ngx_quic_build_int(u_char **pos, uint64_t value); void ngx_quic_init_ssl_methods(SSL_CTX* ctx); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 544adaace..23b8c011f 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -702,30 +702,7 @@ ngx_quic_create_long_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } - p = ad.data; - - *p++ = pkt->flags; - - p = ngx_quic_write_uint32(p, quic_version); - - *p++ = pkt->scid.len; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); - - *p++ = pkt->dcid.len; - p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); - - if (pkt->level == ssl_encryption_initial) { - ngx_quic_build_int(&p, pkt->token.len); - } - - ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl) - pnp = p; - - pn = *pkt->number; - - *p++ = pn; - - ad.len = p - ad.data; + ad.len = ngx_quic_create_long_header(pkt, &ad, out.len, &pnp); ngx_quic_hexdump0(log, "ad", ad.data, ad.len); @@ -734,9 +711,8 @@ ngx_quic_create_long_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, } nonce = ngx_pstrdup(pool, &pkt->secret->iv); - if (pkt->level == ssl_encryption_handshake) { - nonce[11] ^= pn; - } + pn = *pkt->number; + nonce[11] ^= pn; ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(log, "nonce", nonce, 12); diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index 80dcf110e..499301f41 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -8,12 +8,12 @@ #define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ -struct ngx_quic_secret_s { +typedef struct ngx_quic_secret_s { ngx_str_t secret; ngx_str_t key; ngx_str_t iv; ngx_str_t hp; -}; +} ngx_quic_secret_t; typedef struct { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c new file mode 100644 index 000000000..1f49c10f4 --- /dev/null +++ b/src/event/ngx_event_quic_transport.c @@ -0,0 +1,588 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_varint_len(value) \ + ((value) <= 63 ? 1 \ + : ((uint32_t) value) <= 16383 ? 2 \ + : ((uint64_t) value) <= 1073741823 ? 4 \ + : 8) + + +static uint64_t ngx_quic_parse_int(u_char **pos); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); + + +/* literal errors indexed by corresponding value */ +static char *ngx_quic_errors[] = { + "NO_ERROR", + "INTERNAL_ERROR", + "SERVER_BUSY", + "FLOW_CONTROL_ERROR", + "STREAM_LIMIT_ERROR", + "STREAM_STATE_ERROR", + "FINAL_SIZE_ERROR", + "FRAME_ENCODING_ERROR", + "TRANSPORT_PARAMETER_ERROR", + "CONNECTION_ID_LIMIT_ERROR", + "PROTOCOL_VIOLATION", + "INVALID_TOKEN", + "", + "CRYPTO_BUFFER_EXCEEDED", + "CRYPTO_ERROR", +}; + + +static uint64_t +ngx_quic_parse_int(u_char **pos) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + p = *pos; + len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + + while (--len) { + value = (value << 8) + *p++; + } + + *pos = p; + return value; +} + + +static void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + ngx_uint_t len;//, len2; + + p = *pos; + len = 0; + + while (value >> ((1 << len) * 8 - 2)) { + len++; + } + + *p = len << 6; + +// len2 = + len = (1 << len); + len--; + *p |= value >> (len * 8); + p++; + + while (len) { + *p++ = value >> ((len-- - 1) * 8); + } + + *pos = p; +// return len2; +} + + +u_char * +ngx_quic_error_text(uint64_t error_code) +{ + return (u_char *) ngx_quic_errors[error_code]; +} + + +ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p; + + p = pkt->data; + + ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len); + + if (!(p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet"); + return NGX_ERROR; + } + + pkt->flags = *p++; + + pkt->version = ngx_quic_parse_uint32(p); + p += sizeof(uint32_t); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic flags:%xi version:%xD", pkt->flags, pkt->version); + + if (pkt->version != quic_version) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version"); + return NGX_ERROR; + } + + pkt->dcid.len = *p++; + pkt->dcid.data = p; + p += pkt->dcid.len; + + pkt->scid.len = *p++; + pkt->scid.data = p; + p += pkt->scid.len; + + pkt->raw->pos = p; + + return NGX_OK; +} + + +size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, + size_t pkt_len, u_char **pnp) +{ + u_char *p, *start; + + p = start = out->data; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, quic_version); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, pkt->token.len); + } + + ngx_quic_build_int(&p, pkt_len + 1); // length (inc. pnl) + + *pnp = p; + + *p++ = (uint64_t) (*pkt->number); + + return p - start; +} + + +ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) +{ + u_char *p; + + p = pkt->data; + + ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len); + + if ((p[0] & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet"); + return NGX_ERROR; + } + + pkt->flags = *p++; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic flags:%xi", pkt->flags); + + if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid->len; + pkt->dcid.data = p; + p += pkt->dcid.len; + + pkt->raw->pos = p; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; + + p = pkt->raw->pos; + + pkt->token.len = ngx_quic_parse_int(&p); + pkt->token.data = p; + + p += pkt->token.len; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->data + pkt->len - p) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = plen; + + ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); + ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) +{ + u_char *p; + ngx_int_t plen; + + p = pkt->raw->pos; + + plen = ngx_quic_parse_int(&p); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + if (plen > pkt->data + pkt->len - p) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = plen; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet length: %d", plen); + + return NGX_OK; +} + + +#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) +#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) +#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) + +ssize_t +ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) +{ + u_char *p; + + size_t npad; + + p = start; + + frame->type = *p++; // TODO: check overflow (p < end) + + switch (frame->type) { + + case NGX_QUIC_FT_CRYPTO: + frame->u.crypto.offset = *p++; + frame->u.crypto.len = ngx_quic_parse_int(&p); + frame->u.crypto.data = p; + p += frame->u.crypto.len; + + break; + + case NGX_QUIC_FT_PADDING: + npad = 0; + while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX + p++; npad++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + frame->u.ack.largest = ngx_quic_parse_int(&p); + frame->u.ack.delay = ngx_quic_parse_int(&p); + frame->u.ack.range_count =ngx_quic_parse_int(&p); + frame->u.ack.first_range =ngx_quic_parse_int(&p); + + if (frame->u.ack.range_count) { + frame->u.ack.ranges[0] = ngx_quic_parse_int(&p); + } + + if (frame->type ==NGX_QUIC_FT_ACK_ECN) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + frame->u.ncid.seqnum = ngx_quic_parse_int(&p); + frame->u.ncid.retire = ngx_quic_parse_int(&p); + frame->u.ncid.len = *p++; + ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len); + p += frame->u.ncid.len; + + ngx_memcpy(frame->u.ncid.srt, p, 16); + p += 16; + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + + frame->u.close.error_code = ngx_quic_parse_int(&p); + frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC + frame->u.close.reason.len = ngx_quic_parse_int(&p); + frame->u.close.reason.data = p; + p += frame->u.close.reason.len; + + if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) { + frame->u.close.error_code = NGX_QUIC_ERR_LAST; + } + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + frame->u.stream.type = frame->type; + + frame->u.stream.off = ngx_quic_stream_bit_off(frame->type); + frame->u.stream.len = ngx_quic_stream_bit_len(frame->type); + frame->u.stream.fin = ngx_quic_stream_bit_fin(frame->type); + + frame->u.stream.stream_id = ngx_quic_parse_int(&p); + if (frame->type & 0x04) { + frame->u.stream.offset = ngx_quic_parse_int(&p); + } else { + frame->u.stream.offset = 0; + } + + if (frame->type & 0x02) { + frame->u.stream.length = ngx_quic_parse_int(&p); + } else { + frame->u.stream.length = end - p; /* up to packet end */ + } + + frame->u.stream.data = p; + + p += frame->u.stream.length; + + break; + + default: + return NGX_ERROR; + } + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) +{ + // TODO: handle end arg + + switch (f->type) { + case NGX_QUIC_FT_ACK: + return ngx_quic_create_ack(p, &f->u.ack); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(p, &f->u.stream); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +size_t +ngx_quic_frame_len(ngx_quic_frame_t *frame) +{ + switch (frame->type) { + case NGX_QUIC_FT_ACK: + return ngx_quic_create_ack(NULL, &frame->u.ack); + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(NULL, &frame->u.crypto); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(NULL, &frame->u.stream); + default: + /* BUG: unsupported frame type generated */ + return 0; + } +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) +{ + size_t len; + + /* minimal ACK packet */ + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->pn); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->pn); + + return len; + } + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->pn); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->pn); + + return 5; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->len); + len += crypto->len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->len); + p = ngx_cpymem(p, crypto->data, crypto->len); + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) +{ + size_t len; + u_char *start; + + if (!sf->len) { +#if 0 + ngx_log_error(NGX_LOG_INFO, log, 0, + "attempt to generate a stream frame without length"); +#endif + // XXX: handle error in caller + return NGX_ERROR; + } + + if (p == NULL) { + len = ngx_quic_varint_len(sf->type); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + len += ngx_quic_varint_len(sf->stream_id); + + /* length is always present in generated frames */ + len += ngx_quic_varint_len(sf->length); + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, sf->type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + /* length is always present in generated frames */ + ngx_quic_build_int(&p, sf->length); + + p = ngx_cpymem(p, sf->data, sf->length); + + return p - start; +} diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h new file mode 100644 index 000000000..549cc9757 --- /dev/null +++ b/src/event/ngx_event_quic_transport.h @@ -0,0 +1,177 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ +#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ + + +#include + + +/* 17.2. Long Header Packets */ +#define NGX_QUIC_PKT_LONG 0x80 + +#define NGX_QUIC_PKT_INITIAL 0xC0 +#define NGX_QUIC_PKT_HANDSHAKE 0xE0 + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM0 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 // XXX +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 // XXX +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D // XXX +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E + +/* 22.4. QUIC Transport Error Codes Registry */ +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_SERVER_BUSY 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +/* 0xC is not defined */ +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 + +#define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR + + +typedef struct { + ngx_uint_t pn; + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ranges[20]; + /* TODO: ecn counts */ +} ngx_quic_ack_frame_t; + + +typedef struct { + size_t offset; + size_t len; + u_char *data; +} ngx_quic_crypto_frame_t; + + +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint64_t len; + u_char cid[20]; + u_char srt[16]; +} ngx_quic_new_conn_id_frame_t; + + +typedef struct { + uint8_t type; + uint64_t stream_id; + uint64_t offset; + uint64_t length; + unsigned off:1; + unsigned len:1; + unsigned fin:1; + u_char *data; +} ngx_quic_stream_frame_t; + + +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; +} ngx_quic_close_frame_t; + + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + enum ssl_encryption_level_t level; + ngx_quic_frame_t *next; + union { + ngx_quic_ack_frame_t ack; + ngx_quic_crypto_frame_t crypto; + ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_stream_frame_t stream; + ngx_quic_close_frame_t close; + } u; + u_char info[128]; // for debug +}; + + +typedef struct { + ngx_log_t *log; + + struct ngx_quic_secret_s *secret; + ngx_uint_t type; + ngx_uint_t *number; + ngx_uint_t flags; + uint32_t version; + ngx_str_t token; + enum ssl_encryption_level_t level; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t dcid; + ngx_str_t scid; + uint64_t pn; + ngx_str_t payload; /* decrypted */ +} ngx_quic_header_t; + + +u_char *ngx_quic_error_text(uint64_t error_code); + +ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, + size_t pkt_len, u_char **pnp); + +ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + ngx_str_t *dcid); +ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); + +ssize_t ngx_quic_parse_frame(u_char *start, u_char *end, + ngx_quic_frame_t *frame); +ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); +size_t ngx_quic_frame_len(ngx_quic_frame_t *frame); + +#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ -- cgit v1.2.3 From 023dbc3cfb73793ac84557ee0d228e63a6cd2308 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 18 Mar 2020 13:02:19 +0300 Subject: Style and handlers. Cleanup in ngx_event_quic.c: + reorderded functions, structures + added missing prototypes + added separate handlers for each frame type + numerous indentation/comments/TODO fixes + removed non-implemented qc->state and corresponding enum; this requires deep thinking, stub was unused. + streams inside quic connection are now in own structure --- src/event/ngx_event_quic.c | 1647 +++++++++++++++++++++++--------------------- 1 file changed, 843 insertions(+), 804 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 000b8be8b..e6ee6d2a6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,67 +9,38 @@ #include -/* TODO: real states, these are stubs */ -typedef enum { - NGX_QUIC_ST_INITIAL, - NGX_QUIC_ST_HANDSHAKE, - NGX_QUIC_ST_APP_DATA -} ngx_quic_state_t; - - -struct ngx_quic_connection_s { - - ngx_quic_state_t state; - ngx_ssl_t *ssl; - - ngx_quic_frame_t *frames; - - ngx_str_t scid; - ngx_str_t dcid; - ngx_str_t token; - - /* current packet numbers for each namespace */ - ngx_uint_t initial_pn; - ngx_uint_t handshake_pn; - ngx_uint_t appdata_pn; - - ngx_quic_secrets_t secrets; - - /* streams */ - ngx_rbtree_t stree; - ngx_rbtree_node_t stree_sentinel; - ngx_msec_t stream_timeout; - ngx_connection_handler_pt stream_handler; -}; +typedef struct { + ngx_rbtree_node_t node; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_stream_t s; +} ngx_quic_stream_node_t; typedef struct { - ngx_rbtree_node_t node; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_quic_stream_t s; -} ngx_quic_stream_node_t; + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + ngx_msec_t timeout; + ngx_connection_handler_pt handler; +} ngx_quic_streams_t; -static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); -static ngx_int_t ngx_quic_output(ngx_connection_t *c); +struct ngx_quic_connection_s { + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; -static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_header_t *pkt); -static void ngx_quic_close_connection(ngx_connection_t *c); + /* current packet numbers for each namespace */ + ngx_uint_t initial_pn; + ngx_uint_t handshake_pn; + ngx_uint_t appdata_pn; -static ngx_quic_stream_node_t *ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, - ngx_uint_t key); -static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); + ngx_quic_secrets_t secrets; + ngx_ssl_t *ssl; + ngx_quic_frame_t *frames; -static void ngx_quic_handshake_handler(ngx_event_t *rev); -static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); + ngx_quic_streams_t streams; +}; #if BORINGSSL_API_VERSION >= 10 @@ -84,6 +55,7 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len); #endif + static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); @@ -91,6 +63,44 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); +static void ngx_quic_handshake_handler(ngx_event_t *rev); +static void ngx_quic_close_connection(ngx_connection_t *c); + +static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); +static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); +static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); + +static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, + ngx_quic_frame_t *frame); + +static ngx_int_t ngx_quic_output(ngx_connection_t *c); +ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total); +static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, + ngx_quic_connection_t *qc, enum ssl_encryption_level_t level, + ngx_str_t *payload); + + +static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_quic_stream_node_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + ngx_uint_t key); 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, @@ -98,6 +108,7 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 ngx_quic_set_read_secret, @@ -118,6 +129,142 @@ ngx_quic_init_ssl_methods(SSL_CTX* ctx) } +#if BORINGSSL_API_VERSION >= 10 + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_quic_hexdump(c->log, "level:%d read secret", + rsecret, secret_len, level); + + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + rsecret, secret_len, + &c->quic->secrets.client); +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_quic_hexdump(c->log, "level:%d write secret", + wsecret, secret_len, level); + + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + wsecret, secret_len, + &c->quic->secrets.server); +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_int_t rc; + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); + ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); + + rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + rsecret, secret_len, + &c->quic->secrets.client); + if (rc != 1) { + return rc; + } + + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, + wsecret, secret_len, + &c->quic->secrets.server); +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = c->quic; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_add_handshake_data"); + + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; + } + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, data, len); + + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.len = len; + frame->u.crypto.data = p; + + ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_send_alert(), lvl=%d, alert=%d", + (int) level, (int) alert); + + return 1; +} + + void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, ngx_connection_handler_pt handler) @@ -144,8 +291,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, } // we don't need stream handler for initial packet processing - c->quic->stream_handler = handler; - c->quic->stream_timeout = timeout; + c->quic->streams.handler = handler; + c->quic->streams.timeout = timeout; ngx_add_timer(c->read, timeout); @@ -155,52 +302,183 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, } -static void -ngx_quic_handshake_handler(ngx_event_t *rev) +static ngx_int_t +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_header_t *pkt) { - ssize_t n; - ngx_connection_t *c; - u_char buf[512]; - ngx_buf_t b; + ngx_quic_connection_t *qc; - b.start = buf; - b.end = buf + 512; - b.pos = b.last = b.start; + if (ngx_buf_size(pkt->raw) < 1200) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); + return NGX_ERROR; + } - c = rev->data; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid initial packet: 0x%xi", pkt->flags); + return NGX_ERROR; + } - if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); - ngx_quic_close_connection(c); - return; + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { + return NGX_ERROR; } - if (c->close) { - ngx_quic_close_connection(c); - return; + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NGX_ERROR; } - n = c->recv(c, b.start, b.end - b.start); + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); - if (n == NGX_AGAIN) { - return; - } + c->quic = qc; + qc->ssl = ssl; - if (n == NGX_ERROR) { - c->read->eof = 1; - ngx_quic_close_connection(c); - return; + qc->dcid.len = pkt->dcid.len; + qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; } + ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); - b.last += n; + qc->scid.len = pkt->scid.len; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - if (ngx_quic_input(c, &b) != NGX_OK) { - ngx_quic_close_connection(c); - return; + qc->token.len = pkt->token.len; + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + return NGX_ERROR; } -} + ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); + + + if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) + != NGX_OK) + { + return NGX_ERROR; + } + + pkt->secret = &qc->secrets.client.in; + pkt->level = ssl_encryption_initial; + + if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_quic_payload_handler(c, pkt); +} + + +static ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + int n, sslerr; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + static const uint8_t params[] = + "\x00\x29" /* parameters length: 41 bytes */ + "\x00\x0e\x00\x01\x05" /* active connection id limit: 5 */ + "\x00\x04\x00\x04\x80\x98\x96\x80" /* initial max data = 10000000 */ + "\x00\x09\x00\x01\x03" /* initial max streams uni: 3 */ + "\x00\x08\x00\x01\x10" /* initial max streams bidi: 16 */ + "\x00\x05\x00\x02\x40\xff" /* initial max stream bidi local: 255 */ + "\x00\x06\x00\x02\x40\xff" /* initial max stream bidi remote: 255 */ + "\x00\x07\x00\x02\x40\xff"; /* initial max stream data uni: 255 */ + + qc = c->quic; + + if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + + if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n == -1) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + return NGX_OK; +} + + +static void +ngx_quic_handshake_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_buf_t b; + ngx_connection_t *c; + + u_char buf[512]; + + b.start = buf; + b.end = buf + 512; + b.pos = b.last = b.start; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_quic_close_connection(c); + return; + } + + if (c->close) { + ngx_quic_close_connection(c); + return; + } + + n = c->recv(c, b.start, b.end - b.start); + + if (n == NGX_AGAIN) { + return; + } + + if (n == NGX_ERROR) { + c->read->eof = 1; + ngx_quic_close_connection(c); + return; + } + + b.last += n; + + if (ngx_quic_input(c, &b) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } +} static void @@ -231,14 +509,6 @@ ngx_quic_close_connection(ngx_connection_t *c) } -ngx_connection_t * -ngx_quic_create_uni_stream(ngx_connection_t *c) -{ - /* XXX */ - return NULL; -} - - static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) { @@ -299,317 +569,305 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) return NGX_OK; } + static ngx_int_t -ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, - enum ssl_encryption_level_t level, ngx_str_t *payload) +ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_str_t res; - ngx_quic_header_t pkt; - - pkt.log = c->log; - - static ngx_str_t initial_token = ngx_null_string; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); - - pkt.level = level; - pkt.dcid = qc->dcid; - pkt.scid = qc->scid; - - if (level == ssl_encryption_initial) { - pkt.number = &qc->initial_pn; - pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->secrets.server.in; - pkt.token = initial_token; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; - } else if (level == ssl_encryption_handshake) { - pkt.number = &qc->handshake_pn; - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->secrets.server.hs; + qc = c->quic; + ssl_conn = c->ssl->connection; - } else { - pkt.number = &qc->appdata_pn; - pkt.secret = &qc->secrets.server.ad; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; } - if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) - != NGX_OK) - { + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { return NGX_ERROR; } - ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); - - c->send(c, res.data, res.len); // TODO: err handling + pkt->secret = &qc->secrets.client.in; + pkt->level = ssl_encryption_initial; - (*pkt.number)++; + if (ngx_quic_decrypt(c->pool, ssl_conn, pkt) != NGX_OK) { + return NGX_ERROR; + } - return NGX_OK; + return ngx_quic_payload_handler(c, pkt); } -/* pack a group of frames [start; end) into memory p and send as single packet */ -ngx_int_t -ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, - ngx_quic_frame_t *end, size_t total) +static ngx_int_t +ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ssize_t len; - u_char *p; - ngx_str_t out; - ngx_quic_frame_t *f; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sending frames %p...%p", start, end); + qc = c->quic; + ssl_conn = c->ssl->connection; - p = ngx_pnalloc(c->pool, total); - if (p == NULL) { + /* extract cleartext data into pkt */ + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; } - out.data = p; + if (pkt->dcid.len != qc->dcid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); + return NGX_ERROR; + } - for (f = start; f != end; f = f->next) { + if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); + if (pkt->scid.len != qc->scid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); + return NGX_ERROR; + } - len = ngx_quic_create_frame(p, p + total, f); - if (len == -1) { - return NGX_ERROR; - } + if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); + return NGX_ERROR; + } - p += len; + if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid packet type: 0x%xi", pkt->flags); + return NGX_ERROR; } - out.len = p - out.data; + if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { + return NGX_ERROR; + } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "packet ready: %ui bytes at level %d", - out.len, start->level); + pkt->secret = &qc->secrets.client.hs; + pkt->level = ssl_encryption_handshake; - // IOVEC/sendmsg_chain ? - if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { + if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { return NGX_ERROR; } - return NGX_OK; + return ngx_quic_payload_handler(c, pkt); } static ngx_int_t -ngx_quic_output(ngx_connection_t *c) +ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - size_t len; - ngx_uint_t lvl; - ngx_quic_frame_t *f, *start; ngx_quic_connection_t *qc; qc = c->quic; - if (qc->frames == NULL) { - return NGX_OK; + if (qc->secrets.client.ad.key.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "no read keys yet, packet ignored"); + return NGX_DECLINED; } - lvl = qc->frames->level; - start = qc->frames; - f = start; + if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { + return NGX_ERROR; + } - do { - len = 0; - - do { - /* process same-level group of frames */ + pkt->secret = &qc->secrets.client.ad; + pkt->level = ssl_encryption_application; - len += ngx_quic_frame_len(f);// TODO: handle overflow, max size + if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { + return NGX_ERROR; + } - f = f->next; - } while (f && f->level == lvl); + return ngx_quic_payload_handler(c, pkt); +} - if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { - return NGX_ERROR; - } +static ngx_int_t +ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_uint_t ack_this, do_close; + ngx_quic_frame_t frame, *ack_frame; + ngx_quic_connection_t *qc; - if (f == NULL) { - break; - } + qc = c->quic; - lvl = f->level; // TODO: must not decrease (ever, also between calls) - start = f; + p = pkt->payload.data; + end = p + pkt->payload.len; - } while (1); + ack_this = 0; + do_close = 0; - qc->frames = NULL; + while (p < end) { - return NGX_OK; -} + len = ngx_quic_parse_frame(p, end, &frame); + if (len < 0) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "failed to parse frame type %xi", frame.type); + return NGX_ERROR; + } + p += len; -#if BORINGSSL_API_VERSION >= 10 + switch (frame.type) { -static int -ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *rsecret, size_t secret_len) -{ - ngx_connection_t *c; + case NGX_QUIC_FT_ACK: - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", + frame.u.ack.largest, + frame.u.ack.delay, + frame.u.ack.first_range, + frame.u.ack.range_count); - ngx_quic_hexdump(c->log, "level:%d read secret", - rsecret, secret_len, level); + if (ngx_quic_handle_ack_frame(c, pkt, &frame.u.ack) != NGX_OK) { + return NGX_ERROR; + } - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - rsecret, secret_len, - &c->quic->secrets.client); -} + break; + case NGX_QUIC_FT_CRYPTO: -static int -ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_connection_t *c; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic CRYPTO frame length: %uL off:%uL pp:%p", + frame.u.crypto.len, frame.u.crypto.offset, + frame.u.crypto.data); - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + ngx_quic_hexdump0(c->log, "CRYPTO frame contents", + frame.u.crypto.data, frame.u.crypto.len); - ngx_quic_hexdump(c->log, "level:%d write secret", - wsecret, secret_len, level); - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - wsecret, secret_len, - &c->quic->secrets.server); -} + if (ngx_quic_handle_crypto_frame(c, pkt, &frame.u.crypto) + != NGX_OK) + { + return NGX_ERROR; + } -#else + ack_this = 1; + break; -static int -ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *rsecret, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_int_t rc; - ngx_connection_t *c; + case NGX_QUIC_FT_PADDING: + break; - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + case NGX_QUIC_FT_PING: + ack_this = 1; + break; - ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); - ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); + case NGX_QUIC_FT_NEW_CONNECTION_ID: + ack_this = 1; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "NCID: { seq=%ui retire=%ui len=%ui}", + frame.u.ncid.seqnum, + frame.u.ncid.retire, + frame.u.ncid.len); + break; - rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - rsecret, secret_len, - &c->quic->secrets.client); - if (rc != 1) { - return rc; - } + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + ngx_quic_error_text(frame.u.close.error_code), + frame.u.close.error_code, + frame.u.close.frame_type, + &frame.u.close.reason); - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - wsecret, secret_len, - &c->quic->secrets.server); -} + do_close = 1; + break; -#endif + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAM frame { 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d }", + frame.type, + frame.u.stream.stream_id, + frame.u.stream.offset, + frame.u.stream.length, + frame.u.stream.off, + frame.u.stream.len, + frame.u.stream.fin); -static void -ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) -{ - ngx_quic_frame_t *f; + ngx_quic_hexdump0(c->log, "STREAM frame contents", + frame.u.stream.data, frame.u.stream.length); - if (qc->frames == NULL) { - qc->frames = frame; - return; - } + if (ngx_quic_handle_stream_frame(c, pkt, &frame.u.stream) + != NGX_OK) + { + return NGX_ERROR; + } - for (f = qc->frames; f->next; f = f->next) { - if (f->next->level > frame->level) { + ack_this = 1; break; + + default: + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unsupported frame type 0x%xd in packet", frame.type); + return NGX_ERROR; } } - frame->next = f->next; - f->next = frame; -} - - -static int -ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len) -{ - u_char *p; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = c->quic; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data"); - - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return 0; + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "trailing garbage in payload: %ui bytes", end - p); + return NGX_ERROR; } - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - return 0; + if (do_close) { + // TODO: handle stream close } - ngx_memcpy(p, data, len); - - frame->level = level; - frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.len = len; - frame->u.crypto.data = p; - - ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); - - ngx_quic_queue_frame(qc, frame); - - return 1; -} + if (ack_this == 0) { + /* do not ack packets with ACKs and PADDING */ + return NGX_OK; + } + // packet processed, ACK it now if required + // TODO: if (ack_required) ... - currently just ack each packet -static int -ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) -{ - ngx_connection_t *c; + ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (ack_frame == NULL) { + return NGX_ERROR; + } - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + ack_frame->level = pkt->level; + ack_frame->type = NGX_QUIC_FT_ACK; + ack_frame->u.ack.pn = pkt->pn; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); + ngx_quic_queue_frame(qc, ack_frame); - return 1; + return ngx_quic_output(c); } -static int -ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, - uint8_t alert) +static ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_ack_frame_t *f) { - ngx_connection_t *c; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_send_alert(), lvl=%d, alert=%d", - (int) level, (int) alert); - - return 1; + /* TODO: handle ACK here */ + return NGX_OK; } - static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) + ngx_quic_crypto_frame_t *f) { - int sslerr; - ssize_t n; - ngx_ssl_conn_t *ssl_conn; + int sslerr; + ssize_t n; + ngx_ssl_conn_t *ssl_conn; + + if (f->offset != 0x0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "crypto frame with non-zero offset"); + // TODO: add support for crypto frames spanning packets + return NGX_ERROR; + } ssl_conn = c->ssl->connection; @@ -618,9 +876,8 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - frame->u.crypto.data, frame->u.crypto.len)) + f->data, f->len)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "SSL_provide_quic_data() failed"); @@ -654,449 +911,281 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } - static ngx_int_t -ngx_quic_init_connection(ngx_connection_t *c) +ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) { - int n, sslerr; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - static const uint8_t params[] = - "\x00\x29" /* parameters length: 41 bytes */ - "\x00\x0e\x00\x01\x05" /* active connection id limit: 5 */ - "\x00\x04\x00\x04\x80\x98\x96\x80" /* initial max data = 10000000 */ - "\x00\x09\x00\x01\x03" /* initial max streams uni: 3 */ - "\x00\x08\x00\x01\x10" /* initial max streams bidi: 16 */ - "\x00\x05\x00\x02\x40\xff" /* initial max stream bidi local: 255 */ - "\x00\x06\x00\x02\x40\xff" /* initial max stream bidi remote: 255 */ - "\x00\x07\x00\x02\x40\xff"; /* initial max stream data uni: 255 */ + ngx_buf_t *b; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_event_t *rev, *wev; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; qc = c->quic; - if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { - return NGX_ERROR; - } + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - ssl_conn = c->ssl->connection; + if (sn) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); + b = sn->b; - if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "SSL_set_quic_transport_params() failed"); - return NGX_ERROR; - } - - n = SSL_do_handshake(ssl_conn); + if ((size_t) (b->end - b->pos) < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + return NGX_ERROR; + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + ngx_memcpy(b->pos, f->data, f->length); + b->pos += f->length; - if (n == -1) { - sslerr = SSL_get_error(ssl_conn, n); + // TODO: notify - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); + return NGX_OK; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - return NGX_OK; -} - - -static ssize_t -ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) -{ - ssize_t len; - ngx_buf_t *b; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; - - qs = c->qs; - qc = qs->parent->quic; - - // XXX: get direct pointer from stream structure? - sn = ngx_quic_stream_lookup(&qc->stree, qs->id); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); + sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); if (sn == NULL) { return NGX_ERROR; } - // XXX: how to return EOF? - - b = sn->b; - - if (b->last - b->pos == 0) { - c->read->ready = 0; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv() not ready"); - return NGX_AGAIN; // ? + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination + if (sn->c == NULL) { + return NGX_ERROR; } - len = ngx_min(b->last - b->pos, (ssize_t) size); - - ngx_memcpy(buf, b->pos, len); + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + /* XXX free connection */ + return NGX_ERROR; + } - b->pos += len; + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + /* XXX free pool and connection */ + return NGX_ERROR; + } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: %z of %uz", len, size); + *log = *c->log; + pool->log = log; - return len; -} + sn->c->log = log; + sn->c->pool = pool; + sn->c->listening = c->listening; + sn->c->sockaddr = c->sockaddr; + sn->c->local_sockaddr = c->local_sockaddr; -static ssize_t -ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) -{ - u_char *p; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + rev = sn->c->read; + wev = sn->c->write; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); + rev->ready = 1; - qs = c->qs; - pc = qs->parent; - qc = pc->quic; + rev->log = c->log; + wev->log = c->log; - // XXX: get direct pointer from stream structure? - sn = ngx_quic_stream_lookup(&qc->stree, qs->id); + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - if (sn == NULL) { + sn->node.key = f->stream_id; + sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone + if (sn->b == NULL) { return NGX_ERROR; } + b = sn->b; - frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return 0; - } - - p = ngx_pnalloc(pc->pool, size); - if (p == NULL) { - return 0; - } + ngx_memcpy(b->start, f->data, f->length); + b->last = b->start + f->length; - ngx_memcpy(p, buf, size); + ngx_rbtree_insert(&qc->streams.tree, &sn->node); - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; + sn->s.id = f->stream_id; + sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; + sn->s.parent = c; + sn->c->qs = &sn->s; - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = size; - frame->u.stream.data = p; + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; - c->sent += size; + qc->streams.handler(sn->c); - ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", - qs->id, size, frame->level); - - ngx_quic_queue_frame(qc, frame); - - return size; + return NGX_OK; } -static ngx_chain_t * -ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, - off_t limit) +static void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - size_t len; - ssize_t n; - ngx_buf_t *b; - - for ( /* void */; in; in = in->next) { - b = in->buf; - - if (!ngx_buf_in_memory(b)) { - continue; - } - - if (ngx_buf_size(b) == 0) { - continue; - } - - len = b->last - b->pos; - - n = ngx_quic_stream_send(c, b->pos, len); - - if (n == NGX_ERROR) { - return NGX_CHAIN_ERROR; - } + ngx_quic_frame_t *f; - if (n == NGX_AGAIN) { - return in; - } + if (qc->frames == NULL) { + qc->frames = frame; + return; + } - if (n != (ssize_t) len) { - b->pos += n; - return in; + for (f = qc->frames; f->next; f = f->next) { + if (f->next->level > frame->level) { + break; } } - return NULL; + frame->next = f->next; + f->next = frame; } -/* process all payload from the current packet and generate ack if required */ static ngx_int_t -ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_output(ngx_connection_t *c) { - u_char *end, *p; - ssize_t len; - ngx_buf_t *b; - ngx_log_t *log; - ngx_uint_t ack_this, do_close; - ngx_pool_t *pool; - ngx_event_t *rev, *wev; - ngx_quic_frame_t frame, *ack_frame; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + size_t len; + ngx_uint_t lvl; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; qc = c->quic; - p = pkt->payload.data; - end = p + pkt->payload.len; - - ack_this = 0; - do_close = 0; - - while (p < end) { - - len = ngx_quic_parse_frame(p, end, &frame); - if (len < 0) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "unknown frame type %xi", frame.type); - // XXX: log here - return NGX_ERROR; - } - - p += len; - - switch (frame.type) { - - case NGX_QUIC_FT_ACK: - - // TODO: handle ack - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", - frame.u.ack.largest, - frame.u.ack.delay, - frame.u.ack.first_range, - frame.u.ack.range_count); - - break; - - case NGX_QUIC_FT_CRYPTO: - ngx_quic_hexdump0(c->log, "CRYPTO frame", - frame.u.crypto.data, frame.u.crypto.len); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic CRYPTO frame length: %uL off:%uL pp:%p", - frame.u.crypto.len, frame.u.crypto.offset, - frame.u.crypto.data); - - if (frame.u.crypto.offset != 0x0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "crypto frame with non-zero offset"); - // TODO: support packet spanning with offsets - return NGX_ERROR; - } + if (qc->frames == NULL) { + return NGX_OK; + } - if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { - return NGX_ERROR; - } + lvl = qc->frames->level; + start = qc->frames; + f = start; - ack_this = 1; + do { + len = 0; - continue; + do { + /* process same-level group of frames */ - case NGX_QUIC_FT_PADDING: - continue; + len += ngx_quic_frame_len(f);// TODO: handle overflow, max size - case NGX_QUIC_FT_PING: - ack_this = 1; - continue; + f = f->next; + } while (f && f->level == lvl); - case NGX_QUIC_FT_NEW_CONNECTION_ID: - ack_this = 1; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "NCID: { seq=%ui retire=%ui len=%ui}", - frame.u.ncid.seqnum, - frame.u.ncid.retire, - frame.u.ncid.len); - continue; - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", - ngx_quic_error_text(frame.u.close.error_code), - frame.u.close.error_code, - frame.u.close.frame_type, - &frame.u.close.reason); + if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { + return NGX_ERROR; + } - do_close = 1; + if (f == NULL) { break; + } - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - ack_this = 1; - - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAM frame 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d", - frame.type, - frame.u.stream.stream_id, - frame.u.stream.offset, - frame.u.stream.length, - frame.u.stream.off, - frame.u.stream.len, - frame.u.stream.fin); - - sn = ngx_quic_stream_lookup(&qc->stree, frame.u.stream.stream_id); - if (sn == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); - - sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); - if (sn == NULL) { - return NGX_ERROR; - } - - sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination - if (sn->c == NULL) { - return NGX_ERROR; - } + lvl = f->level; // TODO: must not decrease (ever, also between calls) + start = f; - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - /* XXX free connection */ - return NGX_ERROR; - } + } while (1); - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - /* XXX free pool and connection */ - return NGX_ERROR; - } + qc->frames = NULL; - *log = *c->log; - pool->log = log; + return NGX_OK; +} - sn->c->log = log; - sn->c->pool = pool; - sn->c->listening = c->listening; - sn->c->sockaddr = c->sockaddr; - sn->c->local_sockaddr = c->local_sockaddr; +/* pack a group of frames [start; end) into memory p and send as single packet */ +ngx_int_t +ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, + ngx_quic_frame_t *end, size_t total) +{ + ssize_t len; + u_char *p; + ngx_str_t out; + ngx_quic_frame_t *f; - rev = sn->c->read; - wev = sn->c->write; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending frames %p...%p", start, end); - rev->ready = 1; + p = ngx_pnalloc(c->pool, total); + if (p == NULL) { + return NGX_ERROR; + } - rev->log = c->log; - wev->log = c->log; + out.data = p; - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + for (f = start; f != end; f = f->next) { - sn->node.key = frame.u.stream.stream_id; - sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone - if (sn->b == NULL) { - return NGX_ERROR; - } - b = sn->b; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); - ngx_memcpy(b->start, frame.u.stream.data, frame.u.stream.length); - b->last = b->start + frame.u.stream.length; + len = ngx_quic_create_frame(p, p + total, f); + if (len == -1) { + return NGX_ERROR; + } - ngx_rbtree_insert(&qc->stree, &sn->node); + p += len; + } - sn->s.id = frame.u.stream.stream_id; - sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; - sn->s.parent = c; - sn->c->qs = &sn->s; + out.len = p - out.data; - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "packet ready: %ui bytes at level %d", + out.len, start->level); - qc->stream_handler(sn->c); + // IOVEC/sendmsg_chain ? + if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { + return NGX_ERROR; + } - } else { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); - b = sn->b; + return NGX_OK; +} - if ((size_t) (b->end - b->pos) < frame.u.stream.length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "no space in stream buffer"); - return NGX_ERROR; - } - ngx_memcpy(b->pos, frame.u.stream.data, frame.u.stream.length); - b->pos += frame.u.stream.length; +static ngx_int_t +ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, + enum ssl_encryption_level_t level, ngx_str_t *payload) +{ + ngx_str_t res; + ngx_quic_header_t pkt; - // TODO: ngx_post_event(&c->read, &ngx_posted_events) ??? - } + pkt.log = c->log; - ngx_quic_hexdump0(c->log, "STREAM.data", - frame.u.stream.data, frame.u.stream.length); - break; + static ngx_str_t initial_token = ngx_null_string; - default: - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unexpected frame type 0x%xd in packet", frame.type); - return NGX_ERROR; - } - } + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); - if (p != end) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "trailing garbage in payload: %ui bytes", end - p); - return NGX_ERROR; - } + pkt.level = level; + pkt.dcid = qc->dcid; + pkt.scid = qc->scid; - if (do_close) { - // TODO: handle stream close - } + if (level == ssl_encryption_initial) { + pkt.number = &qc->initial_pn; + pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.secret = &qc->secrets.server.in; + pkt.token = initial_token; - if (ack_this == 0) { - /* do not ack packets with ACKs and PADDING */ - return NGX_OK; - } + } else if (level == ssl_encryption_handshake) { + pkt.number = &qc->handshake_pn; + pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.secret = &qc->secrets.server.hs; - // packet processed, ACK it now if required - // TODO: if (ack_required) ... - currently just ack each packet + } else { + pkt.number = &qc->appdata_pn; + pkt.secret = &qc->secrets.server.ad; + } - ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (ack_frame == NULL) { + if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) + != NGX_OK) + { return NGX_ERROR; } - ack_frame->level = pkt->level; - ack_frame->type = NGX_QUIC_FT_ACK; - ack_frame->u.ack.pn = pkt->pn; + ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); - ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); - ngx_quic_queue_frame(qc, ack_frame); + c->send(c, res.data, res.len); // TODO: err handling - return ngx_quic_output(c); + (*pkt.number)++; + + return NGX_OK; +} + + +ngx_connection_t * +ngx_quic_create_uni_stream(ngx_connection_t *c) +{ + /* XXX */ + return NULL; } @@ -1104,8 +1193,8 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { - ngx_rbtree_node_t **p; - ngx_quic_stream_node_t *qn, *qnt; + ngx_rbtree_node_t **p; + ngx_quic_stream_node_t *qn, *qnt; for ( ;; ) { @@ -1145,7 +1234,7 @@ ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, static ngx_quic_stream_node_t * -ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, ngx_uint_t key) +ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) { ngx_rbtree_node_t *node, *sentinel; @@ -1165,193 +1254,143 @@ ngx_quic_stream_lookup(ngx_rbtree_t *rbtree, ngx_uint_t key) } -static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_header_t *pkt) +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ngx_quic_connection_t *qc; - - if (ngx_buf_size(pkt->raw) < 1200) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); - return NGX_ERROR; - } - - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; - } + ssize_t len; + ngx_buf_t *b; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; - if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%xi", pkt->flags); - return NGX_ERROR; - } + qs = c->qs; + qc = qs->parent->quic; - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; - } + // XXX: get direct pointer from stream structure? + sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { + if (sn == NULL) { return NGX_ERROR; } - ngx_rbtree_init(&qc->stree, &qc->stree_sentinel, - ngx_quic_rbtree_insert_stream); - - c->quic = qc; - qc->ssl = ssl; - - qc->dcid.len = pkt->dcid.len; - qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); + // XXX: how to return EOF? - qc->scid.len = pkt->scid.len; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); - if (qc->scid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); + b = sn->b; - qc->token.len = pkt->token.len; - qc->token.data = ngx_pnalloc(c->pool, qc->token.len); - if (qc->token.data == NULL) { - return NGX_ERROR; + if (b->last - b->pos == 0) { + c->read->ready = 0; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv() not ready"); + return NGX_AGAIN; // ? } - ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); + len = ngx_min(b->last - b->pos, (ssize_t) size); - if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) - != NGX_OK) - { - return NGX_ERROR; - } - - pkt->secret = &qc->secrets.client.in; - pkt->level = ssl_encryption_initial; + ngx_memcpy(buf, b->pos, len); - if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { - return NGX_ERROR; - } + b->pos += len; - if (ngx_quic_init_connection(c) != NGX_OK) { - return NGX_ERROR; - } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv: %z of %uz", len, size); - return ngx_quic_payload_handler(c, pkt); + return len; } -static ngx_int_t -ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) { - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = c->quic; - ssl_conn = c->ssl->connection; - - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - pkt->secret = &qc->secrets.client.in; - pkt->level = ssl_encryption_initial; - - if (ngx_quic_decrypt(c->pool, ssl_conn, pkt) != NGX_OK) { - return NGX_ERROR; - } - - return ngx_quic_payload_handler(c, pkt); -} + u_char *p; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); -static ngx_int_t -ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; + qs = c->qs; + pc = qs->parent; + qc = pc->quic; - qc = c->quic; - ssl_conn = c->ssl->connection; + // XXX: get direct pointer from stream structure? + sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); - /* extract cleartext data into pkt */ - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + if (sn == NULL) { return NGX_ERROR; } - if (pkt->dcid.len != qc->dcid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); - return NGX_ERROR; + frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; } - if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); - return NGX_ERROR; + p = ngx_pnalloc(pc->pool, size); + if (p == NULL) { + return 0; } - if (pkt->scid.len != qc->scid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); - return NGX_ERROR; - } + ngx_memcpy(p, buf, size); - if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); - return NGX_ERROR; - } + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; - if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%xi", pkt->flags); - return NGX_ERROR; - } + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = size; + frame->u.stream.data = p; - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { - return NGX_ERROR; - } + c->sent += size; - pkt->secret = &qc->secrets.client.hs; - pkt->level = ssl_encryption_handshake; + ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", + qs->id, size, frame->level); - if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { - return NGX_ERROR; - } + ngx_quic_queue_frame(qc, frame); - return ngx_quic_payload_handler(c, pkt); + return size; } -static ngx_int_t -ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit) { - ngx_quic_connection_t *qc; + size_t len; + ssize_t n; + ngx_buf_t *b; - qc = c->quic; + for ( /* void */; in; in = in->next) { + b = in->buf; - if (qc->secrets.client.ad.key.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "no read keys yet, packet ignored"); - return NGX_DECLINED; - } + if (!ngx_buf_in_memory(b)) { + continue; + } - if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_buf_size(b) == 0) { + continue; + } - pkt->secret = &qc->secrets.client.ad; - pkt->level = ssl_encryption_application; + len = b->last - b->pos; - if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { - return NGX_ERROR; - } + n = ngx_quic_stream_send(c, b->pos, len); - return ngx_quic_payload_handler(c, pkt); -} + if (n == NGX_ERROR) { + return NGX_CHAIN_ERROR; + } + + if (n == NGX_AGAIN) { + return in; + } + if (n != (ssize_t) len) { + b->pos += n; + return in; + } + } + return NULL; +} -- cgit v1.2.3 From 01dc7445f0fc392edd4f4e23f4fa1af69af68e41 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 13:46:35 +0300 Subject: Refactored HTTP/3 parser. --- auto/modules | 4 +- src/http/ngx_http_request.c | 14 +- src/http/ngx_http_request.h | 9 +- src/http/v3/ngx_http_v3.c | 80 --- src/http/v3/ngx_http_v3.h | 35 +- src/http/v3/ngx_http_v3_parse.c | 1424 +++++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_parse.h | 145 ++++ src/http/v3/ngx_http_v3_request.c | 137 +++- src/http/v3/ngx_http_v3_streams.c | 692 +++--------------- src/http/v3/ngx_http_v3_tables.c | 30 + 10 files changed, 1875 insertions(+), 695 deletions(-) create mode 100644 src/http/v3/ngx_http_v3_parse.c create mode 100644 src/http/v3/ngx_http_v3_parse.h diff --git a/auto/modules b/auto/modules index 24bbcb2ae..abd3aa4b9 100644 --- a/auto/modules +++ b/auto/modules @@ -412,8 +412,10 @@ if [ $HTTP = YES ]; then ngx_module_name=ngx_http_v3_module ngx_module_incs=src/http/v3 - ngx_module_deps=src/http/v3/ngx_http_v3.h + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_parse.h" ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_parse.c \ src/http/v3/ngx_http_v3_tables.c \ src/http/v3/ngx_http_v3_streams.c \ src/http/v3/ngx_http_v3_request.c \ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 85b090d5f..166d8ffba 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1163,7 +1163,7 @@ ngx_http_process_request_line(ngx_event_t *rev) switch (r->http_version) { #if (NGX_HTTP_V3) case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_header(r, r->header_in, 1); + rc = ngx_http_v3_parse_header(r, r->header_in); break; #endif @@ -1510,7 +1510,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) switch (r->http_version) { #if (NGX_HTTP_V3) case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_header(r, r->header_in, 0); + rc = ngx_http_v3_parse_header(r, r->header_in); break; #endif @@ -1547,11 +1547,17 @@ ngx_http_process_request_headers(ngx_event_t *rev) h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; - h->key.data[h->key.len] = '\0'; + + if (h->key.data[h->key.len]) { + h->key.data[h->key.len] = '\0'; + } h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; - h->value.data[h->value.len] = '\0'; + + if (h->value.data[h->value.len]) { + h->value.data[h->value.len] = '\0'; + } h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 04d88db7b..772d53b2d 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -595,14 +595,7 @@ struct ngx_http_request_s { u_char *port_end; #if (NGX_HTTP_V3) - ngx_uint_t h3_length; - ngx_uint_t h3_index; - ngx_uint_t h3_insert_count; - ngx_uint_t h3_sign; - ngx_uint_t h3_delta_base; - ngx_uint_t h3_huffman; - ngx_uint_t h3_dynamic; - ngx_uint_t h3_offset; + void *h3_parse; #endif unsigned http_minor:16; diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index e804cf6f5..cca84dbc1 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -94,83 +94,3 @@ ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) return (uintptr_t) p; } - - -uint64_t -ngx_http_v3_decode_varlen_int(u_char *p) -{ - uint64_t value; - ngx_uint_t len; - - len = *p >> 6; - value = *p & 0x3f; - - while (len--) { - value = (value << 8) + *p++; - } - - return value; -} - - -int64_t -ngx_http_v3_decode_prefix_int(u_char **src, size_t len, ngx_uint_t prefix) -{ - u_char *p; - int64_t value, thresh; - - if (len == 0) { - return NGX_ERROR; - } - - p = *src; - - thresh = (1 << prefix) - 1; - value = *p++ & thresh; - - if (value != thresh) { - *src = p; - return value; - } - - value = 0; - - /* XXX handle overflows */ - - while (--len) { - value = (value << 7) + (*p & 0x7f); - if ((*p++ & 0x80) == 0) { - *src = p; - return value + thresh; - } - } - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_decode_huffman(ngx_connection_t *c, ngx_str_t *s) -{ - u_char state, *p, *data; - - state = 0; - - p = ngx_pnalloc(c->pool, s->len * 8 / 5); - if (p == NULL) { - return NGX_ERROR; - } - - data = p; - - if (ngx_http_v2_huff_decode(&state, s->data, s->len, &p, 1, c->log) - != NGX_OK) - { - return NGX_ERROR; - } - - s->len = p - data; - s->data = data; - - return NGX_OK; -} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 8fa54d8e5..fc1cadd79 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -12,13 +12,31 @@ #include #include #include +#include #define NGX_HTTP_V3_STREAM 0x48335354 /* "H3ST" */ -#define NGX_HTTP_V3_VARLEN_INT_LEN 4 -#define NGX_HTTP_V3_PREFIX_INT_LEN 11 +#define NGX_HTTP_V3_VARLEN_INT_LEN 4 +#define NGX_HTTP_V3_PREFIX_INT_LEN 11 + +#define NGX_HTTP_V3_STREAM_CONTROL 0x00 +#define NGX_HTTP_V3_STREAM_PUSH 0x01 +#define NGX_HTTP_V3_STREAM_ENCODER 0x02 +#define NGX_HTTP_V3_STREAM_DECODER 0x03 + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + +#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 +#define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06 +#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 typedef struct { @@ -28,8 +46,11 @@ typedef struct { ngx_connection_t *client_encoder; ngx_connection_t *client_decoder; + ngx_connection_t *client_control; + ngx_connection_t *server_encoder; ngx_connection_t *server_decoder; + ngx_connection_t *server_control; } ngx_http_v3_connection_t; @@ -39,18 +60,12 @@ typedef struct { } ngx_http_v3_header_t; -ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, - ngx_uint_t pseudo); +ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b); ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); - uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix); -uint64_t ngx_http_v3_decode_varlen_int(u_char *p); -int64_t ngx_http_v3_decode_prefix_int(u_char **src, size_t len, - ngx_uint_t prefix); -ngx_int_t ngx_http_v3_decode_huffman(ngx_connection_t *c, ngx_str_t *s); void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c); @@ -67,6 +82,8 @@ ngx_http_v3_header_t *ngx_http_v3_lookup_table(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index); ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c new file mode 100644 index 000000000..e0b28a6c3 --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.c @@ -0,0 +1,1424 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +ngx_int_t +ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, u_char ch) +{ + enum { + sw_start = 0, + sw_length_2, + sw_length_3, + sw_length_4, + sw_length_5, + sw_length_6, + sw_length_7, + sw_length_8 + }; + + switch (st->state) { + + case sw_start: + + st->value = ch; + if (st->value & 0xc0) { + st->state = sw_length_2; + break; + } + + goto done; + + case sw_length_2: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc000) == 0x4000) { + st->value &= 0x3fff; + goto done; + } + + st->state = sw_length_3; + break; + + case sw_length_4: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc0000000) == 0x80000000) { + st->value &= 0x3fffffff; + goto done; + } + + st->state = sw_length_5; + break; + + case sw_length_3: + case sw_length_5: + case sw_length_6: + case sw_length_7: + + st->value = (st->value << 8) + ch; + st->state++; + break; + + case sw_length_8: + + st->value = (st->value << 8) + ch; + st->value &= 0x3fffffffffffffff; + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse varlen int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +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, u_char ch) +{ + enum { + sw_start = 0, + sw_value + }; + + switch (st->state) { + + case sw_start: + + st->mask = (1 << prefix) - 1; + st->value = (ch & st->mask); + + if (st->value != st->mask) { + goto done; + } + + st->value = 0; + st->state = sw_value; + break; + + case sw_value: + + st->value = (st->value << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + st->value += st->mask; + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse prefix int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, + u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_length, + sw_prefix, + sw_header_rep, + sw_done + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers"); + + if (ch != NGX_HTTP_V3_FRAME_HEADERS) { + return NGX_ERROR; + } + + st->state = sw_length; + break; + + case sw_length: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + st->length = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers len:%ui", st->length); + + st->state = sw_prefix; + break; + + case sw_prefix: + + if (st->length-- == 0) { + return NGX_ERROR; + } + + rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_DONE) { + break; + } + + if (st->length == 0) { + return NGX_ERROR; + } + + st->state = sw_header_rep; + break; + + case sw_header_rep: + + rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base, + ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (--st->length == 0) { + if (rc != NGX_DONE) { +ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "XXX len"); + return NGX_ERROR; + } + + goto done; + } + + if (rc == NGX_DONE) { + return NGX_OK; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, + ngx_http_v3_parse_header_block_prefix_t *st, u_char ch) +{ + enum { + sw_start = 0, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header block prefix"); + + st->state = sw_req_insert_count; + + /* fall through */ + + case sw_req_insert_count: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch) != NGX_DONE) { + break; + } + + st->insert_count = st->pint.value; + st->state = sw_delta_base; + break; + + case sw_delta_base: + + st->sign = (ch & 0x80) ? 1 : 0; + st->state = sw_read_delta_base; + + /* fall through */ + + case sw_read_delta_base: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->delta_base = st->pint.value; + goto done; + } + + return NGX_AGAIN; + +done: + + if (st->sign) { + 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, + "http3 parse header block prefix done " + "i:%ui, s:%ui, d:%ui, base:%uL", + st->insert_count, st->sign, st->delta_base, st->base); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_rep(ngx_connection_t *c, + ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_header_ri, + sw_header_lri, + sw_header_l, + sw_header_pbi, + sw_header_lpbi + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header representation"); + + ngx_memzero(&st->header, sizeof(ngx_http_v3_parse_header_t)); + + st->header.base = base; + + if (ch & 0x80) { + /* Indexed Header Field */ + + st->state = sw_header_ri; + + } else if (ch & 0x40) { + /* Literal Header Field With Name Reference */ + + st->state = sw_header_lri; + + } else if (ch & 0x20) { + /* Literal Header Field Without Name Reference */ + + st->state = sw_header_l; + + } else if (ch & 0x10) { + /* Indexed Header Field With Post-Base Index */ + + st->state = sw_header_pbi; + + } else { + /* Literal Header Field With Post-Base Name Reference */ + + st->state = sw_header_lpbi; + } + } + + switch (st->state) { + + case sw_header_ri: + rc = ngx_http_v3_parse_header_ri(c, &st->header, ch); + break; + + case sw_header_lri: + rc = ngx_http_v3_parse_header_lri(c, &st->header, ch); + break; + + case sw_header_l: + rc = ngx_http_v3_parse_header_l(c, &st->header, ch); + break; + + case sw_header_pbi: + rc = ngx_http_v3_parse_header_pbi(c, &st->header, ch); + break; + + case sw_header_lpbi: + rc = ngx_http_v3_parse_header_lpbi(c, &st->header, ch); + break; + + default: + rc = NGX_OK; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* rc == NGX_DONE */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header representation done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, + u_char ch) +{ + ngx_uint_t n; + enum { + sw_start = 0, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal huff:%ui, len:%ui", + st->huffman, st->length); + + n = st->length; + + if (st->huffman) { + n = n * 8 / 5; + st->huffstate = 0; + } + + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } + + st->value.data = st->last; + st->state = sw_value; + + /* fall through */ + + case sw_value: + + if (st->huffman) { + if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last, + st->length == 1, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + *st->last++ = ch; + } + + if (--st->length) { + break; + } + + st->value.len = st->last - st->value.data; + *st->last = '\0'; + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal done \"%V\"", &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, + u_char ch) +{ + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_index + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header ri"); + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + break; + } + + st->index = st->pint.value; + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header ri done %s%ui]", + st->dynamic ? "dynamic[-" : "static[", st->index); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + h = ngx_http_v3_lookup_table(c, st->dynamic, st->index); + if (h == NULL) { + return NGX_ERROR; + } + + st->name = h->name; + st->value = h->value; + st->state = sw_start; + + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_lri(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_int_t rc; + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header lri"); + + st->dynamic = (ch & 0x10) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) { + break; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->value = st->literal.value; + goto done; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header lri done %s%ui] \"%V\"", + st->dynamic ? "dynamic[-" : "static[", + st->index, &st->value); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + h = ngx_http_v3_lookup_table(c, st->dynamic, st->index); + if (h == NULL) { + return NGX_ERROR; + } + + st->name = h->name; + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_l(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header l"); + + st->literal.huffman = (ch & 0x08) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->name = st->literal.value; + st->state = sw_value_len; + } + + break; + + case sw_value_len: + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->value = st->literal.value; + goto done; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header l done \"%V\" \"%V\"", + &st->name, &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_pbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_index + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) { + break; + } + + st->index = st->pint.value; + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header pbi done dynamic[+%ui]", st->index); + + h = ngx_http_v3_lookup_table(c, 1, st->base + st->index); + if (h == NULL) { + return NGX_ERROR; + } + + st->name = h->name; + st->value = h->value; + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_int_t rc; + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header lpbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) { + break; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->value = st->literal.value; + goto done; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header lpbi done dynamic[+%ui] \"%V\"", + st->index, &st->value); + + h = ngx_http_v3_lookup_table(c, 1, st->base + st->index); + if (h == NULL) { + return NGX_ERROR; + } + + st->name = h->name; + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) +{ + ngx_http_v3_parse_control_t *st = data; + + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_settings, + sw_max_push_id, + sw_skip + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse control"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + st->type = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame type:%ui", st->type); + + st->state = sw_length; + break; + + case sw_length: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame len:%uL", st->vlint.value); + + st->length = st->vlint.value; + if (st->length == 0) { + st->state = sw_type; + break; + } + + switch (st->type) { + + case NGX_HTTP_V3_FRAME_SETTINGS: + st->state = sw_settings; + break; + + case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: + st->state = sw_max_push_id; + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse skip unknown frame"); + st->state = sw_skip; + } + + break; + + case sw_settings: + + rc = ngx_http_v3_parse_settings(c, &st->settings, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (--st->length > 0) { + break; + } + + if (rc != NGX_DONE) { + return NGX_ERROR; + } + + st->state = sw_type; + break; + + case sw_max_push_id: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse MAX_PUSH_ID:%uL", st->vlint.value); + + st->state = sw_type; + break; + + case sw_skip: + + if (--st->length == 0) { + st->state = sw_type; + } + + break; + } + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, u_char ch) +{ + enum { + sw_start = 0, + sw_id, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings"); + + st->state = sw_id; + + /* fall through */ + + case sw_id: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + st->id = st->vlint.value; + st->state = sw_value; + break; + + case sw_value: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) +{ + ngx_http_v3_parse_encoder_t *st = data; + + ngx_int_t rc; + enum { + sw_start = 0, + sw_inr, + sw_iwnr, + sw_capacity, + sw_duplicate + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction"); + + if (ch & 0x80) { + /* Insert With Name Reference */ + + st->state = sw_inr; + + } else if (ch & 0x40) { + /* Insert Without Name Reference */ + + st->state = sw_iwnr; + + } else if (ch & 0x20) { + /* Set Dynamic Table Capacity */ + + st->state = sw_capacity; + + } else { + /* Duplicate */ + + st->state = sw_duplicate; + } + } + + switch (st->state) { + + case sw_inr: + + rc = ngx_http_v3_parse_header_inr(c, &st->header, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_DONE) { + break; + } + + goto done; + + case sw_iwnr: + + rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc != NGX_DONE) { + break; + } + + goto done; + + case sw_capacity: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_set_capacity(c, st->pint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + + case sw_duplicate: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_duplicate(c, st->pint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_inr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header inr"); + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_name_index; + + /* fall through */ + + case sw_name_index: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + break; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->value = st->literal.value; + goto done; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header inr done %s[%ui] \"%V\"", + st->dynamic ? "dynamic" : "static", + st->index, &st->value); + + if (ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value) != NGX_OK) + { + return NGX_ERROR; + } + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header iwnr"); + + st->literal.huffman = (ch & 0x20) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->name = st->literal.value; + st->state = sw_value_len; + } + + break; + + case sw_value_len: + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { + break; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, ch); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + st->value = st->literal.value; + goto done; + } + + break; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header iwnr done \"%V\":\"%V\"", + &st->name, &st->value); + + if (ngx_http_v3_insert(c, &st->name, &st->value) != NGX_OK) { + return NGX_ERROR; + } + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) +{ + ngx_http_v3_parse_decoder_t *st = data; + + enum { + sw_start = 0, + sw_ack_header, + sw_cancel_stream, + sw_inc_insert_count + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction"); + + if (ch & 0x80) { + /* Header Acknowledgement */ + + st->state = sw_ack_header; + + } else if (ch & 0x40) { + /* Stream Cancellation */ + + st->state = sw_cancel_stream; + + } else { + /* Insert Count Increment */ + + st->state = sw_inc_insert_count; + } + } + + switch (st->state) { + + case sw_ack_header: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_ack_header(c, st->pint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + + case sw_cancel_stream: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_cancel_stream(c, st->pint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + + case sw_inc_insert_count: + + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + break; + } + + if (ngx_http_v3_inc_insert_count(c, st->pint.value) != NGX_OK) { + return NGX_ERROR; + } + + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction done"); + + st->state = sw_start; + return NGX_DONE; +} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h new file mode 100644 index 000000000..959da7941 --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.h @@ -0,0 +1,145 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_ +#define _NGX_HTTP_V3_PARSE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_uint_t state; + uint64_t value; +} ngx_http_v3_parse_varlen_int_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t mask; + uint64_t value; +} ngx_http_v3_parse_prefix_int_t; + + +typedef struct { + ngx_uint_t state; + uint64_t id; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_settings_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t insert_count; + ngx_uint_t delta_base; + ngx_uint_t sign; + ngx_uint_t base; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_header_block_prefix_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_uint_t huffman; + ngx_str_t value; + u_char *last; + u_char huffstate; +} ngx_http_v3_parse_literal_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t index; + ngx_uint_t base; + ngx_uint_t dynamic; + + ngx_str_t name; + ngx_str_t value; + + ngx_http_v3_parse_prefix_int_t pint; + ngx_http_v3_parse_literal_t literal; +} ngx_http_v3_parse_header_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_header_t header; +} ngx_http_v3_parse_header_rep_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_header_block_prefix_t prefix; + ngx_http_v3_parse_header_rep_t header_rep; +} ngx_http_v3_parse_headers_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_header_t header; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_encoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_decoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_settings_t settings; +} ngx_http_v3_parse_control_t; + + +ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, u_char ch); +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, u_char ch); + +ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, + ngx_http_v3_parse_headers_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, + ngx_http_v3_parse_header_block_prefix_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c, + ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch); +ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, + ngx_http_v3_parse_literal_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); + +ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch); +ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, u_char ch); + +ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch); +ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); + +ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch); + + +#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 9cb351c2d..e6cd27183 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,15 +10,6 @@ #include -#define NGX_HTTP_V3_FRAME_DATA 0x00 -#define NGX_HTTP_V3_FRAME_HEADERS 0x01 -#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 -#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 -#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 -#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 -#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); @@ -46,6 +37,110 @@ struct { }; +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_str_t *name, *value; + ngx_connection_t *c; + ngx_http_v3_parse_headers_t *st; + enum { + sw_start = 0, + sw_prev, + sw_headers, + sw_last, + sw_done + }; + + c = r->connection; + st = r->h3_parse; + + if (st == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header"); + + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); + if (st == NULL) { + goto failed; + } + + r->h3_parse = st; + } + + switch (r->state) { + + case sw_prev: + r->state = sw_headers; + return NGX_OK; + + case sw_done: + goto done; + + case sw_last: + r->state = sw_done; + return NGX_OK; + + default: + break; + } + + for ( /* void */ ; b->pos < b->last; b->pos++) { + + rc = ngx_http_v3_parse_headers(c, st, *b->pos); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_AGAIN) { + continue; + } + + name = &st->header_rep.header.name; + value = &st->header_rep.header.value; + + if (r->state == sw_start + && ngx_http_v3_process_pseudo_header(r, name, value) != NGX_OK) + { + if (rc == NGX_DONE) { + r->state = sw_last; + } else { + r->state = sw_prev; + } + + } else if (rc == NGX_DONE) { + r->state = sw_done; + } + + if (r->state == sw_start) { + continue; + } + + r->header_name_start = name->data; + r->header_name_end = name->data + name->len; + r->header_start = value->data; + r->header_end = value->data + value->len; + r->header_hash = ngx_hash_key(name->data, name->len); + + /* XXX r->lowcase_index = i; */ + + return NGX_OK; + } + + return NGX_AGAIN; + +failed: + + return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST + : NGX_HTTP_PARSE_INVALID_HEADER; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + + return NGX_HTTP_PARSE_HEADER_DONE; +} + +#if 0 ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) { @@ -167,11 +262,14 @@ again: break; } - /* fall through */ + length &= 0x3fffff; + state = sw_header_block; + break; case sw_length_3: - length &= 0x3fffff; + length = (length << 8) + ch; + length &= 0x3fffffff; state = sw_header_block; break; @@ -567,6 +665,7 @@ failed: return NGX_HTTP_PARSE_INVALID_REQUEST; } +#endif static ngx_int_t @@ -576,6 +675,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t i; ngx_connection_t *c; + if (name->len == 0 || name->data[0] != ':') { + return NGX_DECLINED; + } + c = r->connection; if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { @@ -635,14 +738,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_OK; } - if (name->len && name->data[0] == ':') { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 unknown pseudo header \"%V\" \"%V\"", - name, value); - return NGX_OK; - } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 unknown pseudo header \"%V\" \"%V\"", name, value); - return NGX_DONE; + return NGX_OK; } @@ -789,7 +888,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); *b->last = 0; b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); - b->last = ngx_sprintf(b->last, "%03ui ", r->headers_out.status); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); } if (r->headers_out.server == NULL) { diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 2b757d81f..18e38a68d 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -10,42 +10,30 @@ #include -#define NGX_HTTP_V3_CONTROL_STREAM 0x00 -#define NGX_HTTP_V3_PUSH_STREAM 0x01 -#define NGX_HTTP_V3_ENCODER_STREAM 0x02 -#define NGX_HTTP_V3_DECODER_STREAM 0x03 +typedef ngx_int_t (*ngx_http_v3_handler_pt)(ngx_connection_t *c, void *data, + u_char ch); typedef struct { - uint32_t signature; /* QSTR */ - u_char buf[4]; + uint32_t signature; /* QSTR */ - ngx_uint_t len; - ngx_uint_t type; - ngx_uint_t state; - ngx_uint_t index; - ngx_uint_t offset; + ngx_http_v3_handler_pt handler; + void *data; - ngx_str_t name; - ngx_str_t value; - - unsigned client:1; - unsigned dynamic:1; - unsigned huffman:1; + ngx_uint_t type; + ngx_uint_t client; /* unsigned client:1; */ } ngx_http_v3_uni_stream_t; static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_uni_stream_cleanup(void *data); static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); -static void ngx_http_v3_dummy_stream_handler(ngx_event_t *rev); -static void ngx_http_v3_client_encoder_handler(ngx_event_t *rev); -static void ngx_http_v3_client_decoder_handler(ngx_event_t *rev); - +static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static ngx_connection_t *ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type); -static ngx_connection_t *ngx_http_v3_get_server_encoder(ngx_connection_t *c); -static ngx_connection_t *ngx_http_v3_get_server_decoder(ngx_connection_t *c); +static ngx_connection_t *ngx_http_v3_get_control(ngx_connection_t *c); +static ngx_connection_t *ngx_http_v3_get_encoder(ngx_connection_t *c); +static ngx_connection_t *ngx_http_v3_get_decoder(ngx_connection_t *c); void @@ -56,8 +44,13 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) c->log->connection = c->number; + /* XXX */ + (void) ngx_http_v3_get_control(c); + (void) ngx_http_v3_get_encoder(c); + (void) ngx_http_v3_get_decoder(c); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 new uni stream id:0x%uXL", c->qs->id); + "http3 new uni stream id:0x%uxL", c->qs->id); us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { @@ -81,7 +74,7 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) cln->data = c; c->read->handler = ngx_http_v3_read_uni_stream_type; - c->read->handler(c->read); + ngx_http_v3_read_uni_stream_type(c->read); } @@ -115,7 +108,7 @@ ngx_http_v3_uni_stream_cleanup(void *data) switch (us->type) { - case NGX_HTTP_V3_ENCODER_STREAM: + case NGX_HTTP_V3_STREAM_ENCODER: if (us->client) { h3c->client_encoder = NULL; @@ -125,7 +118,7 @@ ngx_http_v3_uni_stream_cleanup(void *data) break; - case NGX_HTTP_V3_DECODER_STREAM: + case NGX_HTTP_V3_STREAM_DECODER: if (us->client) { h3c->client_decoder = NULL; @@ -134,482 +127,114 @@ ngx_http_v3_uni_stream_cleanup(void *data) } break; - } -} - - -static void -ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) -{ - u_char *p; - ssize_t n, len; - ngx_connection_t *c; - ngx_http_v3_connection_t *h3c; - ngx_http_v3_uni_stream_t *us; - - c = rev->data; - us = c->data; - h3c = c->qs->parent->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); - - while (rev->ready) { - p = &us->buf[us->len]; + case NGX_HTTP_V3_STREAM_CONTROL: - if (us->len == 0) { - len = 1; + if (us->client) { + h3c->client_control = NULL; } else { - len = (us->buf[0] >> 6) + 1 - us->len; - } - - n = c->recv(c, p, len); - - if (n == NGX_ERROR) { - goto failed; - } - - if (n == NGX_AGAIN) { - break; - } - - us->len += n; - - if (n != len) { - break; - } - - if ((us->buf[0] >> 6) + 1 == us->len) { - us->type = ngx_http_v3_decode_varlen_int(us->buf); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 stream type:%ui", us->type); - - switch (us->type) { - - case NGX_HTTP_V3_ENCODER_STREAM: - if (h3c->client_encoder) { - goto failed; - } - - h3c->client_encoder = c; - rev->handler = ngx_http_v3_client_encoder_handler; - break; - - case NGX_HTTP_V3_DECODER_STREAM: - if (h3c->client_decoder) { - goto failed; - } - - h3c->client_decoder = c; - rev->handler = ngx_http_v3_client_decoder_handler; - break; - - case NGX_HTTP_V3_CONTROL_STREAM: - case NGX_HTTP_V3_PUSH_STREAM: - - /* ignore these */ - - default: - rev->handler = ngx_http_v3_dummy_stream_handler; - } - - rev->handler(rev); - return; + h3c->server_control = NULL; } - } - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - goto failed; - } - - return; -failed: - - ngx_http_v3_close_uni_stream(c); -} - - -static void -ngx_http_v3_dummy_stream_handler(ngx_event_t *rev) -{ - u_char buf[128]; - ngx_connection_t *c; - - /* read out and ignore */ - - c = rev->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy stream reader"); - - while (rev->ready) { - if (c->recv(c, buf, sizeof(buf)) == NGX_ERROR) { - goto failed; - } - } - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - goto failed; + break; } - - return; - -failed: - - ngx_http_v3_close_uni_stream(c); } static void -ngx_http_v3_client_encoder_handler(ngx_event_t *rev) +ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) { - u_char v; + u_char ch; ssize_t n; - ngx_str_t name, value; - ngx_uint_t dynamic, huffman, index, offset; - ngx_connection_t *c, *pc; + ngx_connection_t *c; + ngx_http_v3_connection_t *h3c; ngx_http_v3_uni_stream_t *st; - enum { - sw_start = 0, - sw_inr_name_index, - sw_inr_value_length, - sw_inr_read_value_length, - sw_inr_value, - sw_iwnr_name_length, - sw_iwnr_name, - sw_iwnr_value_length, - sw_iwnr_read_value_length, - sw_iwnr_value, - sw_capacity, - sw_duplicate - } state; c = rev->data; st = c->data; - pc = c->qs->parent; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client encoder"); + h3c = c->qs->parent->data; - state = st->state; - dynamic = st->dynamic; - huffman = st->huffman; - index = st->index; - offset = st->offset; - name = st->name; - value = st->value; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); while (rev->ready) { - /* XXX limit checks */ - /* XXX buffer input */ - - n = c->recv(c, &v, 1); + n = c->recv(c, &ch, 1); - if (n == NGX_ERROR || n == 0) { + if (n == NGX_ERROR) { goto failed; } - if (n != 1) { + if (n == NGX_AGAIN || n != 1) { break; } - /* XXX v -> ch */ - - switch (state) { - - case sw_start: - - if (v & 0x80) { - /* Insert With Name Reference */ - - dynamic = (v & 0x40) ? 0 : 1; - index = v & 0x3f; - - if (index != 0x3f) { - state = sw_inr_value_length; - break; - } - - index = 0; - state = sw_inr_name_index; - break; - } - - if (v & 0x40) { - /* Insert Without Name Reference */ - - huffman = (v & 0x20) ? 1 : 0; - name.len = v & 0x1f; - - if (name.len != 0x1f) { - offset = 0; - state = sw_iwnr_name; - break; - } - - name.len = 0; - state = sw_iwnr_name_length; - break; - } - - if (v & 0x20) { - /* Set Dynamic Table Capacity */ - - index = v & 0x1f; - - if (index != 0x1f) { - if (ngx_http_v3_set_capacity(c, index) != NGX_OK) { - goto failed; - } - - break; - } - - index = 0; - state = sw_capacity; - break; - } - - /* Duplicate */ - - index = v & 0x1f; - - if (index != 0x1f) { - if (ngx_http_v3_duplicate(c, index) != NGX_OK) { - goto failed; - } - - break; - } - - index = 0; - state = sw_duplicate; - break; - - case sw_inr_name_index: - - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - index += 0x3f; - state = sw_inr_value_length; - break; - - case sw_inr_value_length: - - huffman = (v & 0x80) ? 1 : 0; - value.len = v & 0x7f; - - if (value.len == 0) { - value.data = NULL; - - if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK) - { - goto failed; - } - - state = sw_start; - break; - } - - if (value.len != 0x7f) { - value.data = ngx_pnalloc(pc->pool, value.len); - if (value.data == NULL) { - goto failed; - } - - state = sw_inr_value; - offset = 0; - break; - } + st->type = ch; - value.len = 0; - state = sw_inr_read_value_length; - break; - - case sw_inr_read_value_length: + switch (st->type) { - value.len = (value.len << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } + case NGX_HTTP_V3_STREAM_ENCODER: - value.len += 0x7f; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); - value.data = ngx_pnalloc(pc->pool, value.len); - if (value.data == NULL) { + if (h3c->client_encoder) { goto failed; } - state = sw_inr_value; - offset = 0; - break; - - case sw_inr_value: - - value.data[offset++] = v; - if (offset != value.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) { - goto failed; - } - } - - if (ngx_http_v3_ref_insert(c, dynamic, index, &value) != NGX_OK) { - goto failed; - } + h3c->client_encoder = c; + st->handler = ngx_http_v3_parse_encoder; + n = sizeof(ngx_http_v3_parse_encoder_t); - state = sw_start; break; - case sw_iwnr_name_length: + case NGX_HTTP_V3_STREAM_DECODER: - name.len = (name.len << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - name.len += 0x1f; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); - name.data = ngx_pnalloc(pc->pool, name.len); - if (name.data == NULL) { + if (h3c->client_decoder) { goto failed; } - offset = 0; - state = sw_iwnr_name; - break; - - case sw_iwnr_name: - - name.data[offset++] = v; - if (offset != name.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(pc, &name) != NGX_OK) { - goto failed; - } - } - - state = sw_iwnr_value_length; - break; - - case sw_iwnr_value_length: - - huffman = (v & 0x80) ? 1 : 0; - value.len = v & 0x7f; - - if (value.len == 0) { - value.data = NULL; - - if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) { - goto failed; - } - - state = sw_start; - break; - } - - if (value.len != 0x7f) { - value.data = ngx_pnalloc(pc->pool, value.len); - if (value.data == NULL) { - goto failed; - } - - offset = 0; - state = sw_iwnr_value; - break; - } - - state = sw_iwnr_read_value_length; - break; - - case sw_iwnr_read_value_length: - - value.len = (value.len << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - value.data = ngx_pnalloc(pc->pool, value.len); - if (value.data == NULL) { - goto failed; - } + h3c->client_decoder = c; + st->handler = ngx_http_v3_parse_decoder; + n = sizeof(ngx_http_v3_parse_decoder_t); - offset = 0; - state = sw_iwnr_value; break; - case sw_iwnr_value: - - value.data[offset++] = v; - if (offset != value.len) { - break; - } + case NGX_HTTP_V3_STREAM_CONTROL: - if (huffman) { - if (ngx_http_v3_decode_huffman(pc, &value) != NGX_OK) { - goto failed; - } - } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); - if (ngx_http_v3_insert(c, &name, &value) != NGX_OK) { + if (h3c->client_control) { goto failed; } - state = sw_start; - break; - - - case sw_capacity: - - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - index += 0x1f; + h3c->client_control = c; + st->handler = ngx_http_v3_parse_control; + n = sizeof(ngx_http_v3_parse_control_t); - if (ngx_http_v3_set_capacity(c, index) != NGX_OK) { - goto failed; - } - - state = sw_start; break; - case sw_duplicate: - - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } + default: - index += 0x1f; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xi", st->type); + n = 0; + } - if (ngx_http_v3_duplicate(c, index) != NGX_OK) { + if (n) { + st->data = ngx_pcalloc(c->pool, n); + if (st->data == NULL) { goto failed; } - - state = sw_start; - break; } - } - st->state = state; - st->dynamic = dynamic; - st->huffman = huffman; - st->index = index; - st->offset = offset; - st->name = name; - st->value = value; + rev->handler = ngx_http_v3_uni_read_handler; + ngx_http_v3_uni_read_handler(rev); + return; + } if (ngx_handle_read_event(rev, 0) != NGX_OK) { goto failed; @@ -624,158 +249,61 @@ failed: static void -ngx_http_v3_client_decoder_handler(ngx_event_t *rev) +ngx_http_v3_uni_read_handler(ngx_event_t *rev) { - u_char v; + u_char buf[128]; ssize_t n; - ngx_uint_t index; + ngx_int_t rc, i; ngx_connection_t *c; ngx_http_v3_uni_stream_t *st; - enum { - sw_start = 0, - sw_ack_header, - sw_cancel_stream, - sw_inc_insert_count - } state; c = rev->data; st = c->data; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client decoder"); - - state = st->state; - index = st->index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); while (rev->ready) { - /* XXX limit checks */ - /* XXX buffer input */ - - n = c->recv(c, &v, 1); + n = c->recv(c, buf, sizeof(buf)); if (n == NGX_ERROR || n == 0) { goto failed; } - if (n != 1) { + if (n == NGX_AGAIN) { break; } - switch (state) { - - case sw_start: - - if (v & 0x80) { - /* Header Acknowledgement */ - - index = v & 0x7f; - - if (index != 0x7f) { - if (ngx_http_v3_ack_header(c, index) != NGX_OK) { - goto failed; - } - - break; - } - - index = 0; - state = sw_ack_header; - break; - } - - if (v & 0x40) { - /* Stream Cancellation */ - - index = v & 0x3f; - - if (index != 0x3f) { - if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) { - goto failed; - } - - break; - } - - index = 0; - state = sw_cancel_stream; - break; - } - - /* Insert Count Increment */ - - index = v & 0x3f; - - if (index != 0x3f) { - if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) { - goto failed; - } - - break; - } - - index = 0; - state = sw_inc_insert_count; - break; - - case sw_ack_header: - - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - index += 0x7f; - - if (ngx_http_v3_ack_header(c, index) != NGX_OK) { - goto failed; - } - - state = sw_start; - break; - - case sw_cancel_stream: + if (st->handler == NULL) { + continue; + } - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } + for (i = 0; i < n; i++) { - index += 0x3f; + rc = st->handler(c, st->data, buf[i]); - if (ngx_http_v3_cancel_stream(c, index) != NGX_OK) { + if (rc == NGX_ERROR) { goto failed; } - state = sw_start; - break; - - case sw_inc_insert_count: - - index = (index << 7) + (v & 0x7f); - if (v & 0x80) { - break; - } - - index += 0x3f; - - if (ngx_http_v3_inc_insert_count(c, index) != NGX_OK) { - goto failed; + if (rc == NGX_DONE) { + goto done; } - state = sw_start; - break; + /* rc == NGX_AGAIN */ } } - st->state = state; - st->index = index; - if (ngx_handle_read_event(rev, 0) != NGX_OK) { goto failed; } return; +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read done"); + failed: ngx_http_v3_close_uni_stream(c); @@ -835,7 +363,7 @@ failed: static ngx_connection_t * -ngx_http_v3_get_server_encoder(ngx_connection_t *c) +ngx_http_v3_get_control(ngx_connection_t *c) { ngx_http_v3_connection_t *h3c; @@ -843,7 +371,7 @@ ngx_http_v3_get_server_encoder(ngx_connection_t *c) if (h3c->server_encoder == NULL) { h3c->server_encoder = ngx_http_v3_create_uni_stream(c, - NGX_HTTP_V3_ENCODER_STREAM); + NGX_HTTP_V3_STREAM_CONTROL); } return h3c->server_encoder; @@ -851,18 +379,34 @@ ngx_http_v3_get_server_encoder(ngx_connection_t *c) static ngx_connection_t * -ngx_http_v3_get_server_decoder(ngx_connection_t *c) +ngx_http_v3_get_encoder(ngx_connection_t *c) { ngx_http_v3_connection_t *h3c; h3c = c->qs->parent->data; - if (h3c->server_decoder == NULL) { - h3c->server_decoder = ngx_http_v3_create_uni_stream(c, - NGX_HTTP_V3_DECODER_STREAM); + if (h3c->server_encoder == NULL) { + h3c->server_encoder = ngx_http_v3_create_uni_stream(c, + NGX_HTTP_V3_STREAM_ENCODER); } - return h3c->server_decoder; + return h3c->server_encoder; +} + + +static ngx_connection_t * +ngx_http_v3_get_decoder(ngx_connection_t *c) +{ + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + + if (h3c->server_encoder == NULL) { + h3c->server_encoder = ngx_http_v3_create_uni_stream(c, + NGX_HTTP_V3_STREAM_DECODER); + } + + return h3c->server_encoder; } @@ -878,7 +422,7 @@ ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, "http3 client ref insert, %s[%ui] \"%V\"", dynamic ? "dynamic" : "static", index, value); - ec = ngx_http_v3_get_server_encoder(c); + ec = ngx_http_v3_get_encoder(c); if (ec == NULL) { return NGX_ERROR; } @@ -923,7 +467,7 @@ ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client insert \"%V\":\"%V\"", name, value); - ec = ngx_http_v3_get_server_encoder(c); + ec = ngx_http_v3_get_encoder(c); if (ec == NULL) { return NGX_ERROR; } @@ -972,7 +516,7 @@ ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client set capacity %ui", capacity); - ec = ngx_http_v3_get_server_encoder(c); + ec = ngx_http_v3_get_encoder(c); if (ec == NULL) { return NGX_ERROR; } @@ -999,7 +543,7 @@ ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client duplicate %ui", index); - ec = ngx_http_v3_get_server_encoder(c); + ec = ngx_http_v3_get_encoder(c); if (ec == NULL) { return NGX_ERROR; } @@ -1026,7 +570,7 @@ ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client ack header %ui", stream_id); - dc = ngx_http_v3_get_server_decoder(c); + dc = ngx_http_v3_get_decoder(c); if (dc == NULL) { return NGX_ERROR; } @@ -1053,7 +597,7 @@ ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client cancel stream %ui", stream_id); - dc = ngx_http_v3_get_server_decoder(c); + dc = ngx_http_v3_get_decoder(c); if (dc == NULL) { return NGX_ERROR; } @@ -1080,7 +624,7 @@ ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client increment insert count %ui", inc); - dc = ngx_http_v3_get_server_decoder(c); + dc = ngx_http_v3_get_decoder(c); if (dc == NULL) { return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 1c1d8c051..b4fc90b38 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -383,3 +383,33 @@ ngx_http_v3_new_header(ngx_connection_t *c) return NGX_OK; } + + +ngx_int_t +ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) +{ + switch (id) { + + case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_BLOCKED_STREAMS:%uL", value); + break; + + default: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param #%uL:%uL", id, value); + } + + return NGX_OK; +} -- cgit v1.2.3 From 85430505feb676dd45ca65ea75a0ccd0ed32ea3a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 14:09:50 +0300 Subject: Removed comment. --- src/http/v3/ngx_http_v3_parse.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index e0b28a6c3..0fd44bc40 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -208,7 +208,6 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, if (--st->length == 0) { if (rc != NGX_DONE) { -ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "XXX len"); return NGX_ERROR; } -- cgit v1.2.3 From d36684447c0c9f2e2324f9dc43e534ccc7edec18 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 14:10:44 +0300 Subject: Fixed HTTP/3 server stream creation. --- src/http/v3/ngx_http_v3_streams.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 18e38a68d..6a5610b9a 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -321,7 +321,7 @@ ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type) ngx_pool_cleanup_t *cln; ngx_http_v3_uni_stream_t *us; - sc = ngx_quic_create_uni_stream(c->qs->parent); + sc = ngx_quic_create_uni_stream(c); if (sc == NULL) { return NULL; } @@ -369,8 +369,8 @@ ngx_http_v3_get_control(ngx_connection_t *c) h3c = c->qs->parent->data; - if (h3c->server_encoder == NULL) { - h3c->server_encoder = ngx_http_v3_create_uni_stream(c, + if (h3c->server_control == NULL) { + h3c->server_control = ngx_http_v3_create_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); } @@ -401,8 +401,8 @@ ngx_http_v3_get_decoder(ngx_connection_t *c) h3c = c->qs->parent->data; - if (h3c->server_encoder == NULL) { - h3c->server_encoder = ngx_http_v3_create_uni_stream(c, + if (h3c->server_decoder == NULL) { + h3c->server_decoder = ngx_http_v3_create_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); } -- cgit v1.2.3 From 2973465556eea419776003e1e5d62691e8e42568 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 18 Mar 2020 13:49:39 +0300 Subject: Implemented creation of server unidirectional streams. The ngx_quic_create_stream() function is a generic function extracted from the ngx_quic_handle_stream_frame() function. --- src/event/ngx_event_quic.c | 167 +++++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 59 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e6ee6d2a6..0fb45da30 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -22,6 +22,8 @@ typedef struct { ngx_rbtree_node_t sentinel; ngx_msec_t timeout; ngx_connection_handler_pt handler; + + ngx_uint_t id_counter; } ngx_quic_streams_t; @@ -101,6 +103,8 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); static ngx_quic_stream_node_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key); +static ngx_quic_stream_node_t *ngx_quic_create_stream(ngx_connection_t *c, + ngx_uint_t id); 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, @@ -916,9 +920,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) { ngx_buf_t *b; - ngx_log_t *log; - ngx_pool_t *pool; - ngx_event_t *rev, *wev; ngx_quic_connection_t *qc; ngx_quic_stream_node_t *sn; @@ -945,69 +946,16 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); - sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); + sn = ngx_quic_create_stream(c, f->stream_id); if (sn == NULL) { return NGX_ERROR; } - sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination - if (sn->c == NULL) { - return NGX_ERROR; - } - - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - /* XXX free connection */ - return NGX_ERROR; - } - - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - /* XXX free pool and connection */ - return NGX_ERROR; - } - - *log = *c->log; - pool->log = log; - - sn->c->log = log; - sn->c->pool = pool; - - sn->c->listening = c->listening; - sn->c->sockaddr = c->sockaddr; - sn->c->local_sockaddr = c->local_sockaddr; - - rev = sn->c->read; - wev = sn->c->write; - - rev->ready = 1; - - rev->log = c->log; - wev->log = c->log; - - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - - sn->node.key = f->stream_id; - sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone - if (sn->b == NULL) { - return NGX_ERROR; - } b = sn->b; ngx_memcpy(b->start, f->data, f->length); b->last = b->start + f->length; - ngx_rbtree_insert(&qc->streams.tree, &sn->node); - - sn->s.id = f->stream_id; - sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; - sn->s.parent = c; - sn->c->qs = &sn->s; - - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; - qc->streams.handler(sn->c); return NGX_OK; @@ -1184,8 +1132,34 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_connection_t * ngx_quic_create_uni_stream(ngx_connection_t *c) { - /* XXX */ - return NULL; + ngx_uint_t id; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qs = c->qs; + qc = qs->parent->quic; + + /* + * A stream ID is a 62-bit integer that is unique for all streams + * on a connection. + * + * 0x3 | Server-Initiated, Unidirectional + */ + id = (qc->streams.id_counter << 2) | 0x3; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "creating server uni stream #%ui id %ui", + qc->streams.id_counter, id); + + qc->streams.id_counter++; + + sn = ngx_quic_create_stream(qs->parent, id); + if (sn == NULL) { + return NULL; + } + + return sn->c; } @@ -1254,6 +1228,81 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) } +static ngx_quic_stream_node_t * +ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) +{ + ngx_log_t *log; + ngx_pool_t *pool; + ngx_event_t *rev, *wev; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qc = c->quic; + + sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); + if (sn == NULL) { + return NULL; + } + + sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination + if (sn->c == NULL) { + return NULL; + } + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + /* XXX free connection */ + // TODO: add pool cleanup handdler + return NULL; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + /* XXX free pool and connection */ + return NULL; + } + + *log = *c->log; + pool->log = log; + + sn->c->log = log; + sn->c->pool = pool; + + sn->c->listening = c->listening; + sn->c->sockaddr = c->sockaddr; + sn->c->local_sockaddr = c->local_sockaddr; + + rev = sn->c->read; + wev = sn->c->write; + + rev->ready = 1; + + rev->log = c->log; + wev->log = c->log; + + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + sn->node.key =id; + sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone + if (sn->b == NULL) { + return NULL; + } + + ngx_rbtree_insert(&qc->streams.tree, &sn->node); + + sn->s.id = id; + sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; + sn->s.parent = c; + sn->c->qs = &sn->s; + + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; + + return sn; +} + + static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { -- cgit v1.2.3 From 04d037b239440bf7a2f422eb4b7c4e4e7652939e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 15:28:20 +0300 Subject: Fixed pointer increment while parsing HTTP/3 header. --- src/http/v3/ngx_http_v3_request.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e6cd27183..a9adcf8c2 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -83,9 +83,8 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) break; } - for ( /* void */ ; b->pos < b->last; b->pos++) { - - rc = ngx_http_v3_parse_headers(c, st, *b->pos); + while (b->pos < b->last) { + rc = ngx_http_v3_parse_headers(c, st, *b->pos++); if (rc == NGX_ERROR) { goto failed; -- cgit v1.2.3 From 50f919cec4a81d8c37d754e0be72283c92954800 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 18 Mar 2020 16:35:11 +0300 Subject: Added parsing of RESET_STREAM and STOP_SENDING frames --- src/event/ngx_event_quic.c | 17 +++++++++++++++++ src/event/ngx_event_quic_transport.c | 11 +++++++++++ src/event/ngx_event_quic_transport.h | 15 +++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0fb45da30..7f732ba8b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -808,6 +808,23 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; + case NGX_QUIC_FT_RESET_STREAM: + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "RESET STREAM frame" + " { id 0x%xi error_code 0x%xi final_size 0x%xi }", + frame.u.reset_stream.id, + frame.u.reset_stream.error_code, + frame.u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STOP SENDING frame" + " { id 0x%xi error_code 0x%xi}", + frame.u.stop_sending.id, + frame.u.stop_sending.error_code); + break; + default: ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported frame type 0x%xd in packet", frame.type); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1f49c10f4..0e51714bd 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -423,6 +423,17 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) break; + case NGX_QUIC_FT_RESET_STREAM: + frame->u.reset_stream.id = ngx_quic_parse_int(&p); + frame->u.reset_stream.error_code = ngx_quic_parse_int(&p); + frame->u.reset_stream.final_size = ngx_quic_parse_int(&p); + break; + + case NGX_QUIC_FT_STOP_SENDING: + frame->u.stop_sending.id = ngx_quic_parse_int(&p); + frame->u.stop_sending.error_code = ngx_quic_parse_int(&p); + break; + default: return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 549cc9757..76dac324d 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -116,6 +116,19 @@ typedef struct { } ngx_quic_close_frame_t; +typedef struct { + uint64_t id; + uint64_t error_code; + uint64_t final_size; +} ngx_quic_reset_stream_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; +} ngx_quic_stop_sending_frame_t; + + typedef struct ngx_quic_frame_s ngx_quic_frame_t; struct ngx_quic_frame_s { @@ -128,6 +141,8 @@ struct ngx_quic_frame_s { ngx_quic_new_conn_id_frame_t ncid; ngx_quic_stream_frame_t stream; ngx_quic_close_frame_t close; + ngx_quic_reset_stream_frame_t reset_stream; + ngx_quic_stop_sending_frame_t stop_sending; } u; u_char info[128]; // for debug }; -- cgit v1.2.3 From 5aa8e519c9fecc00b3a74781716ceb66609c5661 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 16:37:16 +0300 Subject: Moved setting QUIC methods to runtime. This allows listening to both https and http3 in the same server. Also, the change eliminates the ssl_quic directive. --- src/event/ngx_event_openssl.c | 22 ---------------------- src/event/ngx_event_openssl.h | 1 - src/event/ngx_event_quic.c | 13 ++++++------- src/http/modules/ngx_http_ssl_module.c | 14 -------------- src/http/modules/ngx_http_ssl_module.h | 1 - 5 files changed, 6 insertions(+), 45 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index eac1981a2..91b415caa 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1459,28 +1459,6 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) } -ngx_int_t -ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) -{ - if (!enable) { - return NGX_OK; - } - -#if NGX_OPENSSL_QUIC - - ngx_quic_init_ssl_methods(ssl->ctx); - return NGX_OK; - -#else - - ngx_log_error(NGX_LOG_WARN, ssl->log, 0, - "\"ssl_quic\" is not supported on this platform"); - return NGX_ERROR; - -#endif -} - - ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 620a216ef..bd90e47fe 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -196,7 +196,6 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); -ngx_int_t ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7f732ba8b..b77ae0f0c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -126,13 +126,6 @@ static SSL_QUIC_METHOD quic_method = { }; -void -ngx_quic_init_ssl_methods(SSL_CTX* ctx) -{ - SSL_CTX_set_quic_method(ctx, &quic_method); -} - - #if BORINGSSL_API_VERSION >= 10 static int @@ -410,6 +403,12 @@ ngx_quic_init_connection(ngx_connection_t *c) ssl_conn = c->ssl->connection; + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "SSL_set_quic_method() failed"); + return NGX_ERROR; + } + if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "SSL_set_quic_transport_params() failed"); diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 8640c2211..4b480a006 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -249,13 +249,6 @@ static ngx_command_t ngx_http_ssl_commands[] = { offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, - { ngx_string("ssl_quic"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_ssl_srv_conf_t, quic), - NULL }, - ngx_null_command }; @@ -575,7 +568,6 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) sscf->enable = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; - sscf->quic = NGX_CONF_UNSET; sscf->buffer_size = NGX_CONF_UNSET_SIZE; sscf->verify = NGX_CONF_UNSET_UINT; sscf->verify_depth = NGX_CONF_UNSET_UINT; @@ -620,8 +612,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->early_data, prev->early_data, 0); - ngx_conf_merge_value(conf->quic, prev->quic, 0); - ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); @@ -867,10 +857,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } - if (ngx_ssl_quic(cf, &conf->ssl, conf->quic) != NGX_OK) { - return NGX_CONF_ERROR; - } - return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index 310d7c737..26fdccfe4 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -21,7 +21,6 @@ typedef struct { ngx_flag_t prefer_server_ciphers; ngx_flag_t early_data; - ngx_flag_t quic; ngx_uint_t protocols; -- cgit v1.2.3 From e63accd7bd222aebd7cf4f90aeb8cca617d01b94 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 20:22:16 +0300 Subject: HTTP/3 $request_line variable. --- src/http/ngx_http_request.c | 4 +- src/http/v3/ngx_http_v3_request.c | 573 +++----------------------------------- 2 files changed, 37 insertions(+), 540 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 166d8ffba..687de931c 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1177,7 +1177,7 @@ ngx_http_process_request_line(ngx_event_t *rev) r->request_line.len = r->request_end - r->request_start; r->request_line.data = r->request_start; - r->request_length = r->header_in->pos - r->request_start; + r->request_length = r->header_in->pos - r->request_start; /* XXX */ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); @@ -1593,7 +1593,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header done"); - r->request_length += r->header_in->pos - r->header_name_start; + r->request_length += r->header_in->pos - r->header_name_start; /* XXX */ r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a9adcf8c2..75488db1d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -40,6 +40,8 @@ struct { ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) { + size_t n; + u_char *p; ngx_int_t rc; ngx_str_t *name, *value; ngx_connection_t *c; @@ -97,23 +99,45 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) name = &st->header_rep.header.name; value = &st->header_rep.header.value; - if (r->state == sw_start - && ngx_http_v3_process_pseudo_header(r, name, value) != NGX_OK) - { - if (rc == NGX_DONE) { - r->state = sw_last; - } else { + if (r->state == sw_start) { + + if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) { + if (rc == NGX_OK) { + continue; + } + + r->state = sw_done; + + } else if (rc == NGX_OK) { r->state = sw_prev; + + } else { + r->state = sw_last; + } + + n = (r->method_end - r->method_start) + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3") - 1; + + p = ngx_pnalloc(c->pool, n); + if (p == NULL) { + goto failed; } + r->request_start = p; + + p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + + r->request_end = p; + } else if (rc == NGX_DONE) { r->state = sw_done; } - if (r->state == sw_start) { - continue; - } - r->header_name_start = name->data; r->header_name_end = name->data + name->len; r->header_start = value->data; @@ -139,533 +163,6 @@ done: return NGX_HTTP_PARSE_HEADER_DONE; } -#if 0 -ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) -{ - u_char *p, ch; - ngx_str_t name, value; - ngx_int_t rc; - ngx_uint_t length, index, insert_count, sign, base, delta_base, - huffman, dynamic, offset; - ngx_connection_t *c; - ngx_http_v3_header_t *h; - enum { - sw_start = 0, - sw_length, - sw_length_1, - sw_length_2, - sw_length_3, - sw_header_block, - sw_req_insert_count, - sw_delta_base, - sw_read_delta_base, - sw_header, - sw_old_header, - sw_header_ri, - sw_header_pbi, - sw_header_lri, - sw_header_lpbi, - sw_header_l_name_len, - sw_header_l_name, - sw_header_value_len, - sw_header_read_value_len, - sw_header_value - } state; - - c = r->connection; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header, pseudo:%ui", pseudo); - - if (r->state == sw_old_header) { - r->state = sw_header; - return NGX_OK; - } - - length = r->h3_length; - index = r->h3_index; - insert_count = r->h3_insert_count; - sign = r->h3_sign; - delta_base = r->h3_delta_base; - huffman = r->h3_huffman; - dynamic = r->h3_dynamic; - offset = r->h3_offset; - - name.data = r->header_name_start; - name.len = r->header_name_end - r->header_name_start; - value.data = r->header_start; - value.len = r->header_end - r->header_start; - - if (r->state == sw_start) { - length = 1; - } - -again: - - state = r->state; - - if (state == sw_header && length == 0) { - r->state = sw_start; - return NGX_HTTP_PARSE_HEADER_DONE; - } - - for (p = b->pos; p < b->last; p++) { - - if (state >= sw_header_block && length-- == 0) { - goto failed; - } - - ch = *p; - - switch (state) { - - case sw_start: - - if (ch != NGX_HTTP_V3_FRAME_HEADERS) { - goto failed; - } - - r->request_start = p; - state = sw_length; - break; - - case sw_length: - - length = ch; - if (length & 0xc0) { - state = sw_length_1; - break; - } - - state = sw_header_block; - break; - - case sw_length_1: - - length = (length << 8) + ch; - if ((length & 0xc000) != 0x4000) { - state = sw_length_2; - break; - } - - length &= 0x3fff; - state = sw_header_block; - break; - - case sw_length_2: - - length = (length << 8) + ch; - if ((length & 0xc00000) != 0x800000) { - state = sw_length_3; - break; - } - - length &= 0x3fffff; - state = sw_header_block; - break; - - case sw_length_3: - - length = (length << 8) + ch; - length &= 0x3fffffff; - state = sw_header_block; - break; - - case sw_header_block: - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block length:%ui", length); - - if (ch != 0xff) { - insert_count = ch; - state = sw_delta_base; - break; - } - - insert_count = 0; - state = sw_req_insert_count; - break; - - case sw_req_insert_count: - - insert_count = (insert_count << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - insert_count += 0xff; - state = sw_delta_base; - break; - - case sw_delta_base: - - sign = (ch & 0x80) ? 1 : 0; - delta_base = ch & 0x7f; - - if (delta_base != 0x7f) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block " - "insert_count:%ui, sign:%ui, delta_base:%ui", - insert_count, sign, delta_base); - goto done; - } - - delta_base = 0; - state = sw_read_delta_base; - break; - - case sw_read_delta_base: - - delta_base = (delta_base << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - delta_base += 0x7f; - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block " - "insert_count:%ui, sign:%ui, delta_base:%ui", - insert_count, sign, delta_base); - goto done; - - case sw_header: - - index = 0; - huffman = 0; - ngx_str_null(&name); - ngx_str_null(&value); - - if (ch & 0x80) { - /* Indexed Header Field */ - - dynamic = (ch & 0x40) ? 0 : 1; - index = ch & 0x3f; - - if (index != 0x3f) { - goto done; - } - - index = 0; - state = sw_header_ri; - break; - } - - if (ch & 0x40) { - /* Literal Header Field With Name Reference */ - - dynamic = (ch & 0x10) ? 0 : 1; - index = ch & 0x0f; - - if (index != 0x0f) { - state = sw_header_value_len; - break; - } - - index = 0; - state = sw_header_lri; - break; - } - - if (ch & 0x20) { - /* Literal Header Field Without Name Reference */ - - huffman = (ch & 0x08) ? 1 : 0; - name.len = ch & 0x07; - - if (name.len == 0) { - goto failed; - } - - if (name.len != 0x07) { - offset = 0; - state = sw_header_l_name; - break; - } - - name.len = 0; - state = sw_header_l_name_len; - break; - } - - if (ch & 10) { - /* Indexed Header Field With Post-Base Index */ - - dynamic = 2; - index = ch & 0x0f; - - if (index != 0x0f) { - goto done; - } - - index = 0; - state = sw_header_pbi; - break; - } - - /* Literal Header Field With Post-Base Name Reference */ - - dynamic = 2; - index = ch & 0x07; - - if (index != 0x07) { - state = sw_header_value_len; - break; - } - - index = 0; - state = sw_header_lpbi; - break; - - case sw_header_ri: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x3f; - goto done; - - case sw_header_pbi: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x0f; - goto done; - - case sw_header_lri: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x0f; - state = sw_header_value_len; - break; - - case sw_header_lpbi: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x07; - state = sw_header_value_len; - break; - - - case sw_header_l_name_len: - - name.len = (name.len << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - name.len += 0x07; - offset = 0; - state = sw_header_l_name; - break; - - case sw_header_l_name: - if (offset++ == 0) { - name.data = p; - } - - if (offset != name.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(c, &name) != NGX_OK) { - goto failed; - } - } - - state = sw_header_value_len; - break; - - case sw_header_value_len: - - huffman = (ch & 0x80) ? 1 : 0; - value.len = ch & 0x7f; - - if (value.len == 0) { - value.data = p; - goto done; - } - - if (value.len != 0x7f) { - offset = 0; - state = sw_header_value; - break; - } - - value.len = 0; - state = sw_header_read_value_len; - break; - - case sw_header_read_value_len: - - value.len = (value.len << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - value.len += 0x7f; - offset = 0; - state = sw_header_value; - break; - - case sw_header_value: - - if (offset++ == 0) { - value.data = p; - } - - if (offset != value.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(c, &value) != NGX_OK) { - goto failed; - } - } - - goto done; - - case sw_old_header: - - break; - } - } - - b->pos = p; - r->state = state; - r->h3_length = length; - r->h3_index = index; - r->h3_insert_count = insert_count; - r->h3_sign = sign; - r->h3_delta_base = delta_base; - r->h3_huffman = huffman; - r->h3_dynamic = dynamic; - r->h3_offset = offset; - - /* XXX fix large reallocations */ - r->header_name_start = name.data; - r->header_name_end = name.data + name.len; - r->header_start = value.data; - r->header_end = value.data + value.len; - - /* XXX r->lowcase_index = i; */ - - return NGX_AGAIN; - -done: - - b->pos = p + 1; - r->state = sw_header; - r->h3_length = length; - r->h3_insert_count = insert_count; - r->h3_sign = sign; - r->h3_delta_base = delta_base; - - if (state < sw_header) { - if (ngx_http_v3_check_insert_count(c, insert_count) != NGX_OK) { - return NGX_DONE; - } - - goto again; - } - - if (sign == 0) { - base = insert_count + delta_base; - } else { - base = insert_count - delta_base - 1; - } - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header %s[%ui], base:%ui, \"%V\":\"%V\"", - dynamic ? "dynamic" : "static", index, base, &name, &value); - - if (name.data == NULL) { - - if (dynamic == 2) { - index = base - index - 1; - } else if (dynamic == 1) { - index += base; - } - - h = ngx_http_v3_lookup_table(c, dynamic, index); - if (h == NULL) { - goto failed; - } - - name = h->name; - - if (value.data == NULL) { - value = h->value; - } - } - - /* XXX ugly reallocation for the trailing '\0' */ - - p = ngx_pnalloc(c->pool, name.len + value.len + 2); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, name.data, name.len); - name.data = p; - ngx_memcpy(p + name.len + 1, value.data, value.len); - value.data = p + name.len + 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header \"%V\":\"%V\"", &name, &value); - - if (pseudo) { - rc = ngx_http_v3_process_pseudo_header(r, &name, &value); - - if (rc == NGX_ERROR) { - goto failed; - } - - if (rc == NGX_OK) { - r->request_end = p + 1; - goto again; - } - - /* rc == NGX_DONE */ - - r->state = sw_old_header; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header left:%ui", length); - - r->header_name_start = name.data; - r->header_name_end = name.data + name.len; - r->header_start = value.data; - r->header_end = value.data + value.len; - r->header_hash = ngx_hash_key(name.data, name.len); /* XXX */ - - /* XXX r->lowcase_index = i; */ - - return NGX_OK; - -failed: - - return NGX_HTTP_PARSE_INVALID_REQUEST; -} -#endif - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, @@ -675,7 +172,7 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_connection_t *c; if (name->len == 0 || name->data[0] != ':') { - return NGX_DECLINED; + return NGX_DONE; } c = r->connection; -- cgit v1.2.3 From 0561665873037ce3c5c9e4888982fa32f5c35727 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 20:28:28 +0300 Subject: Added copying addr_text to QUIC stream connections. Now $remote_addr holds client address. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b77ae0f0c..3c9c01e79 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1287,6 +1287,7 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) sn->c->listening = c->listening; sn->c->sockaddr = c->sockaddr; sn->c->local_sockaddr = c->local_sockaddr; + sn->c->addr_text = c->addr_text; rev = sn->c->read; wev = sn->c->write; -- cgit v1.2.3 From 6aa611c314f87afc0242736c98383e3836800588 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 18 Mar 2020 23:07:40 +0300 Subject: Implemented send_alert callback, CONNECTION_CLOSE writer. The callback produces a CONNECTION_CLOSE frame, as per quic-tls-24#section-4.9. --- src/event/ngx_event_quic.c | 16 ++++++++++++++++ src/event/ngx_event_quic_transport.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3c9c01e79..44ddcc81d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -251,6 +251,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert) { ngx_connection_t *c; + ngx_quic_frame_t *frame; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -258,6 +259,21 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, "ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return 0; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = 0x100 + alert; + + ngx_quic_queue_frame(c->quic, frame); + + if (ngx_quic_output(c) != NGX_OK) { + return 0; + } + return 1; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 0e51714bd..35013461f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -58,6 +58,7 @@ static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); /* literal errors indexed by corresponding value */ @@ -464,6 +465,9 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) case NGX_QUIC_FT_STREAM7: return ngx_quic_create_stream(p, &f->u.stream); + case NGX_QUIC_FT_CONNECTION_CLOSE: + return ngx_quic_create_close(p, &f->u.close); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -489,6 +493,8 @@ ngx_quic_frame_len(ngx_quic_frame_t *frame) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: return ngx_quic_create_stream(NULL, &frame->u.stream); + case NGX_QUIC_FT_CONNECTION_CLOSE: + return ngx_quic_create_close(NULL, &frame->u.close); default: /* BUG: unsupported frame type generated */ return 0; @@ -597,3 +603,31 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) return p - start; } + + +static size_t +ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CONNECTION_CLOSE); + len += ngx_quic_varint_len(cl->error_code); + len += ngx_quic_varint_len(cl->frame_type); + len += ngx_quic_varint_len(cl->reason.len); + len += cl->reason.len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CONNECTION_CLOSE); + ngx_quic_build_int(&p, cl->error_code); + ngx_quic_build_int(&p, cl->frame_type); + ngx_quic_build_int(&p, cl->reason.len); + p = ngx_cpymem(p, cl->reason.data, cl->reason.len); + + return p - start; +} -- cgit v1.2.3 From 33d8317dd5fa0b25f27a64a54e1ae099023fb1dd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 19 Mar 2020 11:15:43 +0300 Subject: Added parsing of STREAMS BLOCKED frames. While there, added hex prefix for debug to avoid frame type confusion. --- src/event/ngx_event_quic.c | 11 ++++++++++- src/event/ngx_event_quic_transport.c | 10 ++++++++++ src/event/ngx_event_quic_transport.h | 7 +++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 44ddcc81d..c16c19ad1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -722,7 +722,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) len = ngx_quic_parse_frame(p, end, &frame); if (len < 0) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "failed to parse frame type %xi", frame.type); + "failed to parse frame type 0x%xi", frame.type); return NGX_ERROR; } @@ -840,6 +840,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) frame.u.stop_sending.error_code); break; + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "STREAMS BLOCKED frame" + " { limit %i bidi: %d }", + frame.u.streams_blocked.limit, + frame.u.streams_blocked.bidi); + break; + default: ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported frame type 0x%xd in packet", frame.type); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 35013461f..cd971fb36 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -435,6 +435,16 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) frame->u.stop_sending.error_code = ngx_quic_parse_int(&p); break; + case NGX_QUIC_FT_STREAMS_BLOCKED: + frame->u.streams_blocked.limit = ngx_quic_parse_int(&p); + frame->u.streams_blocked.bidi = 1; + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED2: + frame->u.streams_blocked.limit = ngx_quic_parse_int(&p); + frame->u.streams_blocked.bidi = 0; + break; + default: return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 76dac324d..d09dbfedb 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -129,6 +129,12 @@ typedef struct { } ngx_quic_stop_sending_frame_t; +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_streams_blocked_frame_t; + + typedef struct ngx_quic_frame_s ngx_quic_frame_t; struct ngx_quic_frame_s { @@ -143,6 +149,7 @@ struct ngx_quic_frame_s { ngx_quic_close_frame_t close; ngx_quic_reset_stream_frame_t reset_stream; ngx_quic_stop_sending_frame_t stop_sending; + ngx_quic_streams_blocked_frame_t streams_blocked; } u; u_char info[128]; // for debug }; -- cgit v1.2.3 From 31e794f0ad881248f72e87f32e6e594cb222e94d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 18 Mar 2020 23:26:26 +0300 Subject: MAX_DATA frame parser/handler. --- src/event/ngx_event_quic.c | 11 +++++++++++ src/event/ngx_event_quic_transport.c | 4 ++++ src/event/ngx_event_quic_transport.h | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c16c19ad1..a58a0aaba 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -42,6 +42,7 @@ struct ngx_quic_connection_s { ngx_quic_frame_t *frames; ngx_quic_streams_t streams; + ngx_uint_t max_data; }; @@ -823,6 +824,16 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; + case NGX_QUIC_FT_MAX_DATA: + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "MAX_DATA frame" + " { Maximum Data %ui }", + frame.u.max_data.max_data); + + c->quic->max_data = frame.u.max_data.max_data; + ack_this = 1; + break; + case NGX_QUIC_FT_RESET_STREAM: ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "RESET STREAM frame" diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index cd971fb36..5527323a3 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -424,6 +424,10 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) break; + case NGX_QUIC_FT_MAX_DATA: + frame->u.max_data.max_data = ngx_quic_parse_int(&p); + break; + case NGX_QUIC_FT_RESET_STREAM: frame->u.reset_stream.id = ngx_quic_parse_int(&p); frame->u.reset_stream.error_code = ngx_quic_parse_int(&p); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index d09dbfedb..565161f93 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -109,6 +109,11 @@ typedef struct { } ngx_quic_stream_frame_t; +typedef struct { + uint64_t max_data; +} ngx_quic_max_data_frame_t; + + typedef struct { uint64_t error_code; uint64_t frame_type; @@ -146,6 +151,7 @@ struct ngx_quic_frame_s { ngx_quic_crypto_frame_t crypto; ngx_quic_new_conn_id_frame_t ncid; ngx_quic_stream_frame_t stream; + ngx_quic_max_data_frame_t max_data; ngx_quic_close_frame_t close; ngx_quic_reset_stream_frame_t reset_stream; ngx_quic_stop_sending_frame_t stop_sending; -- cgit v1.2.3 From 8ad2707d4f5c007ec308262ea03481d95eee7e02 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 19 Mar 2020 15:03:09 +0300 Subject: Fixed header creation for header_only responses in HTTP/3. --- src/http/ngx_http_header_filter_module.c | 29 ++++++++++++++++------------- src/http/v3/ngx_http_v3_request.c | 26 +++++++++++++++----------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index 02e251f79..85edf3657 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -179,6 +179,22 @@ ngx_http_header_filter(ngx_http_request_t *r) return NGX_OK; } + if (r->http_version < NGX_HTTP_VERSION_10) { + return NGX_OK; + } + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.status_line.len == 0) { + if (r->headers_out.status == NGX_HTTP_NO_CONTENT + || r->headers_out.status == NGX_HTTP_NOT_MODIFIED) + { + r->header_only = 1; + } + } + #if (NGX_HTTP_V3) if (r->http_version == NGX_HTTP_VERSION_30) { @@ -194,14 +210,6 @@ ngx_http_header_filter(ngx_http_request_t *r) #endif - if (r->http_version < NGX_HTTP_VERSION_10) { - return NGX_OK; - } - - if (r->method == NGX_HTTP_HEAD) { - r->header_only = 1; - } - if (r->headers_out.last_modified_time != -1) { if (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT @@ -235,7 +243,6 @@ ngx_http_header_filter(ngx_http_request_t *r) /* 2XX */ if (status == NGX_HTTP_NO_CONTENT) { - r->header_only = 1; ngx_str_null(&r->headers_out.content_type); r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; @@ -252,10 +259,6 @@ ngx_http_header_filter(ngx_http_request_t *r) { /* 3XX */ - if (status == NGX_HTTP_NOT_MODIFIED) { - r->header_only = 1; - } - status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; status_line = &ngx_http_status_lines[status]; len += ngx_http_status_lines[status].len; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 75488db1d..9a2654fd4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -259,7 +259,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); /* XXX support chunked body in the chunked filter */ - if (r->headers_out.content_length_n == -1) { + if (!r->header_only && r->headers_out.content_length_n == -1) { return NULL; } @@ -310,11 +310,11 @@ ngx_http_v3_create_header(ngx_http_request_t *r) + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; } - if (r->headers_out.content_length_n == 0) { - len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); - - } else { + if (r->headers_out.content_length_n > 0) { len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); } if (r->headers_out.last_modified == NULL @@ -454,18 +454,18 @@ ngx_http_v3_create_header(ngx_http_request_t *r) } } - if (r->headers_out.content_length_n == 0) { - /* content-length: 0 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); - - } else if (r->headers_out.content_length_n > 0) { + if (r->headers_out.content_length_n > 0) { /* content-length: 0 */ *b->last = 0x70; b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); p = b->last++; b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); *p = b->last - p - 1; + + } else if (r->headers_out.content_length_n == 0) { + /* content-length: 0 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); } if (r->headers_out.last_modified == NULL @@ -521,6 +521,10 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); } + if (r->header_only) { + b->last_buf = 1; + } + cl = ngx_alloc_chain_link(c->pool); if (cl == NULL) { return NULL; -- cgit v1.2.3 From 4cbfc073942d6fd2ef4ef69d252687b12fe5687c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 19 Mar 2020 15:34:35 +0300 Subject: Send a FIN frame when QUIC stream is closed. --- src/event/ngx_event_quic.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a58a0aaba..991aa499c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -110,6 +110,7 @@ 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 void ngx_quic_stream_cleanup_handler(void *data); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); @@ -1286,6 +1287,7 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) ngx_log_t *log; ngx_pool_t *pool; ngx_event_t *rev, *wev; + ngx_pool_cleanup_t *cln; ngx_quic_connection_t *qc; ngx_quic_stream_node_t *sn; @@ -1352,6 +1354,16 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) sn->c->send = ngx_quic_stream_send; sn->c->send_chain = ngx_quic_stream_send_chain; + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sn->c); + ngx_destroy_pool(pool); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sn->c; + return sn; } @@ -1457,6 +1469,58 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) } +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin"); + + qs = c->qs; + pc = qs->parent; + qc = pc->quic; + + if ((qs->id & 0x03) == 0x02) { + /* do not send fin for client unidirectional streams */ + return; + } + + // XXX: get direct pointer from stream structure? + sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); + + if (sn == NULL) { + return; + } + + frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 1; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = 0; + frame->u.stream.data = NULL; + + ngx_sprintf(frame->info, "stream %xi fin=1 level=%d", qs->id, frame->level); + + ngx_quic_queue_frame(qc, frame); +} + + static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) -- cgit v1.2.3 From 18ce6d5ebfcf619c103994dd38278014ec01506c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 19 Mar 2020 17:07:12 +0300 Subject: Added boundaries checks into frame parser. The ngx_quic_parse_frame() functions now has new 'pkt' argument: the packet header of a currently processed frame. This allows to log errors/debug closer to reasons and perform additional checks regarding possible frame types. The handler only performs processing of good frames. A number of functions like read_uint32(), parse_int[_multi] probably should be implemented as a macro, but currently it is better to have them as functions for simpler debugging. --- src/event/ngx_event_quic.c | 71 +---- src/event/ngx_event_quic_transport.c | 542 ++++++++++++++++++++++++++++------- src/event/ngx_event_quic_transport.h | 12 +- 3 files changed, 448 insertions(+), 177 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 991aa499c..8ef6ab213 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -721,10 +721,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) while (p < end) { - len = ngx_quic_parse_frame(p, end, &frame); + len = ngx_quic_parse_frame(pkt, p, end, &frame); if (len < 0) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "failed to parse frame type 0x%xi", frame.type); return NGX_ERROR; } @@ -733,14 +731,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) switch (frame.type) { case NGX_QUIC_FT_ACK: - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", - frame.u.ack.largest, - frame.u.ack.delay, - frame.u.ack.first_range, - frame.u.ack.range_count); - if (ngx_quic_handle_ack_frame(c, pkt, &frame.u.ack) != NGX_OK) { return NGX_ERROR; } @@ -749,15 +739,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_CRYPTO: - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic CRYPTO frame length: %uL off:%uL pp:%p", - frame.u.crypto.len, frame.u.crypto.offset, - frame.u.crypto.data); - - ngx_quic_hexdump0(c->log, "CRYPTO frame contents", - frame.u.crypto.data, frame.u.crypto.len); - - if (ngx_quic_handle_crypto_frame(c, pkt, &frame.u.crypto) != NGX_OK) { @@ -776,20 +757,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_NEW_CONNECTION_ID: ack_this = 1; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "NCID: { seq=%ui retire=%ui len=%ui}", - frame.u.ncid.seqnum, - frame.u.ncid.retire, - frame.u.ncid.len); break; case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", - ngx_quic_error_text(frame.u.close.error_code), - frame.u.close.error_code, - frame.u.close.frame_type, - &frame.u.close.reason); do_close = 1; break; @@ -803,19 +773,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAM frame { 0x%xi id 0x%xi offset 0x%xi len 0x%xi bits:off=%d len=%d fin=%d }", - frame.type, - frame.u.stream.stream_id, - frame.u.stream.offset, - frame.u.stream.length, - frame.u.stream.off, - frame.u.stream.len, - frame.u.stream.fin); - - ngx_quic_hexdump0(c->log, "STREAM frame contents", - frame.u.stream.data, frame.u.stream.length); - if (ngx_quic_handle_stream_frame(c, pkt, &frame.u.stream) != NGX_OK) { @@ -826,44 +783,24 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_MAX_DATA: - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "MAX_DATA frame" - " { Maximum Data %ui }", - frame.u.max_data.max_data); - c->quic->max_data = frame.u.max_data.max_data; ack_this = 1; break; case NGX_QUIC_FT_RESET_STREAM: - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "RESET STREAM frame" - " { id 0x%xi error_code 0x%xi final_size 0x%xi }", - frame.u.reset_stream.id, - frame.u.reset_stream.error_code, - frame.u.reset_stream.final_size); + /* TODO: handle */ break; case NGX_QUIC_FT_STOP_SENDING: - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STOP SENDING frame" - " { id 0x%xi error_code 0x%xi}", - frame.u.stop_sending.id, - frame.u.stop_sending.error_code); + /* TODO: handle; need ack ? */ break; case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "STREAMS BLOCKED frame" - " { limit %i bidi: %d }", - frame.u.streams_blocked.limit, - frame.u.streams_blocked.bidi); + /* TODO: handle; need ack ? */ break; default: - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unsupported frame type 0x%xd in packet", frame.type); return NGX_ERROR; } } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 5527323a3..c3b4b8bc6 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -51,9 +51,17 @@ : 8) -static uint64_t ngx_quic_parse_int(u_char **pos); +static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...); static void ngx_quic_build_int(u_char **pos, uint64_t value); +static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); +static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, + u_char **out); +static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, + u_char *dst); + static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); @@ -81,23 +89,117 @@ static char *ngx_quic_errors[] = { }; -static uint64_t -ngx_quic_parse_int(u_char **pos) +static ngx_inline u_char * +ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) { u_char *p; uint64_t value; ngx_uint_t len; - p = *pos; + if (pos >= end) { + printf("OOPS >=\n"); + return NULL; + } + + p = pos; len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + if ((size_t)(end - p) < (len - 1)) { + printf("LEN TOO BIG: need %ld have %ld\n", len, end - p); + return NULL; + } + while (--len) { value = (value << 8) + *p++; } - *pos = p; - return value; + *out = value; + + return p; +} + + +static ngx_inline u_char * +ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...) +{ + u_char *p; + va_list ap; + uint64_t *item; + + p = pos; + + va_start(ap, end); + + do { + item = va_arg(ap, uint64_t *); + if (item == NULL) { + break; + } + + p = ngx_quic_parse_int(p, end, item); + if (p == NULL) { + return NULL; + } + + } while (1); + + va_end(ap); + + return p; +} + + +static ngx_inline u_char * +ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) +{ + if ((size_t)(end - pos) < 1) { + return NULL; + } + + *value = *pos; + + return pos + 1; +} + + +static ngx_inline u_char * +ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint32(pos); + + return pos + sizeof(uint32_t); +} + + +static ngx_inline u_char * +ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + *out = pos; + + return pos + len; +} + + +static u_char * +ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + ngx_memcpy(dst, pos, len); + + return pos + len; } @@ -141,37 +243,72 @@ ngx_quic_error_text(uint64_t error_code) ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt) { - u_char *p; + u_char *p, *end; + uint8_t idlen; p = pkt->data; + end = pkt->data + pkt->len; ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len); - if (!(p[0] & NGX_QUIC_PKT_LONG)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet"); + p = ngx_quic_read_uint8(p, end, &pkt->flags); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read flags"); return NGX_ERROR; } - pkt->flags = *p++; + if (!(pkt->flags & NGX_QUIC_PKT_LONG)) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a long packet"); + return NGX_ERROR; + } - pkt->version = ngx_quic_parse_uint32(p); - p += sizeof(uint32_t); + p = ngx_quic_read_uint32(p, end, &pkt->version); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read version"); + return NGX_ERROR; + } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic flags:%xi version:%xD", pkt->flags, pkt->version); if (pkt->version != quic_version) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version"); + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unsupported quic version"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read dcid len"); + return NGX_ERROR; + } + + pkt->dcid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read dcid"); return NGX_ERROR; } - pkt->dcid.len = *p++; - pkt->dcid.data = p; - p += pkt->dcid.len; + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read scid len"); + return NGX_ERROR; + } - pkt->scid.len = *p++; - pkt->scid.data = p; - p += pkt->scid.len; + pkt->scid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read scid"); + return NGX_ERROR; + } pkt->raw->pos = p; @@ -214,30 +351,41 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) { - u_char *p; + u_char *p, *end; p = pkt->data; + end = pkt->data + pkt->len; ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len); - if ((p[0] & NGX_QUIC_PKT_LONG)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet"); + p = ngx_quic_read_uint8(p, end, &pkt->flags); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read flags"); return NGX_ERROR; } - pkt->flags = *p++; + if (pkt->flags & NGX_QUIC_PKT_LONG) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a short packet"); + return NGX_ERROR; + } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic flags:%xi", pkt->flags); if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unexpected quic dcid"); return NGX_ERROR; } pkt->dcid.len = dcid->len; - pkt->dcid.data = p; - p += pkt->dcid.len; + + p = ngx_quic_read_bytes(p, end, dcid->len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet is too short to read dcid"); + return NGX_ERROR; + } pkt->raw->pos = p; @@ -248,23 +396,39 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) { - u_char *p; - ngx_int_t plen; + u_char *p, *end; + uint64_t plen; p = pkt->raw->pos; - pkt->token.len = ngx_quic_parse_int(&p); - pkt->token.data = p; + end = pkt->raw->last; + + pkt->log->action = "parsing quic initial header"; + + p = ngx_quic_parse_int(p, end, &pkt->token.len); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "failed to parse token length"); + return NGX_ERROR; + } - p += pkt->token.len; + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "packet too short to read token data"); + return NGX_ERROR; + } - plen = ngx_quic_parse_int(&p); + p = ngx_quic_parse_int(p, end, &plen); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "bad packet length"); + return NGX_ERROR; + } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic packet length: %d", plen); - if (plen > pkt->data + pkt->len - p) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); + if (plen > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated initial packet"); return NGX_ERROR; } @@ -275,9 +439,6 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %d", plen); - return NGX_OK; } @@ -285,27 +446,31 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) { - u_char *p; - ngx_int_t plen; + u_char *p, *end; + uint64_t plen; p = pkt->raw->pos; + end = pkt->raw->last; - plen = ngx_quic_parse_int(&p); + pkt->log->action = "parsing quic handshake header"; + + p = ngx_quic_parse_int(p, end, &plen); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "bad packet length"); + return NGX_ERROR; + } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic packet length: %d", plen); - if (plen > pkt->data + pkt->len - p) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); + if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated handshake packet"); return NGX_ERROR; } pkt->raw->pos = p; pkt->len = plen; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %d", plen); - return NGX_OK; } @@ -315,30 +480,59 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) #define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) ssize_t -ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) +ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *f) { - u_char *p; - - size_t npad; + u_char *p; p = start; - frame->type = *p++; // TODO: check overflow (p < end) + /* TODO: add a check if frame is allowed in this type of packet */ + + p = ngx_quic_parse_int(p, end, &f->type); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to obtain quic frame type"); + return NGX_ERROR; + } - switch (frame->type) { + switch (f->type) { case NGX_QUIC_FT_CRYPTO: - frame->u.crypto.offset = *p++; - frame->u.crypto.len = ngx_quic_parse_int(&p); - frame->u.crypto.data = p; - p += frame->u.crypto.len; + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse crypto frame offset"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.crypto.len); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse crypto frame len"); + return NGX_ERROR; + } + + p = ngx_quic_read_bytes(p, end, f->u.crypto.len, &f->u.crypto.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse crypto frame data"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic CRYPTO frame length: %uL off:%uL pp:%p", + f->u.crypto.len, f->u.crypto.offset, + f->u.crypto.data); + + ngx_quic_hexdump0(pkt->log, "CRYPTO frame contents", + f->u.crypto.data, f->u.crypto.len); break; case NGX_QUIC_FT_PADDING: - npad = 0; - while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX - p++; npad++; + while (p < end && *p == NGX_QUIC_FT_PADDING) { + p++; } break; @@ -346,19 +540,38 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: - frame->u.ack.largest = ngx_quic_parse_int(&p); - frame->u.ack.delay = ngx_quic_parse_int(&p); - frame->u.ack.range_count =ngx_quic_parse_int(&p); - frame->u.ack.first_range =ngx_quic_parse_int(&p); + p = ngx_quic_parse_int_multi(p, end, &f->u.ack.largest, + &f->u.ack.delay, &f->u.ack.range_count, + &f->u.ack.first_range, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse ack frame"); + return NGX_ERROR; + } - if (frame->u.ack.range_count) { - frame->u.ack.ranges[0] = ngx_quic_parse_int(&p); + if (f->u.ack.range_count) { + p = ngx_quic_parse_int(p, end, &f->u.ack.ranges[0]); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse ack frame first range"); + return NGX_ERROR; + } } - if (frame->type ==NGX_QUIC_FT_ACK_ECN) { + if (f->type == NGX_QUIC_FT_ACK_ECN) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "TODO: parse ECN ack frames"); + /* TODO: add parsing of such frames */ return NGX_ERROR; } + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", + f->u.ack.largest, + f->u.ack.delay, + f->u.ack.first_range, + f->u.ack.range_count); + break; case NGX_QUIC_FT_PING: @@ -366,28 +579,72 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) case NGX_QUIC_FT_NEW_CONNECTION_ID: - frame->u.ncid.seqnum = ngx_quic_parse_int(&p); - frame->u.ncid.retire = ngx_quic_parse_int(&p); - frame->u.ncid.len = *p++; - ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len); - p += frame->u.ncid.len; + p = ngx_quic_parse_int_multi(p, end, &f->u.ncid.seqnum, + &f->u.ncid.retire, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse new connection id frame"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse new connection id length"); + return NGX_ERROR; + } + + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse new connection id cid"); + return NGX_ERROR; + } - ngx_memcpy(frame->u.ncid.srt, p, 16); - p += 16; + p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse new connection id srt"); + return NGX_ERROR; + } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "NCID: { seq=%ui retire=%ui len=%ui}", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; case NGX_QUIC_FT_CONNECTION_CLOSE: - frame->u.close.error_code = ngx_quic_parse_int(&p); - frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC - frame->u.close.reason.len = ngx_quic_parse_int(&p); - frame->u.close.reason.data = p; - p += frame->u.close.reason.len; + p = ngx_quic_parse_int_multi(p, end, &f->u.close.error_code, + &f->u.close.frame_type, + &f->u.close.reason.len, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse close connection frame"); + return NGX_ERROR; + } + + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, + &f->u.close.reason.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse close reason"); + return NGX_ERROR; + } - if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) { - frame->u.close.error_code = NGX_QUIC_ERR_LAST; + if (f->u.close.error_code > NGX_QUIC_ERR_LAST) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unkown error code: %ui, truncated", + f->u.close.error_code); + f->u.close.error_code = NGX_QUIC_ERR_LAST; } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + ngx_quic_error_text(f->u.close.error_code), + f->u.close.error_code, f->u.close.frame_type, + &f->u.close.reason); + break; case NGX_QUIC_FT_STREAM0: @@ -399,57 +656,134 @@ ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - frame->u.stream.type = frame->type; + f->u.stream.type = f->type; + + f->u.stream.off = ngx_quic_stream_bit_off(f->type); + f->u.stream.len = ngx_quic_stream_bit_len(f->type); + f->u.stream.fin = ngx_quic_stream_bit_fin(f->type); + + p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse stream frame id"); + return NGX_ERROR; + } - frame->u.stream.off = ngx_quic_stream_bit_off(frame->type); - frame->u.stream.len = ngx_quic_stream_bit_len(frame->type); - frame->u.stream.fin = ngx_quic_stream_bit_fin(frame->type); + if (f->type & 0x04) { + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse stream frame offset"); + return NGX_ERROR; + } - frame->u.stream.stream_id = ngx_quic_parse_int(&p); - if (frame->type & 0x04) { - frame->u.stream.offset = ngx_quic_parse_int(&p); } else { - frame->u.stream.offset = 0; + f->u.stream.offset = 0; } - if (frame->type & 0x02) { - frame->u.stream.length = ngx_quic_parse_int(&p); + if (f->type & 0x02) { + p = ngx_quic_parse_int(p, end, &f->u.stream.length); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse stream frame length"); + return NGX_ERROR; + } + } else { - frame->u.stream.length = end - p; /* up to packet end */ + f->u.stream.length = end - p; /* up to packet end */ } - frame->u.stream.data = p; + p = ngx_quic_read_bytes(p, end, f->u.stream.length, + &f->u.stream.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse stream frame data"); + return NGX_ERROR; + } - p += frame->u.stream.length; + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "STREAM frame { 0x%xi id 0x%xi offset 0x%xi " + "len 0x%xi bits:off=%d len=%d fin=%d }", + f->type, f->u.stream.stream_id, f->u.stream.offset, + f->u.stream.length, f->u.stream.off, f->u.stream.len, + f->u.stream.fin); + ngx_quic_hexdump0(pkt->log, "STREAM frame contents", + f->u.stream.data, f->u.stream.length); break; case NGX_QUIC_FT_MAX_DATA: - frame->u.max_data.max_data = ngx_quic_parse_int(&p); + + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse max data frame"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "MAX_DATA frame { Maximum Data %ui }", + f->u.max_data.max_data); break; case NGX_QUIC_FT_RESET_STREAM: - frame->u.reset_stream.id = ngx_quic_parse_int(&p); - frame->u.reset_stream.error_code = ngx_quic_parse_int(&p); - frame->u.reset_stream.final_size = ngx_quic_parse_int(&p); + + p = ngx_quic_parse_int_multi(p, end, &f->u.reset_stream.id, + &f->u.reset_stream.error_code, + &f->u.reset_stream.final_size, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse reset stream frame"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "RESET STREAM frame" + " { id 0x%xi error_code 0x%xi final_size 0x%xi }", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); break; case NGX_QUIC_FT_STOP_SENDING: - frame->u.stop_sending.id = ngx_quic_parse_int(&p); - frame->u.stop_sending.error_code = ngx_quic_parse_int(&p); - break; - case NGX_QUIC_FT_STREAMS_BLOCKED: - frame->u.streams_blocked.limit = ngx_quic_parse_int(&p); - frame->u.streams_blocked.bidi = 1; + p = ngx_quic_parse_int_multi(p, end, &f->u.stop_sending.id, + &f->u.stop_sending.error_code, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse stop sending frame"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "STOP SENDING frame { id 0x%xi error_code 0x%xi}", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: - frame->u.streams_blocked.limit = ngx_quic_parse_int(&p); - frame->u.streams_blocked.bidi = 0; + + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse streams blocked frame limit"); + return NGX_ERROR; + } + + f->u.streams_blocked.bidi = + (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "STREAMS BLOCKED frame { limit %i bidi: %d }", + f->u.streams_blocked.limit, + f->u.streams_blocked.bidi); + break; default: + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unsupported frame type 0x%xd in packet", f->type); + return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 565161f93..51f03f39f 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -82,8 +82,8 @@ typedef struct { typedef struct { - size_t offset; - size_t len; + uint64_t offset; + uint64_t len; u_char *data; } ngx_quic_crypto_frame_t; @@ -91,7 +91,7 @@ typedef struct { typedef struct { uint64_t seqnum; uint64_t retire; - uint64_t len; + uint8_t len; u_char cid[20]; u_char srt[16]; } ngx_quic_new_conn_id_frame_t; @@ -166,8 +166,8 @@ typedef struct { struct ngx_quic_secret_s *secret; ngx_uint_t type; - ngx_uint_t *number; - ngx_uint_t flags; + ngx_uint_t *number; + uint8_t flags; uint32_t version; ngx_str_t token; enum ssl_encryption_level_t level; @@ -197,7 +197,7 @@ ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); -ssize_t ngx_quic_parse_frame(u_char *start, u_char *end, +ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); size_t ngx_quic_frame_len(ngx_quic_frame_t *frame); -- cgit v1.2.3 From 1b4b8af624c066c4adc49048686cface6671ddb6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 19 Mar 2020 14:59:55 +0300 Subject: The ngx_quic_frame_len() function is not really needed. --- src/event/ngx_event_quic.c | 2 +- src/event/ngx_event_quic_transport.c | 27 --------------------------- src/event/ngx_event_quic_transport.h | 1 - 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8ef6ab213..6f83bfd51 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -997,7 +997,7 @@ ngx_quic_output(ngx_connection_t *c) do { /* process same-level group of frames */ - len += ngx_quic_frame_len(f);// TODO: handle overflow, max size + len += ngx_quic_create_frame(NULL, NULL, f);// TODO: handle overflow, max size f = f->next; } while (f && f->level == lvl); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index c3b4b8bc6..ffcc7d5c1 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -823,33 +823,6 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) } -size_t -ngx_quic_frame_len(ngx_quic_frame_t *frame) -{ - switch (frame->type) { - case NGX_QUIC_FT_ACK: - return ngx_quic_create_ack(NULL, &frame->u.ack); - case NGX_QUIC_FT_CRYPTO: - return ngx_quic_create_crypto(NULL, &frame->u.crypto); - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - return ngx_quic_create_stream(NULL, &frame->u.stream); - case NGX_QUIC_FT_CONNECTION_CLOSE: - return ngx_quic_create_close(NULL, &frame->u.close); - default: - /* BUG: unsupported frame type generated */ - return 0; - } -} - - static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 51f03f39f..311ff9441 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -200,6 +200,5 @@ ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); -size_t ngx_quic_frame_len(ngx_quic_frame_t *frame); #endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ -- cgit v1.2.3 From 06294ab67abc5a95ea75facc07d2a3ac8f07b381 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 19 Mar 2020 17:22:43 +0300 Subject: Fixed build. --- src/event/ngx_event_quic_transport.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ffcc7d5c1..7927f07ec 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -397,7 +397,7 @@ ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) { u_char *p, *end; - uint64_t plen; + uint64_t varint; p = pkt->raw->pos; @@ -405,12 +405,14 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) pkt->log->action = "parsing quic initial header"; - p = ngx_quic_parse_int(p, end, &pkt->token.len); + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "failed to parse token length"); return NGX_ERROR; } + pkt->token.len = varint; + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -418,22 +420,22 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) return NGX_ERROR; } - p = ngx_quic_parse_int(p, end, &plen); + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "bad packet length"); return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %d", plen); + "quic packet length: %d", varint); - if (plen > (uint64_t) ((pkt->data + pkt->len) - p)) { + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated initial packet"); return NGX_ERROR; } pkt->raw->pos = p; - pkt->len = plen; + pkt->len = varint; ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len); ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); @@ -483,19 +485,22 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *f) { - u_char *p; + u_char *p; + uint64_t varint; p = start; /* TODO: add a check if frame is allowed in this type of packet */ - p = ngx_quic_parse_int(p, end, &f->type); + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "failed to obtain quic frame type"); return NGX_ERROR; } + f->type = varint; + switch (f->type) { case NGX_QUIC_FT_CRYPTO: -- cgit v1.2.3 From 2710df57c7ec3e33d1bc03ad6e851fa54edc4f69 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 19 Mar 2020 17:33:36 +0300 Subject: Fixed specifiers in "quic packet length" logging. --- src/event/ngx_event_quic_transport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7927f07ec..91fe882af 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -427,7 +427,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %d", varint); + "quic packet length: %uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated initial packet"); @@ -463,7 +463,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %d", plen); + "quic packet length: %uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated handshake packet"); -- cgit v1.2.3 From ea6809ac736f41a1273419cad08713468a3495f6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 20 Mar 2020 09:23:31 +0300 Subject: Fixed ACKs to packet numbers greater than 63. --- src/event/ngx_event_quic_transport.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 91fe882af..7b8b93b5c 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -831,7 +831,8 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { - size_t len; + size_t len; + u_char *start; /* minimal ACK packet */ @@ -845,13 +846,15 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) return len; } + start = p; + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ngx_quic_build_int(&p, ack->pn); ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, ack->pn); - return 5; + return p - start; } -- cgit v1.2.3 From 3ba0d03a6eea3b38ece6a8aca4ceacdb9dbf039e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 20 Mar 2020 10:14:58 +0300 Subject: Double MAX_STREAMS on STREAMS_BLOCKED. --- src/event/ngx_event_quic.c | 39 +++++++++++++++++++++++++++++++++++- src/event/ngx_event_quic_transport.c | 29 +++++++++++++++++++++++++++ src/event/ngx_event_quic_transport.h | 7 +++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6f83bfd51..adce820a4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -88,6 +88,8 @@ static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); +static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -797,7 +799,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: - /* TODO: handle; need ack ? */ + + if (ngx_quic_handle_streams_blocked_frame(c, pkt, + &frame.u.streams_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + ack_this = 1; break; default: @@ -952,6 +962,33 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + ngx_quic_frame_t *frame; + + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + frame->u.max_streams.limit = f->limit * 2; + frame->u.max_streams.bidi = f->bidi; + + ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d", + (int) frame->u.max_streams.limit, + (int) frame->u.max_streams.bidi, + frame->level); + + ngx_quic_queue_frame(c->quic, frame); + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7b8b93b5c..9d7302021 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -66,6 +66,8 @@ static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); +static size_t ngx_quic_create_max_streams(u_char *p, + ngx_quic_max_streams_frame_t *ms); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); @@ -821,6 +823,9 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) case NGX_QUIC_FT_CONNECTION_CLOSE: return ngx_quic_create_close(p, &f->u.close); + case NGX_QUIC_FT_MAX_STREAMS: + return ngx_quic_create_max_streams(p, &f->u.max_streams); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -934,6 +939,30 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) } +static size_t +ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 311ff9441..8e6d81fe5 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -140,6 +140,12 @@ typedef struct { } ngx_quic_streams_blocked_frame_t; +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_max_streams_frame_t; + + typedef struct ngx_quic_frame_s ngx_quic_frame_t; struct ngx_quic_frame_s { @@ -156,6 +162,7 @@ struct ngx_quic_frame_s { ngx_quic_reset_stream_frame_t reset_stream; ngx_quic_stop_sending_frame_t stop_sending; ngx_quic_streams_blocked_frame_t streams_blocked; + ngx_quic_max_streams_frame_t max_streams; } u; u_char info[128]; // for debug }; -- cgit v1.2.3 From 5f9b188c21627737000238280140c371c9f33272 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 19 Mar 2020 21:46:28 +0300 Subject: Reset QUIC timeout on every datagram. --- src/event/ngx_event_quic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index adce820a4..629d83360 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -478,6 +478,8 @@ ngx_quic_handshake_handler(ngx_event_t *rev) return; } + ngx_add_timer(rev, c->quic->streams.timeout); + if (c->close) { ngx_quic_close_connection(c); return; -- cgit v1.2.3 From 30de0ca52dc7d460a184781e82288b0e2723ebce Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 13:47:44 +0300 Subject: Configurable transport parameters. - integer parameters can be configured using the following directives: quic_max_idle_timeout quic_max_ack_delay quic_max_packet_size quic_initial_max_data quic_initial_max_stream_data_bidi_local quic_initial_max_stream_data_bidi_remote quic_initial_max_stream_data_uni quic_initial_max_streams_bidi quic_initial_max_streams_uni quic_ack_delay_exponent quic_active_migration quic_active_connection_id_limit - only following parameters are actually sent: active_connection_id_limit initial_max_streams_uni initial_max_streams_bidi initial_max_stream_data_bidi_local initial_max_stream_data_bidi_remote initial_max_stream_data_uni (other parameters are to be added into ngx_quic_create_transport_params() function as needed, should be easy now) - draft 24 and draft 27 are now supported (at compile-time using quic_version macro) --- src/event/ngx_event_quic.c | 40 +++++--- src/event/ngx_event_quic.h | 27 +++++- src/event/ngx_event_quic_transport.c | 104 +++++++++++++++++++- src/event/ngx_event_quic_transport.h | 21 ++++ src/http/ngx_http_request.c | 5 +- src/http/v3/ngx_http_v3.h | 5 + src/http/v3/ngx_http_v3_module.c | 182 ++++++++++++++++++++++++++++++++++- 7 files changed, 361 insertions(+), 23 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 629d83360..2f623ee30 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -32,6 +32,8 @@ struct ngx_quic_connection_s { ngx_str_t dcid; ngx_str_t token; + ngx_quic_tp_t tp; + /* current packet numbers for each namespace */ ngx_uint_t initial_pn; ngx_uint_t handshake_pn; @@ -67,7 +69,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_header_t *pkt); + ngx_quic_tp_t *tp, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_handshake_handler(ngx_event_t *rev); static void ngx_quic_close_connection(ngx_connection_t *c); @@ -283,8 +285,8 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void -ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, - ngx_connection_handler_pt handler) +ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, + ngx_msec_t timeout, ngx_connection_handler_pt handler) { ngx_buf_t *b; ngx_quic_header_t pkt; @@ -302,7 +304,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, pkt.data = b->start; pkt.len = b->last - b->start; - if (ngx_quic_new_connection(c, ssl, &pkt) != NGX_OK) { + if (ngx_quic_new_connection(c, ssl, tp, &pkt) != NGX_OK) { ngx_quic_close_connection(c); return; } @@ -320,7 +322,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; @@ -354,6 +356,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, c->quic = qc; qc->ssl = ssl; + qc->tp = *tp; qc->dcid.len = pkt->dcid.len; qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); @@ -402,19 +405,11 @@ static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { int n, sslerr; + u_char *p; + ssize_t len; ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; - static const uint8_t params[] = - "\x00\x29" /* parameters length: 41 bytes */ - "\x00\x0e\x00\x01\x05" /* active connection id limit: 5 */ - "\x00\x04\x00\x04\x80\x98\x96\x80" /* initial max data = 10000000 */ - "\x00\x09\x00\x01\x03" /* initial max streams uni: 3 */ - "\x00\x08\x00\x01\x10" /* initial max streams bidi: 16 */ - "\x00\x05\x00\x02\x40\xff" /* initial max stream bidi local: 255 */ - "\x00\x06\x00\x02\x40\xff" /* initial max stream bidi remote: 255 */ - "\x00\x07\x00\x02\x40\xff"; /* initial max stream data uni: 255 */ - qc = c->quic; if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { @@ -429,7 +424,20 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } - if (SSL_set_quic_transport_params(ssl_conn, params, sizeof(params) - 1) == 0) { + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp); + if (len < 0) { + return NGX_ERROR; + } + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "SSL_set_quic_transport_params() failed"); return NGX_ERROR; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index b3b86d99b..e30186d1b 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -15,6 +15,29 @@ //#define quic_version 0xff00001b /* draft-27 (FFN 76) */ +typedef struct { + /* configurable */ + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + ngx_uint_t max_packet_size; + ngx_uint_t initial_max_data; + ngx_uint_t initial_max_stream_data_bidi_local; + ngx_uint_t initial_max_stream_data_bidi_remote; + ngx_uint_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t disable_active_migration; + ngx_uint_t active_connection_id_limit; + + /* TODO */ + ngx_uint_t original_connection_id; + u_char stateless_reset_token[16]; + void *preferred_address; +} ngx_quic_tp_t; + + struct ngx_quic_stream_s { uint64_t id; ngx_uint_t unidirectional:1; @@ -25,8 +48,8 @@ struct ngx_quic_stream_s { void ngx_quic_init_ssl_methods(SSL_CTX* ctx); -void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_msec_t timeout, - ngx_connection_handler_pt handler); +void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, + ngx_msec_t timeout, ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 9d7302021..0eee5052f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -87,6 +87,7 @@ static char *ngx_quic_errors[] = { "INVALID_TOKEN", "", "CRYPTO_BUFFER_EXCEEDED", + "", "CRYPTO_ERROR", }; @@ -639,11 +640,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } - if (f->u.close.error_code > NGX_QUIC_ERR_LAST) { + if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unkown error code: %ui, truncated", f->u.close.error_code); - f->u.close.error_code = NGX_QUIC_ERR_LAST; + f->u.close.error_code = NGX_QUIC_ERR_LAST - 1; } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -963,6 +964,105 @@ ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) } +ssize_t +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) +{ + u_char *p; + size_t len; + +#if (quic_version < 0xff00001b) + +/* older drafts with static transport parameters encoding */ + +#define ngx_quic_tp_len(id, value) \ + 4 + ngx_quic_varint_len(value) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + p = ngx_quic_write_uint16(p, id); \ + p = ngx_quic_write_uint16(p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#else + +/* recent drafts with variable integer transport parameters encoding */ + +#define ngx_quic_tp_len(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value) \ + + ngx_quic_varint_len(ngx_quic_varint_len(value)) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#endif + + p = pos; + + len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA,tp->initial_max_data); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + if (pos == NULL) { +#if (quic_version < 0xff00001b) + len += ngx_quic_varint_len(len); +#endif + return len; + } + +#if (quic_version < 0xff00001b) + /* TLS extension length */ + p = ngx_quic_write_uint16(p, len); +#endif + + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, + tp->initial_max_data); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + ngx_quic_hexdump0(ngx_cycle->log, "transport parameters", pos, p - pos); + + return p - pos; +} + + static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 8e6d81fe5..9fd6ac16b 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -65,10 +65,28 @@ #define NGX_QUIC_ERR_INVALID_TOKEN 0x0B /* 0xC is not defined */ #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +/* 0xE is not defined */ #define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 #define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR +/* Transport parameters */ +#define NGX_QUIC_TP_ORIGINAL_CONNECTION_ID 0x00 +#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02 +#define NGX_QUIC_TP_MAX_PACKET_SIZE 0x03 +#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0a +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0b +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0c +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0d +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0e + typedef struct { ngx_uint_t pn; @@ -208,4 +226,7 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); +ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp); + #endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 687de931c..6c9fdc543 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -341,11 +341,14 @@ ngx_http_init_connection(ngx_connection_t *c) #if (NGX_HTTP_V3) if (hc->quic) { + ngx_http_v3_srv_conf_t *v3cf; ngx_http_ssl_srv_conf_t *sscf; + v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - ngx_quic_run(c, &sscf->ssl, c->listening->post_accept_timeout, + ngx_quic_run(c, &sscf->ssl, &v3cf->quic, + c->listening->post_accept_timeout, ngx_http_quic_stream_handler); return; } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index fc1cadd79..2c63f544e 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -39,6 +39,11 @@ #define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 +typedef struct { + ngx_quic_tp_t quic; +} ngx_http_v3_srv_conf_t; + + typedef struct { ngx_http_connection_t hc; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index af310bd44..6d3f34d41 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -11,10 +11,100 @@ static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("quic_max_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.max_idle_timeout), + NULL }, + + { ngx_string("quic_max_ack_delay"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay), + NULL }, + + { ngx_string("quic_max_packet_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.max_packet_size), + NULL }, + + { ngx_string("quic_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_data), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_local"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_local), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_remote"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_remote), + NULL }, + + { ngx_string("quic_initial_max_stream_data_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_uni), + NULL }, + + { ngx_string("quic_initial_max_streams_bidi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_bidi), + NULL }, + + { ngx_string("quic_initial_max_streams_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_uni), + NULL }, + + { ngx_string("quic_ack_delay_exponent"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.ack_delay_exponent), + NULL }, + + { ngx_string("quic_active_migration"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.disable_active_migration), + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), + NULL }, + ngx_null_command }; +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, + void *parent, void *child); + + static ngx_http_module_t ngx_http_v3_module_ctx = { NULL, /* preconfiguration */ NULL, /* postconfiguration */ @@ -22,8 +112,8 @@ static ngx_http_module_t ngx_http_v3_module_ctx = { NULL, /* create main configuration */ NULL, /* init main configuration */ - NULL, /* create server configuration */ - NULL, /* merge server configuration */ + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ @@ -44,3 +134,91 @@ ngx_module_t ngx_http_v3_module = { NULL, /* exit master */ NGX_MODULE_V1_PADDING }; + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *v3cf; + + v3cf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (v3cf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * v3cf->quic.original_connection_id = 0; + * v3cf->quic.stateless_reset_token = { 0 } + * conf->quic.preferred_address = NULL + */ + + v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC; + v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC; + + v3cf->quic.max_packet_size = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_data = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; + v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT; + v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT; + v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT; + v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + return v3cf; +} + + +static char * +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_conf_merge_msec_value(conf->quic.max_idle_timeout, + prev->quic.max_idle_timeout, 10000); + + // > 2 ^ 14 is invalid + ngx_conf_merge_msec_value(conf->quic.max_ack_delay, + prev->quic.max_ack_delay, 25); + + // < 1200 is invalid + ngx_conf_merge_uint_value(conf->quic.max_packet_size, + prev->quic.max_packet_size, 65527); + + ngx_conf_merge_uint_value(conf->quic.initial_max_data, + prev->quic.initial_max_data, 10000000); + + ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local, + prev->quic.initial_max_stream_data_bidi_local, + 255); + + ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote, + prev->quic.initial_max_stream_data_bidi_remote, + 255); + + ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni, + prev->quic.initial_max_stream_data_uni, 255); + + ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi, + prev->quic.initial_max_streams_bidi, 16); + + ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni, + prev->quic.initial_max_streams_uni, 16); + + // > 20 is invalid + ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent, + prev->quic.ack_delay_exponent, 3); + + ngx_conf_merge_uint_value(conf->quic.disable_active_migration, + prev->quic.disable_active_migration, 1); + + // < 2 is invalid + ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, + prev->quic.active_connection_id_limit, 2); + + return NGX_CONF_OK; +} + -- cgit v1.2.3 From 4096676897ba547079e96230a16587150835d356 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 12:44:45 +0300 Subject: Adedd the http "quic" variable. The value is literal "quic" for requests passed over HTTP/3, and empty string otherwise. --- src/http/v3/ngx_http_v3_module.c | 53 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 6d3f34d41..20137b377 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -100,13 +100,16 @@ static ngx_command_t ngx_http_v3_commands[] = { }; +static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_http_module_t ngx_http_v3_module_ctx = { - NULL, /* preconfiguration */ + ngx_http_v3_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ @@ -136,6 +139,54 @@ ngx_module_t ngx_http_v3_module = { }; +static ngx_http_variable_t ngx_http_v3_vars[] = { + { ngx_string("quic"), NULL, ngx_http_variable_quic, + 0, 0, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->connection->qs) { + + v->len = 4; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + v->data = (u_char *) "quic"; + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + + static void * ngx_http_v3_create_srv_conf(ngx_conf_t *cf) { -- cgit v1.2.3 From 6565860bd852623ecba8e6329310f4810f8c3dc5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 14:50:05 +0300 Subject: Added parsing of CONNECTION_CLOSE2 frame (0x1D). The difference is that error code refers to application namespace, i.e. quic error names cannot be used to convert it to string. --- src/event/ngx_event_quic_transport.c | 32 +++++++++++++++++++++----------- src/event/ngx_event_quic_transport.h | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 0eee5052f..23921a04d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -622,6 +622,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE2: p = ngx_quic_parse_int_multi(p, end, &f->u.close.error_code, &f->u.close.frame_type, @@ -640,18 +641,27 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } - if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "unkown error code: %ui, truncated", - f->u.close.error_code); - f->u.close.error_code = NGX_QUIC_ERR_LAST - 1; - } + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", - ngx_quic_error_text(f->u.close.error_code), - f->u.close.error_code, f->u.close.frame_type, - &f->u.close.reason); + if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unkown error code: %ui, truncated", + f->u.close.error_code); + f->u.close.error_code = NGX_QUIC_ERR_LAST - 1; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + ngx_quic_error_text(f->u.close.error_code), + f->u.close.error_code, f->u.close.frame_type, + &f->u.close.reason); + } else { + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "CONN.CLOSE2: { (0x%xi) type=0x%xi reason '%V'}", + f->u.close.error_code, f->u.close.frame_type, + &f->u.close.reason); + } break; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 9fd6ac16b..39a11040f 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -47,7 +47,7 @@ #define NGX_QUIC_FT_PATH_CHALLENGE 0x1A #define NGX_QUIC_FT_PATH_RESPONSE 0x1B #define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C -#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D // XXX +#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E /* 22.4. QUIC Transport Error Codes Registry */ -- cgit v1.2.3 From 1d35d0f31e4fefd7c8381e79d0207fa1e092d550 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 15:14:00 +0300 Subject: Fixed parsing of CONNECTION CLOSE2 frames. The "frame_type" field is not passed in case of 0x1d frame. --- src/event/ngx_event_quic_transport.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 23921a04d..2ea80ca7e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -624,12 +624,24 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE2: - p = ngx_quic_parse_int_multi(p, end, &f->u.close.error_code, - &f->u.close.frame_type, - &f->u.close.reason.len, NULL); + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "failed to parse close connection frame"); + "failed to parse close connection frame error code"); + return NGX_ERROR; + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse close connection frame type"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.close.reason.len); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse close reason length"); return NGX_ERROR; } @@ -657,10 +669,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, &f->u.close.reason); } else { - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "CONN.CLOSE2: { (0x%xi) type=0x%xi reason '%V'}", - f->u.close.error_code, f->u.close.frame_type, - &f->u.close.reason); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "CONN.CLOSE2: { (0x%xi) reason '%V'}", + f->u.close.error_code, &f->u.close.reason); } break; -- cgit v1.2.3 From 21b6854bfe0befc3d985bb49cd9aba467128a093 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 20:03:44 +0300 Subject: Added checks for permitted frame types. + cleanup in macros for packet types + some style fixes in quic_transport.h (case, indentation) --- src/event/ngx_event_quic.c | 21 ++++-- src/event/ngx_event_quic_protection.c | 4 +- src/event/ngx_event_quic_transport.c | 99 +++++++++++++++++++++++++++-- src/event/ngx_event_quic_transport.h | 116 ++++++++++++++++++---------------- 4 files changed, 172 insertions(+), 68 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2f623ee30..86bf815dc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -336,7 +336,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_INITIAL) { + if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet: 0x%xi", pkt->flags); return NGX_ERROR; @@ -563,20 +563,21 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.data = p; pkt.len = b->last - p; pkt.log = c->log; + pkt.flags = p[0]; - if (p[0] == 0) { + if (pkt.flags == 0) { /* XXX: no idea WTF is this, just ignore */ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES"); break; } // TODO: check current state - if (p[0] & NGX_QUIC_PKT_LONG) { + if (ngx_quic_long_pkt(pkt.flags)) { - if ((p[0] & 0xf0) == NGX_QUIC_PKT_INITIAL) { + if (ngx_quic_pkt_in(pkt.flags)) { rc = ngx_quic_initial_input(c, &pkt); - } else if ((p[0] & 0xf0) == NGX_QUIC_PKT_HANDSHAKE) { + } else if (ngx_quic_pkt_hs(pkt.flags)) { rc = ngx_quic_handshake_input(c, &pkt); } else { @@ -665,7 +666,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if ((pkt->flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) { + if (!ngx_quic_pkt_hs(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type: 0x%xi", pkt->flags); return NGX_ERROR; @@ -734,6 +735,14 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) while (p < end) { len = ngx_quic_parse_frame(pkt, p, end, &frame); + + if (len == NGX_DECLINED) { + /* TODO: handle protocol violation: + * such frame not allowed in this packet + */ + return NGX_ERROR; + } + if (len < 0) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 23b8c011f..f648bbee8 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -904,7 +904,7 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } - if (pkt->flags & NGX_QUIC_PKT_LONG) { + if (ngx_quic_long_pkt(pkt->flags)) { clearflags = pkt->flags ^ (mask[0] & 0x0f); } else { @@ -926,7 +926,7 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, in.data = p; - if (pkt->flags & NGX_QUIC_PKT_LONG) { + if (ngx_quic_long_pkt(pkt->flags)) { in.len = pkt->len - pnl; } else { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 2ea80ca7e..b12ea2bc1 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -261,7 +261,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) return NGX_ERROR; } - if (!(pkt->flags & NGX_QUIC_PKT_LONG)) { + if (!ngx_quic_long_pkt(pkt->flags)) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a long packet"); return NGX_ERROR; } @@ -368,7 +368,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) return NGX_ERROR; } - if (pkt->flags & NGX_QUIC_PKT_LONG) { + if (!ngx_quic_short_pkt(pkt->flags)) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a short packet"); return NGX_ERROR; } @@ -489,12 +489,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *f) { u_char *p; + uint8_t flags; uint64_t varint; + flags = pkt->flags; p = start; - /* TODO: add a check if frame is allowed in this type of packet */ - p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -508,6 +508,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_CRYPTO: + if (ngx_quic_pkt_zrtt(flags)) { + goto not_allowed; + } + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -539,6 +543,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; case NGX_QUIC_FT_PADDING: + + /* allowed in any packet type */ + while (p < end && *p == NGX_QUIC_FT_PADDING) { p++; } @@ -548,6 +555,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: + if (ngx_quic_pkt_zrtt(flags)) { + goto not_allowed; + } + p = ngx_quic_parse_int_multi(p, end, &f->u.ack.largest, &f->u.ack.delay, &f->u.ack.range_count, &f->u.ack.first_range, NULL); @@ -583,10 +594,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; case NGX_QUIC_FT_PING: + + /* allowed in any packet type */ + break; case NGX_QUIC_FT_NEW_CONNECTION_ID: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + p = ngx_quic_parse_int_multi(p, end, &f->u.ncid.seqnum, &f->u.ncid.retire, NULL); if (p == NULL) { @@ -621,9 +639,20 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; - case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE2: + if (!ngx_quic_short_pkt(flags)) { + goto not_allowed; + } + + /* fall through */ + + case NGX_QUIC_FT_CONNECTION_CLOSE: + + if (ngx_quic_pkt_zrtt(flags)) { + goto not_allowed; + } + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -685,6 +714,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + f->u.stream.type = f->type; f->u.stream.off = ngx_quic_stream_bit_off(f->type); @@ -743,6 +776,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_MAX_DATA: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -757,6 +794,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_RESET_STREAM: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + p = ngx_quic_parse_int_multi(p, end, &f->u.reset_stream.id, &f->u.reset_stream.error_code, &f->u.reset_stream.final_size, NULL); @@ -775,6 +816,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STOP_SENDING: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + p = ngx_quic_parse_int_multi(p, end, &f->u.stop_sending.id, &f->u.stop_sending.error_code, NULL); if (p == NULL) { @@ -792,6 +837,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, @@ -809,14 +858,52 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; + /* TODO: implement parsing for all frames below */ + case NGX_QUIC_FT_NEW_TOKEN: + case NGX_QUIC_FT_HANDSHAKE_DONE: + + if (!ngx_quic_short_pkt(flags)) { + goto not_allowed; + } + + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unimplemented frame type 0x%xi in packet", f->type); + + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + case NGX_QUIC_FT_MAX_STREAM_DATA: + case NGX_QUIC_FT_DATA_BLOCKED: + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unimplemented frame type 0x%xi in packet", f->type); + break; + default: ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "unsupported frame type 0x%xd in packet", f->type); + "unknown frame type 0x%xi in packet", f->type); return NGX_ERROR; } return p - start; + +not_allowed: + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "frame type 0x%xi is not allowed in packet with flags 0x%xi", + f->type, pkt->flags); + + return NGX_DECLINED; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 39a11040f..a6d64aeb9 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -11,62 +11,70 @@ #include -/* 17.2. Long Header Packets */ -#define NGX_QUIC_PKT_LONG 0x80 +#define ngx_quic_long_pkt(flags) ((flags) & 0x80) /* 17.2 */ +#define ngx_quic_short_pkt(flags) (((flags) & 0x80) == 0) /* 17.3 */ -#define NGX_QUIC_PKT_INITIAL 0xC0 -#define NGX_QUIC_PKT_HANDSHAKE 0xE0 +/* Long packet types */ +#define NGX_QUIC_PKT_INITIAL 0xC0 /* 17.2.2 */ +#define NGX_QUIC_PKT_ZRTT 0xD0 /* 17.2.3 */ +#define NGX_QUIC_PKT_HANDSHAKE 0xE0 /* 17.2.4 */ +#define NGX_QUIC_PKT_RETRY 0xF0 /* 17.2.5 */ + +#define ngx_quic_pkt_in(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_RETRY) /* 12.4. Frames and Frame Types */ -#define NGX_QUIC_FT_PADDING 0x00 -#define NGX_QUIC_FT_PING 0x01 -#define NGX_QUIC_FT_ACK 0x02 -#define NGX_QUIC_FT_ACK_ECN 0x03 -#define NGX_QUIC_FT_RESET_STREAM 0x04 -#define NGX_QUIC_FT_STOP_SENDING 0x05 -#define NGX_QUIC_FT_CRYPTO 0x06 -#define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM0 0x08 -#define NGX_QUIC_FT_STREAM1 0x09 -#define NGX_QUIC_FT_STREAM2 0x0A -#define NGX_QUIC_FT_STREAM3 0x0B -#define NGX_QUIC_FT_STREAM4 0x0C -#define NGX_QUIC_FT_STREAM5 0x0D -#define NGX_QUIC_FT_STREAM6 0x0E -#define NGX_QUIC_FT_STREAM7 0x0F -#define NGX_QUIC_FT_MAX_DATA 0x10 -#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 -#define NGX_QUIC_FT_MAX_STREAMS 0x12 -#define NGX_QUIC_FT_MAX_STREAMS2 0x13 // XXX -#define NGX_QUIC_FT_DATA_BLOCKED 0x14 -#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 -#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 -#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 // XXX -#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 -#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 -#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A -#define NGX_QUIC_FT_PATH_RESPONSE 0x1B -#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C -#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D -#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM0 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E /* 22.4. QUIC Transport Error Codes Registry */ -#define NGX_QUIC_ERR_NO_ERROR 0x00 -#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 -#define NGX_QUIC_ERR_SERVER_BUSY 0x02 -#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 -#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 -#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 -#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 -#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 -#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 -#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 -#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A -#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_SERVER_BUSY 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B /* 0xC is not defined */ -#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D /* 0xE is not defined */ -#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 #define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR @@ -81,11 +89,11 @@ #define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 #define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 #define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 -#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0a -#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0b -#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0c -#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0d -#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0e +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E typedef struct { -- cgit v1.2.3 From b26d5deae47bd3e8532ee8ce5f984455afd372f8 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 20 Mar 2020 20:39:41 +0300 Subject: Removed unused variable. --- src/event/ngx_event_quic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 86bf815dc..307129595 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -635,11 +635,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; qc = c->quic; - ssl_conn = c->ssl->connection; /* extract cleartext data into pkt */ if (ngx_quic_parse_long_header(pkt) != NGX_OK) { -- cgit v1.2.3 From 0f77eac8af88e461c1b695094e63db249841ce0e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 20 Mar 2020 23:49:42 +0300 Subject: Removed unused variable. --- src/http/v3/ngx_http_v3_request.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 9a2654fd4..542fc387d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -245,7 +245,7 @@ ngx_chain_t * ngx_http_v3_create_header(ngx_http_request_t *r) { u_char *p; - size_t len, hlen, n; + size_t len, n; ngx_buf_t *b; ngx_uint_t i, j; ngx_chain_t *hl, *cl, *bl; @@ -553,8 +553,6 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; - hlen = 1 + ngx_http_v3_encode_varlen_int(NULL, len); - if (r->headers_out.content_length_n >= 0) { len = 1 + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); -- cgit v1.2.3 From 4764ef15902bc749356507fc01aa7503e17c81a8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 21 Mar 2020 18:44:10 +0300 Subject: Fixed build with macOS's long long abomination. --- src/event/ngx_event_quic_transport.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index b12ea2bc1..577ad7d45 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -667,13 +667,15 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } - p = ngx_quic_parse_int(p, end, &f->u.close.reason.len); + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "failed to parse close reason length"); return NGX_ERROR; } + f->u.close.reason.len = varint; + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, &f->u.close.reason.data); if (p == NULL) { -- cgit v1.2.3 From 79e49c2a162dd777ff0ab16954d5ee7c58da56e8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 21 Mar 2020 19:22:39 +0300 Subject: Fixed buffer overrun in create_transport_params() with -24. It writes 16-bit prefix as designed, but length calculation assumed varint. --- src/event/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 577ad7d45..826af2bdd 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1136,7 +1136,7 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) if (pos == NULL) { #if (quic_version < 0xff00001b) - len += ngx_quic_varint_len(len); + len += 2; #endif return len; } -- cgit v1.2.3 From 2af37e507db9648f1f11a7ad39c516445f126de2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 21 Mar 2020 19:45:24 +0300 Subject: Fixed parsing NGX_QUIC_FT_CONNECTION_CLOSE. --- src/event/ngx_event_quic_transport.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 826af2bdd..e03194b80 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -662,9 +662,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "failed to parse close connection frame type"); - return NGX_ERROR; + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse close connection frame type"); + return NGX_ERROR; + } } p = ngx_quic_parse_int(p, end, &varint); -- cgit v1.2.3 From 63e6c9349e0e164ab2b7a368b473ed6e054e83c0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Sat, 21 Mar 2020 20:49:55 +0300 Subject: Implemented parsing of remaining frame types. --- src/event/ngx_event_quic_transport.c | 128 ++++++++++++++++++++++++++++++++++- src/event/ngx_event_quic_transport.h | 33 +++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index e03194b80..dadc4f197 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -856,7 +856,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "STREAMS BLOCKED frame { limit %i bidi: %d }", + "STREAMS BLOCKED frame { limit %ui bidi: %d }", f->u.streams_blocked.limit, f->u.streams_blocked.bidi); @@ -877,19 +877,141 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_MAX_STREAMS: case NGX_QUIC_FT_MAX_STREAMS2: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse max streams frame limit"); + return NGX_ERROR; + } + + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "MAX STREAMS frame { limit %ui bidi: %d }", + f->u.max_streams.limit, + f->u.max_streams.bidi); + break; + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_parse_int_multi(p, end, &f->u.max_stream_data.id, + &f->u.max_stream_data.limit, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse max stream data frame"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "MAX STREAM DATA frame { id: %ui limit: %ui }", + f->u.max_stream_data.id, + f->u.max_stream_data.limit); + break; + case NGX_QUIC_FT_DATA_BLOCKED: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse data blocked frame limit"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "DATA BLOCKED frame { limit %ui }", + f->u.data_blocked.limit); + break; + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_parse_int_multi(p, end, &f->u.stream_data_blocked.id, + &f->u.stream_data_blocked.limit, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse tream data blocked frame"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "STREAM DATA BLOCKED frame { id: %ui limit: %ui }", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to parse retire connection id" + " frame sequence number"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "RETIRE CONNECTION ID frame { sequence_number %ui }", + f->u.retire_cid.sequence_number); + break; + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { + goto not_allowed; + } + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to get path challenge frame data"); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "PATH CHALLENGE frame"); + + ngx_quic_hexdump0(pkt->log, "path challenge data", + f->u.path_challenge.data, 8); + break; + case NGX_QUIC_FT_PATH_RESPONSE: if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { goto not_allowed; } - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "unimplemented frame type 0x%xi in packet", f->type); + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "failed to get path response frame data"); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "PATH RESPONSE frame"); + + ngx_quic_hexdump0(pkt->log, "path response data", + f->u.path_response.data, 8); break; default: diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index a6d64aeb9..c8af85c33 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -172,6 +172,33 @@ typedef struct { } ngx_quic_max_streams_frame_t; +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_max_stream_data_frame_t; + + +typedef struct { + uint64_t limit; +} ngx_quic_data_blocked_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_stream_data_blocked_frame_t; + + +typedef struct { + uint64_t sequence_number; +} ngx_quic_retire_cid_frame_t; + + +typedef struct { + u_char data[8]; +} ngx_quic_path_challenge_frame_t; + + typedef struct ngx_quic_frame_s ngx_quic_frame_t; struct ngx_quic_frame_s { @@ -189,6 +216,12 @@ struct ngx_quic_frame_s { ngx_quic_stop_sending_frame_t stop_sending; ngx_quic_streams_blocked_frame_t streams_blocked; ngx_quic_max_streams_frame_t max_streams; + ngx_quic_max_stream_data_frame_t max_stream_data; + ngx_quic_data_blocked_frame_t data_blocked; + ngx_quic_stream_data_blocked_frame_t stream_data_blocked; + ngx_quic_retire_cid_frame_t retire_cid; + ngx_quic_path_challenge_frame_t path_challenge; + ngx_quic_path_challenge_frame_t path_response; } u; u_char info[128]; // for debug }; -- cgit v1.2.3 From ccb0049e3f23bee4946be3e2a930282a1d0f4e4b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sun, 22 Mar 2020 11:35:15 +0300 Subject: Closing connection on NGX_QUIC_FT_CONNECTION_CLOSE. --- src/event/ngx_event_quic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 307129595..f30bd6a97 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -779,6 +779,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE2: do_close = 1; break; @@ -839,7 +840,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (do_close) { - // TODO: handle stream close + ngx_quic_close_connection(c); + return NGX_OK; } if (ack_this == 0) { -- cgit v1.2.3 From de095d5f1dfa4beebaef5e05331da168605e162f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sun, 22 Mar 2020 12:15:54 +0300 Subject: Fixed CRYPTO offset generation. --- src/event/ngx_event_quic.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f30bd6a97..570945755 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -45,6 +45,9 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_uint_t max_data; + +#define SSL_ECRYPTION_LAST ((ssl_encryption_application) + 1) + uint64_t crypto_offset[SSL_ECRYPTION_LAST]; }; @@ -228,9 +231,12 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset += qc->crypto_offset[level]; frame->u.crypto.len = len; frame->u.crypto.data = p; + qc->crypto_offset[level] += len; + ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); ngx_quic_queue_frame(qc, frame); -- cgit v1.2.3 From 4490aefa703204129bf4becbcd5a5251f59abef7 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Sat, 21 Mar 2020 20:51:59 +0300 Subject: Added processing of client transport parameters. note: + parameters are available in SSL connection since they are obtained by ssl stack quote: During connection establishment, both endpoints make authenticated declarations of their transport parameters. These declarations are made unilaterally by each endpoint. and really, we send our parameters before we read client's. no handling of incoming parameters is made by this patch. --- src/event/ngx_event_quic.c | 33 ++++- src/event/ngx_event_quic_transport.c | 245 +++++++++++++++++++++++++++++++++++ src/event/ngx_event_quic_transport.h | 2 + 3 files changed, 279 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 570945755..5a87092cc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -32,6 +32,7 @@ struct ngx_quic_connection_s { ngx_str_t dcid; ngx_str_t token; + ngx_uint_t client_tp_done; ngx_quic_tp_t tp; /* current packet numbers for each namespace */ @@ -206,7 +207,10 @@ static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char *p; + u_char *p, *end; + size_t client_params_len; + const uint8_t *client_params; + ngx_quic_tp_t ctp; ngx_quic_frame_t *frame; ngx_connection_t *c; ngx_quic_connection_t *qc; @@ -217,6 +221,33 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_add_handshake_data"); + /* XXX: obtain client parameters after the handshake? */ + if (!qc->client_tp_done) { + + SSL_get_peer_quic_transport_params(ssl_conn, &client_params, + &client_params_len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL_get_peer_quic_transport_params(): params_len %ui", + client_params_len); + + if (client_params_len != 0) { + p = (u_char *) client_params; + end = p + client_params_len; + + ngx_memzero(&ctp, sizeof(ngx_quic_tp_t)); + + if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) != NGX_OK) + { + return NGX_ERROR; + } + + /* TODO: save/use obtained client parameters: merge with ours? */ + + qc->client_tp_done = 1; + } + } + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return 0; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index dadc4f197..b70d4da6e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -56,6 +56,7 @@ static u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...); static void ngx_quic_build_int(u_char **pos, uint64_t value); static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +/*static*/ u_char *ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value); // usage depends on quic_version static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out); @@ -70,6 +71,9 @@ static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); +static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, + uint16_t id, ngx_quic_tp_t *dst); + /* literal errors indexed by corresponding value */ static char *ngx_quic_errors[] = { @@ -167,6 +171,19 @@ ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) } +/*static*/ ngx_inline u_char * +ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint16_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint16(pos); + + return pos + sizeof(uint16_t); +} + + static ngx_inline u_char * ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) { @@ -1198,6 +1215,234 @@ ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) } +static ngx_int_t +ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, + ngx_quic_tp_t *dst) +{ + uint64_t varint; + + switch (id) { + case NGX_QUIC_TP_ORIGINAL_CONNECTION_ID: + case NGX_QUIC_TP_STATELESS_RESET_TOKEN: + case NGX_QUIC_TP_PREFERRED_ADDRESS: + // TODO + return NGX_ERROR; + } + + switch (id) { + + case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* zero-length option */ + if (end - p != 0) { + return NGX_ERROR; + } + dst->disable_active_migration = 1; + return NGX_OK; + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + case NGX_QUIC_TP_MAX_PACKET_SIZE: + case NGX_QUIC_TP_INITIAL_MAX_DATA: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + case NGX_QUIC_TP_MAX_ACK_DELAY: + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + return NGX_ERROR; + } + break; + + default: + return NGX_ERROR; + } + + switch (id) { + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + dst->max_idle_timeout = varint; + break; + + case NGX_QUIC_TP_MAX_PACKET_SIZE: + dst->max_packet_size = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_DATA: + dst->initial_max_data = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + dst->initial_max_stream_data_bidi_local = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + dst->initial_max_stream_data_bidi_remote = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + dst->initial_max_stream_data_uni = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + dst->initial_max_streams_bidi = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + dst->initial_max_streams_uni = varint; + break; + + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + dst->ack_delay_exponent = varint; + break; + + case NGX_QUIC_TP_MAX_ACK_DELAY: + dst->max_ack_delay = varint; + break; + + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + dst->active_connection_id_limit = varint; + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, + ngx_log_t *log) +{ + +#if (quic_version < 0xff00001b) + + uint16_t id, len, tp_len; + + p = ngx_quic_read_uint16(p, end, &tp_len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse total transport params length"); + return NGX_ERROR; + } + + while (p < end) { + + p = ngx_quic_read_uint16(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint16(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id 0x%xi length", id); + return NGX_ERROR; + } + + if (ngx_quic_parse_transport_param(p, p + len, id, tp) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id 0x%xi data", id); + return NGX_ERROR; + } + + p += len; + }; + +#else + + uint64_t id, len; + + while (p < end) { + p = ngx_quic_parse_int(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id 0x%xi length", id); + return NGX_ERROR; + } + + if (ngx_quic_parse_transport_param(p, p + len, id, tp) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "failed to parse transport param id 0x%xi data", id); + return NGX_ERROR; + } + + p += len; + + } + +#endif + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "trailing garbage in transport parameters: %ui bytes", + end - p); + return NGX_ERROR; + } + + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "client transport parameters parsed successfully"); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "disable active migration: %ui", + tp->disable_active_migration); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "idle timeout: %ui", + tp->max_idle_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max packet size: %ui", + tp->max_packet_size); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max data: %ui", + tp->initial_max_data); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "max stream data bidi local: %ui", + tp->initial_max_stream_data_bidi_local); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "max stream data bidi remote: %ui", + tp->initial_max_stream_data_bidi_remote); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max stream data uni: %ui", + tp->initial_max_stream_data_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "initial max streams bidi: %ui", + tp->initial_max_streams_bidi); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "initial max streams uni: %ui", + tp->initial_max_streams_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "ack delay exponent: %ui", + tp->ack_delay_exponent); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max ack delay: %ui", + tp->max_ack_delay); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "active connection id limit: %ui", + tp->active_connection_id_limit); + + return NGX_OK; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index c8af85c33..931361180 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -267,6 +267,8 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); +ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, ngx_log_t *log); ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp); -- cgit v1.2.3 From 6a3a0ee19f1b37edc9a69765ee745dae96082d96 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 23 Mar 2020 10:57:28 +0300 Subject: Add unsupported version into log. This makes it easier to understand what client wants. --- src/event/ngx_event_quic_transport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index b70d4da6e..61265b3d3 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -294,7 +294,8 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) "quic flags:%xi version:%xD", pkt->flags, pkt->version); if (pkt->version != quic_version) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unsupported quic version"); + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + "unsupported quic version: 0x%xi", pkt->version); return NGX_ERROR; } -- cgit v1.2.3 From b3129b46f69a46a04c1a4dad6137806b948b7a32 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 23 Mar 2020 12:57:24 +0300 Subject: Skip unknown transport parameters. --- src/event/ngx_event_quic_transport.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 61265b3d3..459d61c2d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1227,7 +1227,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, case NGX_QUIC_TP_STATELESS_RESET_TOKEN: case NGX_QUIC_TP_PREFERRED_ADDRESS: // TODO - return NGX_ERROR; + return NGX_DECLINED; } switch (id) { @@ -1259,7 +1259,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, break; default: - return NGX_ERROR; + return NGX_DECLINED; } switch (id) { @@ -1320,6 +1320,7 @@ ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log) { + ngx_int_t rc; #if (quic_version < 0xff00001b) @@ -1348,12 +1349,19 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, return NGX_ERROR; } - if (ngx_quic_parse_transport_param(p, p + len, id, tp) != NGX_OK) { + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, log, 0, "failed to parse transport param id 0x%xi data", id); return NGX_ERROR; } + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "unknown transport param id 0x%xi, skipped", id); + } + p += len; }; @@ -1376,12 +1384,19 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, return NGX_ERROR; } - if (ngx_quic_parse_transport_param(p, p + len, id, tp) != NGX_OK) { + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, log, 0, "failed to parse transport param id 0x%xi data", id); return NGX_ERROR; } + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "unknown transport param id 0x%xi,skipped", id); + } + p += len; } -- cgit v1.2.3 From 5018d9eecc4e7e896a2b186db10b8419b7c6d96c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 23 Mar 2020 14:53:04 +0300 Subject: Connection states code cleanup. + ngx_quic_init_ssl_methods() is no longer there, we setup methods on SSL connection directly. + the handshake_handler is actually a generic quic input handler + updated c->log->action and debug to reflect changes and be more informative + c->quic is always set in ngx_quic_input() + the quic connection state is set by the results of SSL_do_handshake(); --- src/event/ngx_event_quic.c | 51 +++++++++++++++++++++++++++++++++------------- src/event/ngx_event_quic.h | 2 -- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5a87092cc..0012f092c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,6 +9,13 @@ #include +typedef enum { + NGX_QUIC_ST_INITIAL, /* connection just created */ + NGX_QUIC_ST_HANDSHAKE, /* handshake started */ + NGX_QUIC_ST_APPLICATION /* handshake complete */ +} ngx_quic_state_t; + + typedef struct { ngx_rbtree_node_t node; ngx_buf_t *b; @@ -35,6 +42,8 @@ struct ngx_quic_connection_s { ngx_uint_t client_tp_done; ngx_quic_tp_t tp; + ngx_quic_state_t state; + /* current packet numbers for each namespace */ ngx_uint_t initial_pn; ngx_uint_t handshake_pn; @@ -75,7 +84,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); -static void ngx_quic_handshake_handler(ngx_event_t *rev); +static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_close_connection(ngx_connection_t *c); static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); @@ -328,9 +337,9 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_buf_t *b; ngx_quic_header_t pkt; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); - c->log->action = "QUIC handshaking"; + c->log->action = "QUIC initialization"; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -352,7 +361,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_add_timer(c->read, timeout); - c->read->handler = ngx_quic_handshake_handler; + c->read->handler = ngx_quic_input_handler; return; } @@ -388,6 +397,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + qc->state = NGX_QUIC_ST_INITIAL; + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -480,6 +491,8 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } + qc->state = NGX_QUIC_ST_HANDSHAKE; + n = SSL_do_handshake(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); @@ -501,7 +514,7 @@ ngx_quic_init_connection(ngx_connection_t *c) static void -ngx_quic_handshake_handler(ngx_event_t *rev) +ngx_quic_input_handler(ngx_event_t *rev) { ssize_t n; ngx_buf_t b; @@ -515,7 +528,7 @@ ngx_quic_handshake_handler(ngx_event_t *rev) c = rev->data; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic handshake handler"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); @@ -586,15 +599,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) ngx_int_t rc; ngx_quic_header_t pkt; - if (c->quic == NULL) { - // XXX: possible? - ngx_log_error(NGX_LOG_INFO, c->log, 0, "BUG: no QUIC in connection"); - return NGX_ERROR; - } - p = b->start; do { + c->log->action = "processing quic packet"; + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); pkt.raw = b; pkt.data = p; @@ -647,6 +656,8 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; + c->log->action = "processing initial quic packet"; + qc = c->quic; ssl_conn = c->ssl->connection; @@ -674,6 +685,8 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; + c->log->action = "processing handshake quic packet"; + qc = c->quic; /* extract cleartext data into pkt */ @@ -727,6 +740,8 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; + c->log->action = "processing application data quic packet"; + qc = c->quic; if (qc->secrets.client.ad.key.len == 0) { @@ -759,6 +774,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; + c->log->action = "processing quic payload"; + qc = c->quic; p = pkt->payload.data; @@ -957,11 +974,17 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (sslerr == SSL_ERROR_SSL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); } - } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + } else if (n == 1) { + c->quic->state = NGX_QUIC_ST_APPLICATION; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "handshake completed successfully"); + } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_quic_read_level: %d, SSL_quic_write_level: %d", (int) SSL_quic_read_level(ssl_conn), diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index e30186d1b..71e7b00d5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -46,8 +46,6 @@ struct ngx_quic_stream_s { }; -void ngx_quic_init_ssl_methods(SSL_CTX* ctx); - void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_msec_t timeout, ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); -- cgit v1.2.3 From 280c18bdceaa2f3d3abac9bf2cd9489a6c23176f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 23 Mar 2020 18:20:42 +0300 Subject: Fixed received ACK fields order in debug logging. --- src/event/ngx_event_quic_transport.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 459d61c2d..de49d2734 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -603,11 +603,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ACK: { largest=%ui delay=%ui first=%ui count=%ui}", + "ACK: { largest=%ui delay=%ui count=%ui first=%ui}", f->u.ack.largest, f->u.ack.delay, - f->u.ack.first_range, - f->u.ack.range_count); + f->u.ack.range_count, + f->u.ack.first_range); break; -- cgit v1.2.3 From 72b0a1b32a8a9527fccf1d71ee1fc54859cffd4c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 18:47:17 +0300 Subject: Limit output QUIC packets with client max_packet_size. Additionally, receive larger packets than 512 bytes. --- src/event/ngx_event_quic.c | 32 ++++++++++++++++++++++---------- src/event/ngx_event_quic.h | 7 +++++++ src/event/ngx_event_quic_protection.c | 4 ++-- src/http/v3/ngx_http_v3_module.c | 9 ++++++--- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0012f092c..51668dd69 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -41,6 +41,7 @@ struct ngx_quic_connection_s { ngx_uint_t client_tp_done; ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; ngx_quic_state_t state; @@ -219,7 +220,6 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, u_char *p, *end; size_t client_params_len; const uint8_t *client_params; - ngx_quic_tp_t ctp; ngx_quic_frame_t *frame; ngx_connection_t *c; ngx_quic_connection_t *qc; @@ -244,15 +244,12 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, p = (u_char *) client_params; end = p + client_params_len; - ngx_memzero(&ctp, sizeof(ngx_quic_tp_t)); - - if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) != NGX_OK) + if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) + != NGX_OK) { return NGX_ERROR; } - /* TODO: save/use obtained client parameters: merge with ours? */ - qc->client_tp_done = 1; } } @@ -371,6 +368,7 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt) { + ngx_quic_tp_t *ctp; ngx_quic_connection_t *qc; if (ngx_buf_size(pkt->raw) < 1200) { @@ -406,6 +404,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->ssl = ssl; qc->tp = *tp; + ctp = &qc->ctp; + ctp->max_packet_size = NGX_QUIC_DEFAULT_MAX_PACKET_SIZE; + ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + qc->dcid.len = pkt->dcid.len; qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); if (qc->dcid.data == NULL) { @@ -520,10 +523,10 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_buf_t b; ngx_connection_t *c; - u_char buf[512]; + static u_char buf[65535]; b.start = buf; - b.end = buf + 512; + b.end = buf + sizeof(buf); b.pos = b.last = b.start; c = rev->data; @@ -1092,7 +1095,7 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) static ngx_int_t ngx_quic_output(ngx_connection_t *c) { - size_t len; + size_t len, hlen, n; ngx_uint_t lvl; ngx_quic_frame_t *f, *start; ngx_quic_connection_t *qc; @@ -1110,10 +1113,19 @@ ngx_quic_output(ngx_connection_t *c) do { len = 0; + hlen = (lvl == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; + do { /* process same-level group of frames */ - len += ngx_quic_create_frame(NULL, NULL, f);// TODO: handle overflow, max size + n = ngx_quic_create_frame(NULL, NULL, f); + + if (len && hlen + len + n > qc->ctp.max_packet_size) { + break; + } + + len += n; f = f->next; } while (f && f->level == lvl); diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 71e7b00d5..5079e0491 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -14,6 +14,13 @@ #define quic_version 0xff000018 /* draft-24 (ngtcp2) */ //#define quic_version 0xff00001b /* draft-27 (FFN 76) */ +#define NGX_QUIC_MAX_SHORT_HEADER 25 +#define NGX_QUIC_MAX_LONG_HEADER 346 + +#define NGX_QUIC_DEFAULT_MAX_PACKET_SIZE 65527 +#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 +#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 + typedef struct { /* configurable */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index f648bbee8..ee6f011eb 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -697,7 +697,7 @@ ngx_quic_create_long_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, out.len = payload->len + EVP_GCM_TLS_TAG_LEN; - ad.data = ngx_alloc(346 /*max header*/, log); + ad.data = ngx_alloc(NGX_QUIC_MAX_LONG_HEADER, log); if (ad.data == 0) { return NGX_ERROR; } @@ -766,7 +766,7 @@ ngx_quic_create_short_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, out.len = payload->len + EVP_GCM_TLS_TAG_LEN; - ad.data = ngx_alloc(25 /*max header*/, log); + ad.data = ngx_alloc(NGX_QUIC_MAX_SHORT_HEADER, log); if (ad.data == 0) { return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 20137b377..385838df4 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -233,11 +233,13 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) // > 2 ^ 14 is invalid ngx_conf_merge_msec_value(conf->quic.max_ack_delay, - prev->quic.max_ack_delay, 25); + prev->quic.max_ack_delay, + NGX_QUIC_DEFAULT_MAX_ACK_DELAY); // < 1200 is invalid ngx_conf_merge_uint_value(conf->quic.max_packet_size, - prev->quic.max_packet_size, 65527); + prev->quic.max_packet_size, + NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_data, prev->quic.initial_max_data, 10000000); @@ -261,7 +263,8 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) // > 20 is invalid ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent, - prev->quic.ack_delay_exponent, 3); + prev->quic.ack_delay_exponent, + NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); ngx_conf_merge_uint_value(conf->quic.disable_active_migration, prev->quic.disable_active_migration, 1); -- cgit v1.2.3 From 3fa1dec9c71617d78322226b5c7e19faa6d35c1f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 15:49:31 +0300 Subject: Better flow control and buffering for QUIC streams. --- src/event/ngx_event_quic.c | 95 ++++++++++++++++++++++++++++++++---- src/event/ngx_event_quic_transport.c | 28 +++++++++++ 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 51668dd69..4afa86bb0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -16,6 +16,9 @@ typedef enum { } ngx_quic_state_t; +#define NGX_QUIC_STREAM_BUFSIZE 16384 + + typedef struct { ngx_rbtree_node_t node; ngx_buf_t *b; @@ -106,6 +109,8 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -885,6 +890,18 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, + &frame.u.stream_data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + ack_this = 1; + break; + default: return NGX_ERROR; } @@ -1002,6 +1019,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) { ngx_buf_t *b; + ngx_event_t *rev; ngx_quic_connection_t *qc; ngx_quic_stream_node_t *sn; @@ -1013,15 +1031,24 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); b = sn->b; - if ((size_t) (b->end - b->pos) < f->length) { + if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); return NGX_ERROR; } - ngx_memcpy(b->pos, f->data, f->length); - b->pos += f->length; + if ((size_t) (b->end - b->last) < f->length) { + b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; + } + + b->last = ngx_cpymem(b->last, f->data, f->length); - // TODO: notify + rev = sn->c->read; + rev->ready = 1; + + if (rev->active) { + rev->handler(rev); + } return NGX_OK; } @@ -1071,6 +1098,48 @@ ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + size_t n; + ngx_buf_t *b; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + ngx_quic_stream_node_t *sn; + + qc = c->quic; + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id); + return NGX_ERROR; + } + + b = sn->b; + n = (b->pos - b->start) + (b->end - b->last); + + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = f->id; + frame->u.max_stream_data.limit = n; + + ngx_sprintf(frame->info, "MAX_STREAM_DATA id:%d limit:%d level=%d", + (int) frame->u.max_stream_data.id, + (int) frame->u.max_stream_data.limit, + frame->level); + + ngx_quic_queue_frame(c->quic, frame); + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { @@ -1349,6 +1418,7 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) static ngx_quic_stream_node_t * ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) { + size_t n; ngx_log_t *log; ngx_pool_t *pool; ngx_event_t *rev, *wev; @@ -1402,8 +1472,11 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + n = ngx_max(NGX_QUIC_STREAM_BUFSIZE, + qc->tp.initial_max_stream_data_bidi_remote); + sn->node.key =id; - sn->b = ngx_create_temp_buf(pool, 16 * 1024); // XXX enough for everyone + sn->b = ngx_create_temp_buf(pool, n); if (sn->b == NULL) { return NULL; } @@ -1456,11 +1529,10 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) b = sn->b; - if (b->last - b->pos == 0) { + if (b->pos == b->last) { c->read->ready = 0; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv() not ready"); - return NGX_AGAIN; // ? + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv() not ready"); + return NGX_AGAIN; } len = ngx_min(b->last - b->pos, (ssize_t) size); @@ -1469,6 +1541,11 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) b->pos += len; + if (b->pos == b->last) { + b->pos = b->start; + b->last = b->start; + } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv: %z of %uz", len, size); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index de49d2734..c1f2fc992 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -69,6 +69,8 @@ static size_t ngx_quic_create_crypto(u_char *p, static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); +static size_t ngx_quic_create_max_stream_data(u_char *p, + ngx_quic_max_stream_data_frame_t *ms); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, @@ -1079,6 +1081,9 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) case NGX_QUIC_FT_MAX_STREAMS: return ngx_quic_create_max_streams(p, &f->u.max_streams); + case NGX_QUIC_FT_MAX_STREAM_DATA: + return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -1459,6 +1464,29 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, } +static size_t +ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); + len += ngx_quic_varint_len(ms->id); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); + ngx_quic_build_int(&p, ms->id); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) { -- cgit v1.2.3 From f4562d7ed904a0734cfe65d20921977aaf0ae088 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 19:19:44 +0300 Subject: Avoid using QUIC connection after CONNECTION_CLOSE. --- src/event/ngx_event_quic.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4afa86bb0..acc6e671a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -644,8 +644,8 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) rc = ngx_quic_app_input(c, &pkt); } - if (rc == NGX_ERROR) { - return NGX_ERROR; + if (rc != NGX_OK) { + return rc; } /* b->pos is at header end, adjust by actual packet length */ @@ -914,8 +914,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (do_close) { - ngx_quic_close_connection(c); - return NGX_OK; + return NGX_DONE; } if (ack_this == 0) { -- cgit v1.2.3 From 77a4c2d17221d355e1e520cdff8fd41aec0bf3ed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 15:32:24 +0300 Subject: Put zero in 'First ACK Range' when acknowledging one packet. This fixes Chrome CONNECTION_ID_LIMIT_ERROR with the reason: "Underflow with first ack block length 2 largest acked is 1". --- src/event/ngx_event_quic_transport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index c1f2fc992..e15a267b3 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1104,7 +1104,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) len += ngx_quic_varint_len(ack->pn); len += ngx_quic_varint_len(0); len += ngx_quic_varint_len(0); - len += ngx_quic_varint_len(ack->pn); + len += ngx_quic_varint_len(0); return len; } @@ -1115,7 +1115,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, ack->pn); ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, 0); - ngx_quic_build_int(&p, ack->pn); + ngx_quic_build_int(&p, 0); return p - start; } -- cgit v1.2.3 From ede2656c6016d240b804bbcb28b6ecd391cca5de Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 19:26:24 +0300 Subject: Support for HTTP/3 ALPN. This is required by Chrome. --- src/event/ngx_event_quic.h | 4 ++-- src/event/ngx_event_quic_transport.c | 14 +++++++------- src/http/modules/ngx_http_ssl_module.c | 12 ++++++++++-- src/http/v3/ngx_http_v3.h | 3 +++ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 5079e0491..c174bfe44 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,8 +11,8 @@ #include -#define quic_version 0xff000018 /* draft-24 (ngtcp2) */ -//#define quic_version 0xff00001b /* draft-27 (FFN 76) */ +#define NGX_QUIC_DRAFT_VERSION 24 +#define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) #define NGX_QUIC_MAX_SHORT_HEADER 25 #define NGX_QUIC_MAX_LONG_HEADER 346 diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index e15a267b3..90177963f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -56,7 +56,7 @@ static u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...); static void ngx_quic_build_int(u_char **pos, uint64_t value); static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); -/*static*/ u_char *ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value); // usage depends on quic_version +/*static*/ u_char *ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value); // usage depends on NGX_QUIC_VERSION static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out); @@ -295,7 +295,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic flags:%xi version:%xD", pkt->flags, pkt->version); - if (pkt->version != quic_version) { + if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unsupported quic version: 0x%xi", pkt->version); return NGX_ERROR; @@ -349,7 +349,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, *p++ = pkt->flags; - p = ngx_quic_write_uint32(p, quic_version); + p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION); *p++ = pkt->scid.len; p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); @@ -1327,7 +1327,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, { ngx_int_t rc; -#if (quic_version < 0xff00001b) +#if (NGX_QUIC_DRAFT_VERSION < 27) uint16_t id, len, tp_len; @@ -1493,7 +1493,7 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) u_char *p; size_t len; -#if (quic_version < 0xff00001b) +#if (NGX_QUIC_DRAFT_VERSION < 27) /* older drafts with static transport parameters encoding */ @@ -1548,13 +1548,13 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) tp->initial_max_stream_data_uni); if (pos == NULL) { -#if (quic_version < 0xff00001b) +#if (NGX_QUIC_DRAFT_VERSION < 27) len += 2; #endif return len; } -#if (quic_version < 0xff00001b) +#if (NGX_QUIC_DRAFT_VERSION < 27) /* TLS extension length */ p = ngx_quic_write_uint16(p, len); #endif diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 4b480a006..a48d3b924 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -371,7 +371,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2 || NGX_DEBUG) @@ -388,15 +388,23 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; +#endif +#if (NGX_HTTP_V2) if (hc->addr_conf->http2) { srv = (unsigned char *) NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_V2_ALPN_ADVERTISE NGX_HTTP_NPN_ADVERTISE) - 1; } else +#endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->http3) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1; + } else #endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 2c63f544e..81e57dc36 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -17,6 +17,9 @@ #define NGX_HTTP_V3_STREAM 0x48335354 /* "H3ST" */ +#define NGX_HTTP_V3_ALPN(s) NGX_HTTP_V3_ALPN_DRAFT(s) +#define NGX_HTTP_V3_ALPN_DRAFT(s) "\x05h3-" #s +#define NGX_HTTP_V3_ALPN_ADVERTISE NGX_HTTP_V3_ALPN(NGX_QUIC_DRAFT_VERSION) #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 -- cgit v1.2.3 From 9975b088bbd24af0f0543bb921a1313a285da319 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 19:42:09 +0300 Subject: Allow ngx_queue_frame() to insert frame in the front. Previously a frame could only be inserted after the first element of the list. --- src/event/ngx_event_quic.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index acc6e671a..582d3f8ce 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1142,21 +1142,16 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - ngx_quic_frame_t *f; + ngx_quic_frame_t **f; - if (qc->frames == NULL) { - qc->frames = frame; - return; - } - - for (f = qc->frames; f->next; f = f->next) { - if (f->next->level > frame->level) { + for (f = &qc->frames; *f; f = &(*f)->next) { + if ((*f)->level > frame->level) { break; } } - frame->next = f->next; - f->next = frame; + frame->next = *f; + *f = frame; } -- cgit v1.2.3 From 5ac5e51fdfe68e8b11f8c7abd2ce361062f68e54 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 23 Mar 2020 21:20:20 +0300 Subject: Respect QUIC max_idle_timeout. --- src/event/ngx_event_quic.c | 33 ++++++++++++++++++++++++--------- src/event/ngx_event_quic.h | 2 +- src/http/ngx_http_request.c | 4 +--- src/http/v3/ngx_http_v3_module.c | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 582d3f8ce..a4a293a14 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -30,7 +30,6 @@ typedef struct { typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; - ngx_msec_t timeout; ngx_connection_handler_pt handler; ngx_uint_t id_counter; @@ -59,6 +58,8 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_uint_t max_data; + ngx_uint_t send_timer_set; + /* unsigned send_timer_set:1 */ #define SSL_ECRYPTION_LAST ((ssl_encryption_application) + 1) uint64_t crypto_offset[SSL_ECRYPTION_LAST]; @@ -255,6 +256,12 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } + if (qc->ctp.max_idle_timeout > 0 + && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; + } + qc->client_tp_done = 1; } } @@ -334,7 +341,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, - ngx_msec_t timeout, ngx_connection_handler_pt handler) + ngx_connection_handler_pt handler) { ngx_buf_t *b; ngx_quic_header_t pkt; @@ -359,9 +366,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, // we don't need stream handler for initial packet processing c->quic->streams.handler = handler; - c->quic->streams.timeout = timeout; - ngx_add_timer(c->read, timeout); + ngx_add_timer(c->read, c->quic->tp.max_idle_timeout); c->read->handler = ngx_quic_input_handler; @@ -524,9 +530,10 @@ ngx_quic_init_connection(ngx_connection_t *c) static void ngx_quic_input_handler(ngx_event_t *rev) { - ssize_t n; - ngx_buf_t b; - ngx_connection_t *c; + ssize_t n; + ngx_buf_t b; + ngx_connection_t *c; + ngx_quic_connection_t *qc; static u_char buf[65535]; @@ -544,8 +551,6 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - ngx_add_timer(rev, c->quic->streams.timeout); - if (c->close) { ngx_quic_close_connection(c); return; @@ -569,6 +574,11 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_quic_close_connection(c); return; } + + qc = c->quic; + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); } @@ -1209,6 +1219,11 @@ ngx_quic_output(ngx_connection_t *c) qc->frames = NULL; + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + return NGX_OK; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index c174bfe44..1f022c678 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,7 +54,7 @@ struct ngx_quic_stream_s { void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, - ngx_msec_t timeout, ngx_connection_handler_pt handler); + ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 6c9fdc543..acd708cf6 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -347,9 +347,7 @@ ngx_http_init_connection(ngx_connection_t *c) v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - ngx_quic_run(c, &sscf->ssl, &v3cf->quic, - c->listening->post_accept_timeout, - ngx_http_quic_stream_handler); + ngx_quic_run(c, &sscf->ssl, &v3cf->quic, ngx_http_quic_stream_handler); return; } #endif diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 385838df4..45bfc5b1c 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -229,7 +229,7 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_v3_srv_conf_t *conf = child; ngx_conf_merge_msec_value(conf->quic.max_idle_timeout, - prev->quic.max_idle_timeout, 10000); + prev->quic.max_idle_timeout, 60000); // > 2 ^ 14 is invalid ngx_conf_merge_msec_value(conf->quic.max_ack_delay, -- cgit v1.2.3 From f20af3dabca75b1f57f5c72a0e45e7251762b43c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 23 Mar 2020 20:48:34 +0300 Subject: Fixed client certificate verification. For ngx_http_process_request() part to work, this required to set both r->http_connection->ssl and c->ssl on a QUIC stream. To avoid damaging global SSL object, ngx_ssl_shutdown() is managed to ignore QUIC streams. --- src/event/ngx_event_openssl.c | 5 +++++ src/event/ngx_event_quic.c | 1 + src/http/ngx_http_request.c | 1 + 3 files changed, 7 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 91b415caa..2fd254446 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2735,6 +2735,11 @@ ngx_ssl_shutdown(ngx_connection_t *c) int n, sslerr, mode; ngx_err_t err; + if (c->qs) { + /* QUIC streams inherit SSL object */ + return NGX_OK; + } + if (SSL_in_init(c->ssl->connection)) { /* * OpenSSL 1.0.2f complains if SSL_shutdown() is called during diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a4a293a14..a466ac184 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1470,6 +1470,7 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) sn->c->sockaddr = c->sockaddr; sn->c->local_sockaddr = c->local_sockaddr; sn->c->addr_text = c->addr_text; + sn->c->ssl = c->ssl; rev = sn->c->read; wev = sn->c->write; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index acd708cf6..890e5374b 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -225,6 +225,7 @@ ngx_http_init_connection(ngx_connection_t *c) if (c->type == SOCK_DGRAM) { hc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); hc->quic = 1; + hc->ssl = 1; } else #endif -- cgit v1.2.3 From 57544f758953a968d5c02c84d59fea33a7d48ea5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 24 Mar 2020 11:59:14 +0300 Subject: Implemented sending HANDSHAKE_DONE frame after handshake. This makes it possible to switch to draft 27 by default. --- src/event/ngx_event_quic.c | 18 +++++++++++++++--- src/event/ngx_event_quic.h | 2 +- src/event/ngx_event_quic_transport.c | 29 +++++++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a466ac184..d913f4f43 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -964,9 +964,10 @@ static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *f) { - int sslerr; - ssize_t n; - ngx_ssl_conn_t *ssl_conn; + int sslerr; + ssize_t n; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_frame_t *frame; if (f->offset != 0x0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1012,6 +1013,17 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "handshake completed successfully"); + + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + /* 12.4 Frames and frame types, figure 8 */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed"); + ngx_quic_queue_frame(c->quic, frame); } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 1f022c678..469c6a10d 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -11,7 +11,7 @@ #include -#define NGX_QUIC_DRAFT_VERSION 24 +#define NGX_QUIC_DRAFT_VERSION 27 #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) #define NGX_QUIC_MAX_SHORT_HEADER 25 diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 90177963f..cc1a0d1a7 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -66,6 +66,7 @@ static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); +static size_t ngx_quic_create_hs_done(u_char *p); static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); @@ -882,14 +883,18 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; - /* TODO: implement parsing for all frames below */ - case NGX_QUIC_FT_NEW_TOKEN: case NGX_QUIC_FT_HANDSHAKE_DONE: + /* only sent by server, not by client */ + goto not_allowed; + + case NGX_QUIC_FT_NEW_TOKEN: if (!ngx_quic_short_pkt(flags)) { goto not_allowed; } + /* TODO: implement */ + ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unimplemented frame type 0x%xi in packet", f->type); @@ -1065,6 +1070,9 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) case NGX_QUIC_FT_CRYPTO: return ngx_quic_create_crypto(p, &f->u.crypto); + case NGX_QUIC_FT_HANDSHAKE_DONE: + return ngx_quic_create_hs_done(p); + case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: @@ -1147,6 +1155,23 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) } +static size_t +ngx_quic_create_hs_done(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); + + return p - start; +} + + static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) { -- cgit v1.2.3 From 780f4f660c17ac2695521b00d495a6c0130ef8c3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 24 Mar 2020 12:15:39 +0300 Subject: Added QUIC version check for sending HANDSHAKE_DONE frame. --- src/event/ngx_event_quic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d913f4f43..726775122 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -967,7 +967,6 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, int sslerr; ssize_t n; ngx_ssl_conn_t *ssl_conn; - ngx_quic_frame_t *frame; if (f->offset != 0x0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1014,6 +1013,10 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "handshake completed successfully"); +#if (NGX_QUIC_DRAFT_VERSION >= 27) + { + ngx_quic_frame_t *frame; + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return NGX_ERROR; @@ -1024,6 +1027,8 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed"); ngx_quic_queue_frame(c->quic, frame); + } +#endif } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From 8c26e1d1483a242aef549d8003f2957505ccf08b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 24 Mar 2020 17:03:39 +0300 Subject: Logging cleanup. + Client-related errors (i.e. parsing) are done at INFO level + c->log->action is updated through the process of receiving, parsing. handling packet/payload and generating frames/output. --- src/event/ngx_event_quic.c | 11 +++- src/event/ngx_event_quic_transport.c | 121 ++++++++++++++++++----------------- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 726775122..ee6e02684 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -401,6 +401,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + c->log->action = "creating new quic connection"; + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); if (qc == NULL) { return NGX_ERROR; @@ -792,7 +794,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; - c->log->action = "processing quic payload"; qc = c->quic; @@ -804,6 +805,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) while (p < end) { + c->log->action = "parsing frames"; + len = ngx_quic_parse_frame(pkt, p, end, &frame); if (len == NGX_DECLINED) { @@ -817,6 +820,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + c->log->action = "handling frames"; + p += len; switch (frame.type) { @@ -932,6 +937,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_OK; } + c->log->action = "generating acknowledgment"; + // packet processed, ACK it now if required // TODO: if (ack_required) ... - currently just ack each packet @@ -1196,6 +1203,8 @@ ngx_quic_output(ngx_connection_t *c) return NGX_OK; } + c->log->action = "sending frames"; + lvl = qc->frames->level; start = qc->frames; f = start; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index cc1a0d1a7..e5d8f918c 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -276,20 +276,20 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read flags"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read flags"); return NGX_ERROR; } if (!ngx_quic_long_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a long packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet"); return NGX_ERROR; } p = ngx_quic_read_uint32(p, end, &pkt->version); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read version"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read version"); return NGX_ERROR; } @@ -297,15 +297,15 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) "quic flags:%xi version:%xD", pkt->flags, pkt->version); if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version: 0x%xi", pkt->version); return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &idlen); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read dcid len"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read dcid len"); return NGX_ERROR; } @@ -313,15 +313,15 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read dcid"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read dcid"); return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &idlen); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read scid len"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read scid len"); return NGX_ERROR; } @@ -329,8 +329,8 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read scid"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read scid"); return NGX_ERROR; } @@ -384,13 +384,13 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read flags"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read flags"); return NGX_ERROR; } if (!ngx_quic_short_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "not a short packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet"); return NGX_ERROR; } @@ -398,7 +398,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) "quic flags:%xi", pkt->flags); if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "unexpected quic dcid"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); return NGX_ERROR; } @@ -406,8 +406,8 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = ngx_quic_read_bytes(p, end, dcid->len, &pkt->dcid.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet is too short to read dcid"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet is too small to read dcid"); return NGX_ERROR; } @@ -431,7 +431,8 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "failed to parse token length"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse token length"); return NGX_ERROR; } @@ -439,14 +440,14 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, - "packet too short to read token data"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "packet too small to read token data"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "bad packet length"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "bad packet length"); return NGX_ERROR; } @@ -454,7 +455,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) "quic packet length: %uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated initial packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); return NGX_ERROR; } @@ -482,7 +483,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) p = ngx_quic_parse_int(p, end, &plen); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "bad packet length"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "bad packet length"); return NGX_ERROR; } @@ -490,7 +491,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) "quic packet length: %uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, "truncated handshake packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); return NGX_ERROR; } @@ -518,7 +519,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to obtain quic frame type"); return NGX_ERROR; } @@ -535,21 +536,21 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse crypto frame offset"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, &f->u.crypto.len); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse crypto frame len"); return NGX_ERROR; } p = ngx_quic_read_bytes(p, end, f->u.crypto.len, &f->u.crypto.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse crypto frame data"); return NGX_ERROR; } @@ -584,7 +585,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, &f->u.ack.delay, &f->u.ack.range_count, &f->u.ack.first_range, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse ack frame"); return NGX_ERROR; } @@ -592,14 +593,14 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->u.ack.range_count) { p = ngx_quic_parse_int(p, end, &f->u.ack.ranges[0]); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse ack frame first range"); return NGX_ERROR; } } if (f->type == NGX_QUIC_FT_ACK_ECN) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "TODO: parse ECN ack frames"); /* TODO: add parsing of such frames */ return NGX_ERROR; @@ -629,28 +630,28 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int_multi(p, end, &f->u.ncid.seqnum, &f->u.ncid.retire, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse new connection id frame"); return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse new connection id length"); return NGX_ERROR; } p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse new connection id cid"); return NGX_ERROR; } p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse new connection id srt"); return NGX_ERROR; } @@ -676,7 +677,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse close connection frame error code"); return NGX_ERROR; } @@ -684,7 +685,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse close connection frame type"); return NGX_ERROR; } @@ -692,7 +693,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse close reason length"); return NGX_ERROR; } @@ -702,7 +703,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, &f->u.close.reason.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse close reason"); return NGX_ERROR; } @@ -710,7 +711,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unkown error code: %ui, truncated", f->u.close.error_code); f->u.close.error_code = NGX_QUIC_ERR_LAST - 1; @@ -751,7 +752,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse stream frame id"); return NGX_ERROR; } @@ -759,7 +760,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type & 0x04) { p = ngx_quic_parse_int(p, end, &f->u.stream.offset); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse stream frame offset"); return NGX_ERROR; } @@ -771,7 +772,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type & 0x02) { p = ngx_quic_parse_int(p, end, &f->u.stream.length); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse stream frame length"); return NGX_ERROR; } @@ -783,7 +784,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_read_bytes(p, end, f->u.stream.length, &f->u.stream.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse stream frame data"); return NGX_ERROR; } @@ -807,7 +808,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse max data frame"); return NGX_ERROR; } @@ -827,7 +828,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, &f->u.reset_stream.error_code, &f->u.reset_stream.final_size, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse reset stream frame"); return NGX_ERROR; } @@ -848,7 +849,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int_multi(p, end, &f->u.stop_sending.id, &f->u.stop_sending.error_code, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse stop sending frame"); return NGX_ERROR; } @@ -868,7 +869,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse streams blocked frame limit"); return NGX_ERROR; } @@ -895,7 +896,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, /* TODO: implement */ - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_ALERT, pkt->log, 0, "unimplemented frame type 0x%xi in packet", f->type); break; @@ -909,7 +910,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse max streams frame limit"); return NGX_ERROR; } @@ -931,7 +932,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int_multi(p, end, &f->u.max_stream_data.id, &f->u.max_stream_data.limit, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse max stream data frame"); return NGX_ERROR; } @@ -950,7 +951,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse data blocked frame limit"); return NGX_ERROR; } @@ -969,7 +970,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int_multi(p, end, &f->u.stream_data_blocked.id, &f->u.stream_data_blocked.limit, NULL); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse tream data blocked frame"); return NGX_ERROR; } @@ -988,7 +989,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse retire connection id" " frame sequence number"); return NGX_ERROR; @@ -1007,7 +1008,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to get path challenge frame data"); return NGX_ERROR; } @@ -1027,7 +1028,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); if (p == NULL) { - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to get path response frame data"); return NGX_ERROR; } @@ -1040,7 +1041,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; default: - ngx_log_error(NGX_LOG_ERR, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unknown frame type 0x%xi in packet", f->type); return NGX_ERROR; -- cgit v1.2.3 From d8d42e29e7ff43025ba0f56262993cd37c4a5b10 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Mar 2020 19:17:57 +0300 Subject: QUIC streams don't need filter_need_in_memory after 7f0981be07c4. Now they inherit c->ssl always enabled from the main connection, which makes r->main_filter_need_in_memory set for them. --- src/http/ngx_http_request.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 890e5374b..c5165f2dc 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -709,7 +709,6 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_V3) if (hc->quic) { r->http_version = NGX_HTTP_VERSION_30; - r->filter_need_in_memory = 1; } #endif -- cgit v1.2.3 From 685e7d1451b7a517b11c6b04e0564ff953b328b7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Mar 2020 22:12:52 +0300 Subject: Advertise our max_idle_timeout in transport parameters. So we can easily tune how soon client would decide to close a connection. --- src/event/ngx_event_quic_transport.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index e5d8f918c..b391f6b79 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1573,6 +1573,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, tp->initial_max_stream_data_uni); + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + if (pos == NULL) { #if (NGX_QUIC_DRAFT_VERSION < 27) len += 2; @@ -1606,6 +1609,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, tp->initial_max_stream_data_uni); + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + ngx_quic_hexdump0(ngx_cycle->log, "transport parameters", pos, p - pos); return p - pos; -- cgit v1.2.3 From c5505648d7ed963e30723698bfb50026cbca8a73 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 25 Mar 2020 19:42:00 +0300 Subject: Fixed log initialization. Should be done after memzero. --- src/event/ngx_event_quic.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ee6e02684..6cba52766 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1308,13 +1308,12 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_str_t res; ngx_quic_header_t pkt; - pkt.log = c->log; - static ngx_str_t initial_token = ngx_null_string; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); + pkt.log = c->log; pkt.level = level; pkt.dcid = qc->dcid; pkt.scid = qc->scid; -- cgit v1.2.3 From 061a42d9664aa7bdcc6d3af1b79486a9d7a82c89 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 24 Mar 2020 13:49:42 +0300 Subject: Implemented eof in QUIC streams. --- src/event/ngx_event_quic.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6cba52766..c5eb11c5c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1079,6 +1079,10 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, rev = sn->c->read; rev->ready = 1; + if (f->fin) { + rev->pending_eof = 1; + } + if (rev->active) { rev->handler(rev); } @@ -1546,6 +1550,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { ssize_t len; ngx_buf_t *b; + ngx_event_t *rev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_quic_stream_node_t *sn; @@ -1560,12 +1565,22 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - // XXX: how to return EOF? + rev = c->read; b = sn->b; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv: eof:%d, avail:%z", + rev->pending_eof, b->last - b->pos); + if (b->pos == b->last) { - c->read->ready = 0; + rev->ready = 0; + + if (rev->pending_eof) { + rev->eof = 1; + return 0; + } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv() not ready"); return NGX_AGAIN; } @@ -1579,10 +1594,11 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) if (b->pos == b->last) { b->pos = b->start; b->last = b->start; + rev->ready = 0; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: %z of %uz", len, size); + "quic recv: %z of %uz", len, size); return len; } -- cgit v1.2.3 From f75e4e3fef6e270555afefbb15b4741c694596f3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 24 Mar 2020 16:38:03 +0300 Subject: Removed ngx_quic_stream_node_t. Now ngx_quic_stream_t is directly inserted into the tree. --- src/event/ngx_event_quic.c | 144 ++++++++++++++++---------------------------- src/event/ngx_event_quic.h | 10 ++- src/http/ngx_http_request.c | 2 +- 3 files changed, 59 insertions(+), 97 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c5eb11c5c..f8fe74c19 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -19,14 +19,6 @@ typedef enum { #define NGX_QUIC_STREAM_BUFSIZE 16384 -typedef struct { - ngx_rbtree_node_t node; - ngx_buf_t *b; - ngx_connection_t *c; - ngx_quic_stream_t s; -} ngx_quic_stream_node_t; - - typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; @@ -126,9 +118,9 @@ static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -static ngx_quic_stream_node_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, +static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key); -static ngx_quic_stream_node_t *ngx_quic_create_stream(ngx_connection_t *c, +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -1051,10 +1043,10 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) { - ngx_buf_t *b; - ngx_event_t *rev; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; qc = c->quic; @@ -1139,11 +1131,11 @@ static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) { - size_t n; - ngx_buf_t *b; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + size_t n; + ngx_buf_t *b; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; qc = c->quic; sn = ngx_quic_find_stream(&qc->streams.tree, f->id); @@ -1357,21 +1349,16 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_connection_t * ngx_quic_create_uni_stream(ngx_connection_t *c) { - ngx_uint_t id; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + ngx_uint_t id; + ngx_quic_stream_t *qs, *sn; + ngx_quic_connection_t *qc; qs = c->qs; qc = qs->parent->quic; - /* - * A stream ID is a 62-bit integer that is unique for all streams - * on a connection. - * - * 0x3 | Server-Initiated, Unidirectional - */ - id = (qc->streams.id_counter << 2) | 0x3; + id = (qc->streams.id_counter << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED + | NGX_QUIC_STREAM_UNIDIRECTIONAL; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "creating server uni stream #%ui id %ui", @@ -1392,8 +1379,8 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) { - ngx_rbtree_node_t **p; - ngx_quic_stream_node_t *qn, *qnt; + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; for ( ;; ) { @@ -1407,8 +1394,8 @@ ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, } else { /* node->key == temp->key */ - qn = (ngx_quic_stream_node_t *) &node->color; - qnt = (ngx_quic_stream_node_t *) &temp->color; + qn = (ngx_quic_stream_t *) &node->color; + qnt = (ngx_quic_stream_t *) &temp->color; if (qn->c < qnt->c) { p = &temp->left; @@ -1432,7 +1419,7 @@ ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, } -static ngx_quic_stream_node_t * +static ngx_quic_stream_t * ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) { ngx_rbtree_node_t *node, *sentinel; @@ -1443,7 +1430,7 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) while (node != sentinel) { if (key == node->key) { - return (ngx_quic_stream_node_t *) node; + return (ngx_quic_stream_t *) node; } node = (key < node->key) ? node->left : node->right; @@ -1453,20 +1440,20 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) } -static ngx_quic_stream_node_t * +static ngx_quic_stream_t * ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) { - size_t n; - ngx_log_t *log; - ngx_pool_t *pool; - ngx_event_t *rev, *wev; - ngx_pool_cleanup_t *cln; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + size_t n; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_event_t *rev, *wev; + ngx_quic_stream_t *sn; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; qc = c->quic; - sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_node_t)); + sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_t)); if (sn == NULL) { return NULL; } @@ -1522,10 +1509,9 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) ngx_rbtree_insert(&qc->streams.tree, &sn->node); - sn->s.id = id; - sn->s.unidirectional = (sn->s.id & 0x02) ? 1 : 0; - sn->s.parent = c; - sn->c->qs = &sn->s; + sn->id = id; + sn->parent = c; + sn->c->qs = sn; sn->c->recv = ngx_quic_stream_recv; sn->c->send = ngx_quic_stream_send; @@ -1548,27 +1534,15 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ssize_t len; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + ssize_t len; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_quic_stream_t *qs; qs = c->qs; - qc = qs->parent->quic; - - // XXX: get direct pointer from stream structure? - sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); - - if (sn == NULL) { - return NGX_ERROR; - } - + b = qs->b; rev = c->read; - b = sn->b; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv: eof:%d, avail:%z", rev->pending_eof, b->last - b->pos); @@ -1607,12 +1581,11 @@ 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) { - u_char *p; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; + u_char *p; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); @@ -1620,13 +1593,6 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) pc = qs->parent; qc = pc->quic; - // XXX: get direct pointer from stream structure? - sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); - - if (sn == NULL) { - return NGX_ERROR; - } - frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return 0; @@ -1667,29 +1633,21 @@ ngx_quic_stream_cleanup_handler(void *data) { ngx_connection_t *c = data; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_node_t *sn; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin"); + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; qs = c->qs; pc = qs->parent; qc = pc->quic; - if ((qs->id & 0x03) == 0x02) { + if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) { /* do not send fin for client unidirectional streams */ return; } - // XXX: get direct pointer from stream structure? - sn = ngx_quic_find_stream(&qc->streams.tree, qs->id); - - if (sn == NULL) { - return; - } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin"); frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 469c6a10d..796afffb5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -21,6 +21,9 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 +#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 + typedef struct { /* configurable */ @@ -46,10 +49,11 @@ typedef struct { struct ngx_quic_stream_s { - uint64_t id; - ngx_uint_t unidirectional:1; + ngx_rbtree_node_t node; ngx_connection_t *parent; - void *data; + ngx_connection_t *c; + uint64_t id; + ngx_buf_t *b; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index c5165f2dc..6f168c8bd 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -414,7 +414,7 @@ ngx_http_quic_stream_handler(ngx_connection_t *c) pc = c->qs->parent; h3c = pc->data; - if (c->qs->unidirectional) { + if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ngx_http_v3_handle_client_uni_stream(c); return; } -- cgit v1.2.3 From a0a2e0de1d256dc6ebcaf9102682132c01510601 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 24 Mar 2020 18:05:45 +0300 Subject: When closing a QUIC connection, wait for all streams to finish. Additionally, streams are now removed from the tree in cleanup handler. --- src/event/ngx_event_quic.c | 86 ++++++++++++++++++++++++++++++++++----- src/http/v3/ngx_http_v3_streams.c | 21 ++++++++++ 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f8fe74c19..1c70640bd 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -50,8 +50,9 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_uint_t max_data; - ngx_uint_t send_timer_set; - /* unsigned send_timer_set:1 */ + + unsigned send_timer_set:1; + unsigned closing:1; #define SSL_ECRYPTION_LAST ((ssl_encryption_application) + 1) uint64_t crypto_offset[SSL_ECRYPTION_LAST]; @@ -308,6 +309,10 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + if (c->quic->closing) { + return 1; + } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); @@ -536,9 +541,15 @@ ngx_quic_input_handler(ngx_event_t *rev) b.pos = b.last = b.start; c = rev->data; + qc = c->quic; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + if (qc->closing) { + ngx_quic_close_connection(c); + return; + } + if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); ngx_quic_close_connection(c); @@ -569,8 +580,6 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - qc = c->quic; - qc->send_timer_set = 0; ngx_add_timer(rev, qc->tp.max_idle_timeout); } @@ -579,12 +588,56 @@ ngx_quic_input_handler(ngx_event_t *rev) static void ngx_quic_close_connection(ngx_connection_t *c) { - ngx_pool_t *pool; +#if (NGX_DEBUG) + ngx_uint_t ns; +#endif + ngx_pool_t *pool; + ngx_event_t *rev; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection"); + + qc = c->quic; + + if (qc) { + tree = &qc->streams.tree; + + if (tree->root != tree->sentinel) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#if (NGX_DEBUG) + ns = 0; +#endif + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; - /* XXX wait for all streams to close */ + rev = qs->c->read; + rev->ready = 1; + rev->pending_eof = 1; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "close quic connection: %d", c->fd); + ngx_post_event(rev, &ngx_posted_events); + +#if (NGX_DEBUG) + ns++; +#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has %ui active streams", ns); + + qc->closing = 1; + return; + } + } if (c->ssl) { (void) ngx_ssl_shutdown(c); @@ -1587,12 +1640,16 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); - qs = c->qs; pc = qs->parent; qc = pc->quic; + if (qc->closing) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); + frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return 0; @@ -1642,6 +1699,15 @@ ngx_quic_stream_cleanup_handler(void *data) pc = qs->parent; qc = pc->quic; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream cleanup"); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + + if (qc->closing) { + ngx_post_event(pc->read, &ngx_posted_events); + return; + } + if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) { /* do not send fin for client unidirectional streams */ return; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 6a5610b9a..fa1c48a8b 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -29,6 +29,7 @@ static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_uni_stream_cleanup(void *data); static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); +static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); static ngx_connection_t *ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type); static ngx_connection_t *ngx_http_v3_get_control(ngx_connection_t *c); @@ -74,6 +75,8 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) cln->data = c; c->read->handler = ngx_http_v3_read_uni_stream_type; + c->write->handler = ngx_http_v3_dummy_write_handler; + ngx_http_v3_read_uni_stream_type(c->read); } @@ -310,6 +313,21 @@ failed: } +static void +ngx_http_v3_dummy_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + } +} + + /* XXX async & buffered stream writes */ static ngx_connection_t * @@ -338,6 +356,9 @@ ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type) us->type = type; sc->data = us; + sc->read->handler = ngx_http_v3_uni_read_handler; + sc->write->handler = ngx_http_v3_dummy_write_handler; + cln = ngx_pool_cleanup_add(sc->pool, 0); if (cln == NULL) { goto failed; -- cgit v1.2.3 From 95f439630b9c1e4ec11e8dc40cbb602d5b923cc0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Mar 2020 12:56:21 +0300 Subject: Safe QUIC stream creation. --- src/event/ngx_event_quic.c | 108 ++++++++++++++++++++++----------------------- src/event/ngx_event_quic.h | 2 + 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1c70640bd..bf2191d31 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -16,9 +16,6 @@ typedef enum { } ngx_quic_state_t; -#define NGX_QUIC_STREAM_BUFSIZE 16384 - - typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; @@ -122,7 +119,7 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, - ngx_uint_t id); + uint64_t id, size_t rcvbuf_size); 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, @@ -1096,6 +1093,7 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) { + size_t n; ngx_buf_t *b; ngx_event_t *rev; ngx_quic_stream_t *sn; @@ -1137,15 +1135,28 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); - sn = ngx_quic_create_stream(c, f->stream_id); + n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->tp.initial_max_stream_data_uni + : qc->tp.initial_max_stream_data_bidi_remote; + + if (n < NGX_QUIC_STREAM_BUFSIZE) { + n = NGX_QUIC_STREAM_BUFSIZE; + } + + if (n < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + return NGX_ERROR; + } + + sn = ngx_quic_create_stream(c, f->stream_id, n); if (sn == NULL) { return NGX_ERROR; } b = sn->b; + b->last = ngx_cpymem(b->last, f->data, f->length); - ngx_memcpy(b->start, f->data, f->length); - b->last = b->start + f->length; + sn->c->read->ready = 1; qc->streams.handler(sn->c); @@ -1419,7 +1430,7 @@ ngx_quic_create_uni_stream(ngx_connection_t *c) qc->streams.id_counter++; - sn = ngx_quic_create_stream(qs->parent, id); + sn = ngx_quic_create_stream(qs->parent, id, 0); if (sn == NULL) { return NULL; } @@ -1494,82 +1505,65 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) static ngx_quic_stream_t * -ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) { - size_t n; - ngx_log_t *log; - ngx_pool_t *pool; - ngx_event_t *rev, *wev; - ngx_quic_stream_t *sn; - ngx_pool_cleanup_t *cln; - ngx_quic_connection_t *qc; - - qc = c->quic; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_quic_stream_t *sn; + ngx_pool_cleanup_t *cln; - sn = ngx_pcalloc(c->pool, sizeof(ngx_quic_stream_t)); - if (sn == NULL) { + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { return NULL; } - sn->c = ngx_get_connection(-1, c->log); // TODO: free on connection termination - if (sn->c == NULL) { + sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); + if (sn == NULL) { + ngx_destroy_pool(pool); return NULL; } - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - /* XXX free connection */ - // TODO: add pool cleanup handdler + sn->node.key = id; + sn->parent = c; + sn->id = id; + + sn->b = ngx_create_temp_buf(pool, rcvbuf_size); + if (sn->b == NULL) { + ngx_destroy_pool(pool); return NULL; } log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { - /* XXX free pool and connection */ + ngx_destroy_pool(pool); return NULL; } *log = *c->log; pool->log = log; - sn->c->log = log; - sn->c->pool = pool; - - sn->c->listening = c->listening; - sn->c->sockaddr = c->sockaddr; - sn->c->local_sockaddr = c->local_sockaddr; - sn->c->addr_text = c->addr_text; - sn->c->ssl = c->ssl; - - rev = sn->c->read; - wev = sn->c->write; - - rev->ready = 1; - - rev->log = c->log; - wev->log = c->log; - - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - - n = ngx_max(NGX_QUIC_STREAM_BUFSIZE, - qc->tp.initial_max_stream_data_bidi_remote); - - sn->node.key =id; - sn->b = ngx_create_temp_buf(pool, n); - if (sn->b == NULL) { + sn->c = ngx_get_connection(-1, log); + if (sn->c == NULL) { + ngx_destroy_pool(pool); return NULL; } - ngx_rbtree_insert(&qc->streams.tree, &sn->node); - - sn->id = id; - sn->parent = c; sn->c->qs = sn; + sn->c->pool = pool; + sn->c->ssl = c->ssl; + sn->c->sockaddr = c->sockaddr; + sn->c->listening = c->listening; + sn->c->addr_text = c->addr_text; + sn->c->local_sockaddr = c->local_sockaddr; + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); sn->c->recv = ngx_quic_stream_recv; sn->c->send = ngx_quic_stream_send; sn->c->send_chain = ngx_quic_stream_send_chain; + sn->c->read->log = c->log; + sn->c->write->log = c->log; + cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { ngx_close_connection(sn->c); @@ -1580,6 +1574,8 @@ ngx_quic_create_stream(ngx_connection_t *c, ngx_uint_t id) cln->handler = ngx_quic_stream_cleanup_handler; cln->data = sn->c; + ngx_rbtree_insert(&c->quic->streams.tree, &sn->node); + return sn; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 796afffb5..baf7fb36c 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -24,6 +24,8 @@ #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 +#define NGX_QUIC_STREAM_BUFSIZE 16384 + typedef struct { /* configurable */ -- cgit v1.2.3 From dbf1b41cfbbe884413c750bf33f62f6c989cecd8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Mar 2020 12:14:24 +0300 Subject: Simplifed handling HTTP/3 streams. --- src/http/v3/ngx_http_v3.h | 19 ++- src/http/v3/ngx_http_v3_streams.c | 245 ++++++++++++-------------------------- 2 files changed, 86 insertions(+), 178 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 81e57dc36..80bdaebb5 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -15,8 +15,6 @@ #include -#define NGX_HTTP_V3_STREAM 0x48335354 /* "H3ST" */ - #define NGX_HTTP_V3_ALPN(s) NGX_HTTP_V3_ALPN_DRAFT(s) #define NGX_HTTP_V3_ALPN_DRAFT(s) "\x05h3-" #s #define NGX_HTTP_V3_ALPN_ADVERTISE NGX_HTTP_V3_ALPN(NGX_QUIC_DRAFT_VERSION) @@ -41,6 +39,14 @@ #define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06 #define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 +#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 +#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 +#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 +#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3 +#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 +#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 +#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 + typedef struct { ngx_quic_tp_t quic; @@ -51,14 +57,7 @@ typedef struct { ngx_http_connection_t hc; ngx_array_t *dynamic; - - ngx_connection_t *client_encoder; - ngx_connection_t *client_decoder; - ngx_connection_t *client_control; - - ngx_connection_t *server_encoder; - ngx_connection_t *server_decoder; - ngx_connection_t *server_control; + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; } ngx_http_v3_connection_t; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index fa1c48a8b..6078725d7 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -15,40 +15,30 @@ typedef ngx_int_t (*ngx_http_v3_handler_pt)(ngx_connection_t *c, void *data, typedef struct { - uint32_t signature; /* QSTR */ - ngx_http_v3_handler_pt handler; void *data; - - ngx_uint_t type; - ngx_uint_t client; /* unsigned client:1; */ + ngx_int_t index; } ngx_http_v3_uni_stream_t; static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); -static void ngx_http_v3_uni_stream_cleanup(void *data); static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); -static ngx_connection_t *ngx_http_v3_create_uni_stream(ngx_connection_t *c, +static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); -static ngx_connection_t *ngx_http_v3_get_control(ngx_connection_t *c); -static ngx_connection_t *ngx_http_v3_get_encoder(ngx_connection_t *c); -static ngx_connection_t *ngx_http_v3_get_decoder(ngx_connection_t *c); void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) { - ngx_pool_cleanup_t *cln; ngx_http_v3_uni_stream_t *us; c->log->connection = c->number; - /* XXX */ - (void) ngx_http_v3_get_control(c); - (void) ngx_http_v3_get_encoder(c); - (void) ngx_http_v3_get_decoder(c); + ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); + ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new uni stream id:0x%uxL", c->qs->id); @@ -59,21 +49,10 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) return; } - us->signature = NGX_HTTP_V3_STREAM; - us->client = 1; - us->type = (ngx_uint_t) -1; + us->index = -1; c->data = us; - cln = ngx_pool_cleanup_add(c->pool, 0); - if (cln == NULL) { - ngx_http_v3_close_uni_stream(c); - return; - } - - cln->handler = ngx_http_v3_uni_stream_cleanup; - cln->data = c; - c->read->handler = ngx_http_v3_read_uni_stream_type; c->write->handler = ngx_http_v3_dummy_write_handler; @@ -84,23 +63,7 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) static void ngx_http_v3_close_uni_stream(ngx_connection_t *c) { - ngx_pool_t *pool; - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); -} - - -static void -ngx_http_v3_uni_stream_cleanup(void *data) -{ - ngx_connection_t *c = data; - + ngx_pool_t *pool; ngx_http_v3_connection_t *h3c; ngx_http_v3_uni_stream_t *us; @@ -109,38 +72,17 @@ ngx_http_v3_uni_stream_cleanup(void *data) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); - switch (us->type) { - - case NGX_HTTP_V3_STREAM_ENCODER: - - if (us->client) { - h3c->client_encoder = NULL; - } else { - h3c->server_encoder = NULL; - } - - break; - - case NGX_HTTP_V3_STREAM_DECODER: - - if (us->client) { - h3c->client_decoder = NULL; - } else { - h3c->server_decoder = NULL; - } + if (us->index >= 0) { + h3c->known_streams[us->index] = NULL; + } - break; + c->destroyed = 1; - case NGX_HTTP_V3_STREAM_CONTROL: + pool = c->pool; - if (us->client) { - h3c->client_control = NULL; - } else { - h3c->server_control = NULL; - } + ngx_close_connection(c); - break; - } + ngx_destroy_pool(pool); } @@ -149,12 +91,13 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) { u_char ch; ssize_t n; + ngx_int_t index; ngx_connection_t *c; ngx_http_v3_connection_t *h3c; - ngx_http_v3_uni_stream_t *st; + ngx_http_v3_uni_stream_t *us; c = rev->data; - st = c->data; + us = c->data; h3c = c->qs->parent->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); @@ -171,21 +114,15 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) break; } - st->type = ch; - - switch (st->type) { + switch (ch) { case NGX_HTTP_V3_STREAM_ENCODER: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 encoder stream"); - if (h3c->client_encoder) { - goto failed; - } - - h3c->client_encoder = c; - st->handler = ngx_http_v3_parse_encoder; + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + us->handler = ngx_http_v3_parse_encoder; n = sizeof(ngx_http_v3_parse_encoder_t); break; @@ -195,12 +132,8 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 decoder stream"); - if (h3c->client_decoder) { - goto failed; - } - - h3c->client_decoder = c; - st->handler = ngx_http_v3_parse_decoder; + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + us->handler = ngx_http_v3_parse_decoder; n = sizeof(ngx_http_v3_parse_decoder_t); break; @@ -210,12 +143,8 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 control stream"); - if (h3c->client_control) { - goto failed; - } - - h3c->client_control = c; - st->handler = ngx_http_v3_parse_control; + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + us->handler = ngx_http_v3_parse_control; n = sizeof(ngx_http_v3_parse_control_t); break; @@ -223,13 +152,24 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) default: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 stream 0x%02xi", st->type); + "http3 stream 0x%02xi", (ngx_int_t) ch); + index = -1; n = 0; } + if (index >= 0) { + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + goto failed; + } + + us->index = index; + h3c->known_streams[index] = c; + } + if (n) { - st->data = ngx_pcalloc(c->pool, n); - if (st->data == NULL) { + us->data = ngx_pcalloc(c->pool, n); + if (us->data == NULL) { goto failed; } } @@ -258,10 +198,10 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) ssize_t n; ngx_int_t rc, i; ngx_connection_t *c; - ngx_http_v3_uni_stream_t *st; + ngx_http_v3_uni_stream_t *us; c = rev->data; - st = c->data; + us = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); @@ -277,13 +217,13 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) break; } - if (st->handler == NULL) { + if (us->handler == NULL) { continue; } for (i = 0; i < n; i++) { - rc = st->handler(c, st->data, buf[i]); + rc = us->handler(c, us->data, buf[i]); if (rc == NGX_ERROR) { goto failed; @@ -331,14 +271,37 @@ ngx_http_v3_dummy_write_handler(ngx_event_t *wev) /* XXX async & buffered stream writes */ static ngx_connection_t * -ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type) +ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) { u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; size_t n; + ngx_int_t index; ngx_connection_t *sc; - ngx_pool_cleanup_t *cln; + ngx_http_v3_connection_t *h3c; ngx_http_v3_uni_stream_t *us; + switch (type) { + case NGX_HTTP_V3_STREAM_ENCODER: + index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + break; + case NGX_HTTP_V3_STREAM_DECODER: + index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + break; + case NGX_HTTP_V3_STREAM_CONTROL: + index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + break; + default: + index = -1; + } + + h3c = c->qs->parent->data; + + if (index >= 0) { + if (h3c->known_streams[index]) { + return h3c->known_streams[index]; + } + } + sc = ngx_quic_create_uni_stream(c); if (sc == NULL) { return NULL; @@ -352,20 +315,14 @@ ngx_http_v3_create_uni_stream(ngx_connection_t *c, ngx_uint_t type) goto failed; } - us->signature = NGX_HTTP_V3_STREAM; - us->type = type; + us->index = index; + sc->data = us; sc->read->handler = ngx_http_v3_uni_read_handler; sc->write->handler = ngx_http_v3_dummy_write_handler; - cln = ngx_pool_cleanup_add(sc->pool, 0); - if (cln == NULL) { - goto failed; - } - - cln->handler = ngx_http_v3_uni_stream_cleanup; - cln->data = sc; + h3c->known_streams[index] = sc; n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; @@ -383,54 +340,6 @@ failed: } -static ngx_connection_t * -ngx_http_v3_get_control(ngx_connection_t *c) -{ - ngx_http_v3_connection_t *h3c; - - h3c = c->qs->parent->data; - - if (h3c->server_control == NULL) { - h3c->server_control = ngx_http_v3_create_uni_stream(c, - NGX_HTTP_V3_STREAM_CONTROL); - } - - return h3c->server_encoder; -} - - -static ngx_connection_t * -ngx_http_v3_get_encoder(ngx_connection_t *c) -{ - ngx_http_v3_connection_t *h3c; - - h3c = c->qs->parent->data; - - if (h3c->server_encoder == NULL) { - h3c->server_encoder = ngx_http_v3_create_uni_stream(c, - NGX_HTTP_V3_STREAM_ENCODER); - } - - return h3c->server_encoder; -} - - -static ngx_connection_t * -ngx_http_v3_get_decoder(ngx_connection_t *c) -{ - ngx_http_v3_connection_t *h3c; - - h3c = c->qs->parent->data; - - if (h3c->server_decoder == NULL) { - h3c->server_decoder = ngx_http_v3_create_uni_stream(c, - NGX_HTTP_V3_STREAM_DECODER); - } - - return h3c->server_encoder; -} - - ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) @@ -443,7 +352,7 @@ ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, "http3 client ref insert, %s[%ui] \"%V\"", dynamic ? "dynamic" : "static", index, value); - ec = ngx_http_v3_get_encoder(c); + ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); if (ec == NULL) { return NGX_ERROR; } @@ -488,7 +397,7 @@ ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client insert \"%V\":\"%V\"", name, value); - ec = ngx_http_v3_get_encoder(c); + ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); if (ec == NULL) { return NGX_ERROR; } @@ -537,7 +446,7 @@ ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client set capacity %ui", capacity); - ec = ngx_http_v3_get_encoder(c); + ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); if (ec == NULL) { return NGX_ERROR; } @@ -564,7 +473,7 @@ ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client duplicate %ui", index); - ec = ngx_http_v3_get_encoder(c); + ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); if (ec == NULL) { return NGX_ERROR; } @@ -591,7 +500,7 @@ ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client ack header %ui", stream_id); - dc = ngx_http_v3_get_decoder(c); + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { return NGX_ERROR; } @@ -618,7 +527,7 @@ ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client cancel stream %ui", stream_id); - dc = ngx_http_v3_get_decoder(c); + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { return NGX_ERROR; } @@ -645,7 +554,7 @@ ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client increment insert count %ui", inc); - dc = ngx_http_v3_get_decoder(c); + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { return NGX_ERROR; } -- cgit v1.2.3 From bcd54c26431cca5322c2413e88cbbf3097230ea4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Mar 2020 14:05:40 +0300 Subject: Fixed QUIC stream insert and find. --- src/event/ngx_event_quic.c | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index bf2191d31..7f314ffb3 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -117,7 +117,7 @@ static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, - ngx_uint_t key); + uint64_t id); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, @@ -1447,26 +1447,10 @@ ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_quic_stream_t *qn, *qnt; for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; - if (node->key < temp->key) { - - p = &temp->left; - - } else if (node->key > temp->key) { - - p = &temp->right; - - } else { /* node->key == temp->key */ - - qn = (ngx_quic_stream_t *) &node->color; - qnt = (ngx_quic_stream_t *) &temp->color; - - if (qn->c < qnt->c) { - p = &temp->left; - } else { - p = &temp->right; - } - } + p = (qn->id < qnt->id) ? &temp->left : &temp->right; if (*p == sentinel) { break; @@ -1484,20 +1468,22 @@ ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, static ngx_quic_stream_t * -ngx_quic_find_stream(ngx_rbtree_t *rbtree, ngx_uint_t key) +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) { ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; node = rbtree->root; sentinel = rbtree->sentinel; while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; - if (key == node->key) { - return (ngx_quic_stream_t *) node; + if (id == qn->id) { + return qn; } - node = (key < node->key) ? node->left : node->right; + node = (id < qn->id) ? node->left : node->right; } return NULL; -- cgit v1.2.3 From 715d8a250b58c10d87fa44b63367a30ae0bf47c9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 26 Mar 2020 12:11:50 +0300 Subject: Removed memory allocations from encryption code. + ngx_quic_encrypt(): - no longer accepts pool as argument - pkt is 1st arg - payload is passed as pkt->payload - performs encryption to the specified static buffer + ngx_quic_create_long/short_packet() functions: - single buffer for everything, allocated by caller - buffer layout is: [ ad | payload | TAG ] the result is in the beginning of buffer with proper length - nonce is calculated on stack - log is passed explicitly, pkt is 1st arg - no more allocations inside + ngx_quic_create_long_header(): - args changed: no need to pass str_t + added ngx_quic_create_short_header() --- src/event/ngx_event_quic.c | 15 ++- src/event/ngx_event_quic_protection.c | 169 +++++++++++++--------------------- src/event/ngx_event_quic_protection.h | 4 +- src/event/ngx_event_quic_transport.c | 26 +++++- src/event/ngx_event_quic_transport.h | 5 +- 5 files changed, 101 insertions(+), 118 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7f314ffb3..43cd48530 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1365,8 +1365,9 @@ static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, enum ssl_encryption_level_t level, ngx_str_t *payload) { - ngx_str_t res; - ngx_quic_header_t pkt; + ngx_str_t res; + ngx_quic_header_t pkt; + static u_char buf[65535]; static ngx_str_t initial_token = ngx_null_string; @@ -1377,6 +1378,7 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, pkt.level = level; pkt.dcid = qc->dcid; pkt.scid = qc->scid; + pkt.payload = *payload; if (level == ssl_encryption_initial) { pkt.number = &qc->initial_pn; @@ -1394,9 +1396,12 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, pkt.secret = &qc->secrets.server.ad; } - if (ngx_quic_encrypt(c->pool, c->ssl->connection, &pkt, payload, &res) - != NGX_OK) - { + // TODO: ensure header size + payload.len + crypto tail fits into packet + // (i.e. limit payload while pushing frames to < 65k) + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index ee6f011eb..34f2ab49f 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -43,21 +43,19 @@ static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad); -static ngx_int_t ngx_quic_tls_seal(ngx_pool_t *pool, - const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, - u_char *nonce, ngx_str_t *in, ngx_str_t *ad); +static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); -static ngx_int_t ngx_quic_create_long_packet(ngx_pool_t *pool, - ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, - ngx_str_t *res); +static ssize_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, + ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); -static ngx_int_t ngx_quic_create_short_packet(ngx_pool_t *pool, - ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in, - ngx_str_t *res); +static ssize_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, + ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); static ngx_int_t @@ -467,19 +465,9 @@ ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, static ngx_int_t -ngx_quic_tls_seal(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad) +ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) { - ngx_log_t *log; - - log = pool->log; // TODO: pass log ? - - out->len = in->len + EVP_GCM_TLS_TAG_LEN; - out->data = ngx_pnalloc(pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } #ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *ctx; @@ -682,161 +670,128 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, } -static ngx_int_t -ngx_quic_create_long_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +static ssize_t +ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + ngx_str_t *res) { - u_char *p, *pnp, *nonce, *sample, *packet; + u_char *pnp, *sample; uint64_t pn; - ngx_log_t *log; ngx_str_t ad, out; ngx_quic_ciphers_t ciphers; - u_char mask[16]; + u_char nonce[12], mask[16]; - log = pool->log; + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; - out.len = payload->len + EVP_GCM_TLS_TAG_LEN; + ad.data = res->data; + ad.len = ngx_quic_create_long_header(pkt, ad.data, out.len, &pnp); - ad.data = ngx_alloc(NGX_QUIC_MAX_LONG_HEADER, log); - if (ad.data == 0) { - return NGX_ERROR; - } + out.data = res->data + ad.len; - ad.len = ngx_quic_create_long_header(pkt, &ad, out.len, &pnp); - - ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } - nonce = ngx_pstrdup(pool, &pkt->secret->iv); + ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); pn = *pkt->number; nonce[11] ^= pn; - ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(log, "nonce", nonce, 12); + ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); - if (ngx_quic_tls_seal(pool, ciphers.c, pkt->secret, &out, - nonce, payload, &ad) + if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, + nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; } sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + != NGX_OK) + { return NGX_ERROR; } - ngx_quic_hexdump0(log, "sample", sample, 16); - ngx_quic_hexdump0(log, "mask", mask, 16); - ngx_quic_hexdump0(log, "hp_key", pkt->secret->hp.data, 16); + ngx_quic_hexdump0(pkt->log, "sample", sample, 16); + ngx_quic_hexdump0(pkt->log, "mask", mask, 16); + ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); // header protection, pnl = 0 ad.data[0] ^= mask[0] & 0x0f; *pnp ^= mask[1]; - packet = ngx_alloc(ad.len + out.len, log); - if (packet == 0) { - return NGX_ERROR; - } - - p = ngx_cpymem(packet, ad.data, ad.len); - p = ngx_cpymem(p, out.data, out.len); - - res->data = packet; - res->len = p - packet; + res->len = ad.len + out.len; return NGX_OK; } -static ngx_int_t -ngx_quic_create_short_packet(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +static ssize_t +ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + ngx_str_t *res) { - u_char *p, *pnp, *nonce, *sample, *packet; - ngx_log_t *log; + u_char *pnp, *sample; ngx_str_t ad, out; ngx_quic_ciphers_t ciphers; - u_char mask[16]; - - log = pool->log; - - out.len = payload->len + EVP_GCM_TLS_TAG_LEN; - - ad.data = ngx_alloc(NGX_QUIC_MAX_SHORT_HEADER, log); - if (ad.data == 0) { - return NGX_ERROR; - } - - p = ad.data; - - *p++ = 0x40; + u_char nonce[12], mask[16]; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; - pnp = p; + ad.data = res->data; + ad.len = ngx_quic_create_short_header(pkt, ad.data, out.len, &pnp); - *p++ = (*pkt->number); - - ad.len = p - ad.data; - - ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } - nonce = ngx_pstrdup(pool, &pkt->secret->iv); + ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); if (pkt->level == ssl_encryption_handshake || pkt->level == ssl_encryption_application) { nonce[11] ^= *pkt->number; } - ngx_quic_hexdump0(log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(log, "nonce", nonce, 12); + ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); - if (ngx_quic_tls_seal(pool, ciphers.c, pkt->secret, &out, - nonce, payload, &ad) + out.data = res->data + ad.len; + + if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, + &ad, pkt->log) != NGX_OK) { return NGX_ERROR; } - ngx_quic_hexdump0(log, "out", out.data, out.len); + ngx_quic_hexdump0(pkt->log, "out", out.data, out.len); sample = &out.data[3]; // pnl=0 - if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + != NGX_OK) + { return NGX_ERROR; } - ngx_quic_hexdump0(log, "sample", sample, 16); - ngx_quic_hexdump0(log, "mask", mask, 16); - ngx_quic_hexdump0(log, "hp_key", pkt->secret->hp.data, 16); + ngx_quic_hexdump0(pkt->log, "sample", sample, 16); + ngx_quic_hexdump0(pkt->log, "mask", mask, 16); + ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); // header protection, pnl = 0 ad.data[0] ^= mask[0] & 0x1f; *pnp ^= mask[1]; - packet = ngx_alloc(ad.len + out.len, log); - if (packet == 0) { - return NGX_ERROR; - } - - p = ngx_cpymem(packet, ad.data, ad.len); - p = ngx_cpymem(p, out.data, out.len); + res->len = ad.len + out.len; - ngx_quic_hexdump0(log, "packet", packet, p - packet); - - res->data = packet; - res->len = p - packet; + ngx_quic_hexdump0(pkt->log, "packet", res->data, res->len); return NGX_OK; } + static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) { @@ -855,15 +810,15 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) } -ngx_int_t -ngx_quic_encrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res) +ssize_t +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + ngx_str_t *res) { if (pkt->level == ssl_encryption_application) { - return ngx_quic_create_short_packet(pool, ssl_conn, pkt, payload, res); + return ngx_quic_create_short_packet(pkt, ssl_conn, res); } - return ngx_quic_create_long_packet(pool, ssl_conn, pkt, payload, res); + return ngx_quic_create_long_packet(pkt, ssl_conn, res); } diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index 499301f41..c5c62c760 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -36,8 +36,8 @@ int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len, ngx_quic_peer_secrets_t *qsec); -ngx_int_t ngx_quic_encrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res); +ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + ngx_str_t *res); ngx_int_t ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index b391f6b79..467a41157 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -341,12 +341,12 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) size_t -ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, +ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp) { - u_char *p, *start; + u_char *p, *start; - p = start = out->data; + p = start = out; *p++ = pkt->flags; @@ -372,6 +372,26 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, } +size_t +ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp) +{ + u_char *p, *start; + + p = start = out; + + *p++ = 0x40; + + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + *pnp = p; + + *p++ = (*pkt->number); + + return p - start; +} + + ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 931361180..d9e8a91b6 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -255,11 +255,14 @@ typedef struct { u_char *ngx_quic_error_text(uint64_t error_code); ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); -size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out, +size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid); +size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); + ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); -- cgit v1.2.3 From d71df64e9a3902496cc79a6b65e3dbcf1933e1f4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Mar 2020 23:40:50 +0300 Subject: QUIC frames reuse. --- src/event/ngx_event_quic.c | 119 +++++++++++++++++++++++++++-------- src/event/ngx_event_quic_transport.h | 1 + 2 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 43cd48530..a2435ea9a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -44,6 +44,11 @@ struct ngx_quic_connection_s { ngx_quic_secrets_t secrets; ngx_ssl_t *ssl; ngx_quic_frame_t *frames; + ngx_quic_frame_t *free_frames; + +#if (NGX_DEBUG) + ngx_uint_t nframes; +#endif ngx_quic_streams_t streams; ngx_uint_t max_data; @@ -127,6 +132,8 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, static void ngx_quic_stream_cleanup_handler(void *data); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); +static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c, size_t size); +static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); static SSL_QUIC_METHOD quic_method = { @@ -256,23 +263,18 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } } - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c, len); if (frame == NULL) { return 0; } - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - return 0; - } - - ngx_memcpy(p, data, len); + ngx_memcpy(frame->data, data, len); frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; frame->u.crypto.offset += qc->crypto_offset[level]; frame->u.crypto.len = len; - frame->u.crypto.data = p; + frame->u.crypto.data = frame->data; qc->crypto_offset[level] += len; @@ -314,7 +316,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, "ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return 0; } @@ -984,7 +986,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) // packet processed, ACK it now if required // TODO: if (ack_required) ... - currently just ack each packet - ack_frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + ack_frame = ngx_quic_alloc_frame(c, 0); if (ack_frame == NULL) { return NGX_ERROR; } @@ -1066,7 +1068,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, { ngx_quic_frame_t *frame; - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } @@ -1170,7 +1172,7 @@ ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, { ngx_quic_frame_t *frame; - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } @@ -1212,7 +1214,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, b = sn->b; n = (b->pos - b->start) + (b->end - b->last); - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } @@ -1254,7 +1256,7 @@ ngx_quic_output(ngx_connection_t *c) { size_t len, hlen, n; ngx_uint_t lvl; - ngx_quic_frame_t *f, *start; + ngx_quic_frame_t *f, *start, *next; ngx_quic_connection_t *qc; qc = c->quic; @@ -1294,12 +1296,17 @@ ngx_quic_output(ngx_connection_t *c) return NGX_ERROR; } + while (start != f) { + next = start->next; + ngx_quic_free_frame(c, start); + start = next; + } + if (f == NULL) { break; } lvl = f->level; // TODO: must not decrease (ever, also between calls) - start = f; } while (1); @@ -1621,7 +1628,6 @@ 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) { - u_char *p; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -1637,17 +1643,12 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); - frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(pc, size); if (frame == NULL) { return 0; } - p = ngx_pnalloc(pc->pool, size); - if (p == NULL) { - return 0; - } - - ngx_memcpy(p, buf, size); + ngx_memcpy(frame->data, buf, size); frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ @@ -1659,7 +1660,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; frame->u.stream.length = size; - frame->u.stream.data = p; + frame->u.stream.data = frame->data; c->sent += size; @@ -1702,7 +1703,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin"); - frame = ngx_pcalloc(pc->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { return; } @@ -1764,3 +1765,71 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, return NULL; } + + +static ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) +{ + u_char *p; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + if (size) { + p = ngx_alloc(size, c->log); + if (p == NULL) { + return NULL; + } + + } else { + p = NULL; + } + + qc = c->quic; + frame = qc->free_frames; + + if (frame) { + qc->free_frames = frame->next; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "reuse quic frame n:%ui", qc->nframes); + + } else { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + ngx_free(p); + return NULL; + } + +#if (NGX_DEBUG) + ++qc->nframes; +#endif + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "alloc quic frame n:%ui", qc->nframes); + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + frame->data = p; + + return frame; +} + + +static void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (frame->data) { + ngx_free(frame->data); + } + + frame->next = qc->free_frames; + qc->free_frames = frame; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "free quic frame n:%ui", qc->nframes); +} diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index d9e8a91b6..2bedd487c 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -205,6 +205,7 @@ struct ngx_quic_frame_s { ngx_uint_t type; enum ssl_encryption_level_t level; ngx_quic_frame_t *next; + u_char *data; union { ngx_quic_ack_frame_t ack; ngx_quic_crypto_frame_t crypto; -- cgit v1.2.3 From 7c6e6426c67ec19603e2aeaa6024036d27659c4a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 26 Mar 2020 13:54:49 +0300 Subject: Logging cleanup. pool->log is replaced with pkt->log or explicit argument passing where possible. --- src/event/ngx_event_quic_protection.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 34f2ab49f..a93650131 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -42,7 +42,7 @@ static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad); + ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); @@ -358,12 +358,8 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, static ngx_int_t ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad) + ngx_str_t *ad, ngx_log_t *log) { - ngx_log_t *log; - - log = pool->log; // TODO: pass log ? - out->len = in->len - EVP_GCM_TLS_TAG_LEN; out->data = ngx_pnalloc(pool, out->len); if (out->data == NULL) { @@ -829,14 +825,11 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, u_char clearflags, *p, *sample; uint8_t *nonce; uint64_t pn; - ngx_log_t *log; ngx_int_t pnl, rc; ngx_str_t in, ad; ngx_quic_ciphers_t ciphers; uint8_t mask[16]; - log = pool->log; - if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } @@ -851,11 +844,13 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, sample = p + 4; - ngx_quic_hexdump0(log, "sample", sample, 16); + ngx_quic_hexdump0(pkt->log, "sample", sample, 16); /* header protection */ - if (ngx_quic_tls_hp(log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + != NGX_OK) + { return NGX_ERROR; } @@ -871,10 +866,10 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, pkt->pn = pn; - ngx_quic_hexdump0(log, "mask", mask, 5); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + ngx_quic_hexdump0(pkt->log, "mask", mask, 5); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic clear flags: %xi", clearflags); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic packet number: %uL, len: %xi", pn, pnl); /* packet protection */ @@ -904,13 +899,13 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, nonce = ngx_pstrdup(pool, &pkt->secret->iv); nonce[11] ^= pn; - ngx_quic_hexdump0(log, "nonce", nonce, 12); - ngx_quic_hexdump0(log, "ad", ad.data, ad.len); + ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); + ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); rc = ngx_quic_tls_open(pool, ciphers.c, pkt->secret, &pkt->payload, - nonce, &in, &ad); + nonce, &in, &ad, pkt->log); - ngx_quic_hexdump0(log, "packet payload", + ngx_quic_hexdump0(pkt->log, "packet payload", pkt->payload.data, pkt->payload.len); return rc; -- cgit v1.2.3 From 7d408f1bba61998ef6e0cb5296a25433c6a2f275 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 26 Mar 2020 16:54:46 +0300 Subject: Got rid of memory allocation in decryption. Static buffers are used instead in functions where decryption takes place. The pkt->plaintext points to the beginning of a static buffer. The pkt->payload.data points to decrypted data actual start. --- src/event/ngx_event_quic.c | 21 +++++++++++++------- src/event/ngx_event_quic_protection.c | 36 +++++++++++++++++------------------ src/event/ngx_event_quic_protection.h | 3 +-- src/event/ngx_event_quic_transport.h | 3 ++- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a2435ea9a..a479fabfd 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -377,6 +377,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, { ngx_quic_tp_t *ctp; ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; if (ngx_buf_size(pkt->raw) < 1200) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); @@ -448,8 +449,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, pkt->secret = &qc->secrets.client.in; pkt->level = ssl_encryption_initial; + pkt->plaintext = buf; - if (ngx_quic_decrypt(c->pool, NULL, pkt) != NGX_OK) { + if (ngx_quic_decrypt(pkt, NULL) != NGX_OK) { return NGX_ERROR; } @@ -532,8 +534,7 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_buf_t b; ngx_connection_t *c; ngx_quic_connection_t *qc; - - static u_char buf[65535]; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; b.start = buf; b.end = buf + sizeof(buf); @@ -719,6 +720,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; c->log->action = "processing initial quic packet"; @@ -735,8 +737,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->secret = &qc->secrets.client.in; pkt->level = ssl_encryption_initial; + pkt->plaintext = buf; - if (ngx_quic_decrypt(c->pool, ssl_conn, pkt) != NGX_OK) { + if (ngx_quic_decrypt(pkt, ssl_conn) != NGX_OK) { return NGX_ERROR; } @@ -748,6 +751,7 @@ static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; c->log->action = "processing handshake quic packet"; @@ -790,8 +794,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->secret = &qc->secrets.client.hs; pkt->level = ssl_encryption_handshake; + pkt->plaintext = buf; - if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { + if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { return NGX_ERROR; } @@ -803,6 +808,7 @@ static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; c->log->action = "processing application data quic packet"; @@ -820,8 +826,9 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->secret = &qc->secrets.client.ad; pkt->level = ssl_encryption_application; + pkt->plaintext = buf; - if (ngx_quic_decrypt(c->pool, c->ssl->connection, pkt) != NGX_OK) { + if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { return NGX_ERROR; } @@ -1374,7 +1381,7 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, { ngx_str_t res; ngx_quic_header_t pkt; - static u_char buf[65535]; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; static ngx_str_t initial_token = ngx_null_string; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index a93650131..efbf5191f 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -40,7 +40,7 @@ static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); -static ngx_int_t ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, +static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, @@ -356,15 +356,10 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, static ngx_int_t -ngx_quic_tls_open(ngx_pool_t *pool, const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad, ngx_log_t *log) +ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, + ngx_log_t *log) { - out->len = in->len - EVP_GCM_TLS_TAG_LEN; - out->data = ngx_pnalloc(pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } #ifdef OPENSSL_IS_BORINGSSL EVP_AEAD_CTX *ctx; @@ -819,16 +814,14 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_int_t -ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt) +ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) { u_char clearflags, *p, *sample; - uint8_t *nonce; uint64_t pn; ngx_int_t pnl, rc; ngx_str_t in, ad; ngx_quic_ciphers_t ciphers; - uint8_t mask[16]; + uint8_t mask[16], nonce[12]; if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; @@ -884,10 +877,7 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, } ad.len = p - pkt->data; - ad.data = ngx_pnalloc(pool, ad.len); - if (ad.data == NULL) { - return NGX_ERROR; - } + ad.data = pkt->plaintext; ngx_memcpy(ad.data, pkt->data, ad.len); ad.data[0] = clearflags; @@ -896,13 +886,21 @@ ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; } while (--pnl); - nonce = ngx_pstrdup(pool, &pkt->secret->iv); + ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); nonce[11] ^= pn; ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); - rc = ngx_quic_tls_open(pool, ciphers.c, pkt->secret, &pkt->payload, + pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; + + if (NGX_QUIC_DEFAULT_MAX_PACKET_SIZE - ad.len < pkt->payload.len) { + return NGX_ERROR; + } + + pkt->payload.data = pkt->plaintext + ad.len; + + rc = ngx_quic_tls_open(ciphers.c, pkt->secret, &pkt->payload, nonce, &in, &ad, pkt->log); ngx_quic_hexdump0(pkt->log, "packet payload", diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index c5c62c760..2763375e4 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -39,8 +39,7 @@ int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); -ngx_int_t ngx_quic_decrypt(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn); #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 2bedd487c..3afe8688c 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -249,7 +249,8 @@ typedef struct { ngx_str_t dcid; ngx_str_t scid; uint64_t pn; - ngx_str_t payload; /* decrypted */ + u_char *plaintext; + ngx_str_t payload; /* decrypted data */ } ngx_quic_header_t; -- cgit v1.2.3 From 41a8b8d39258aa66cc5069d7684f2d7fba346143 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 26 Mar 2020 18:29:38 +0300 Subject: Merged ngx_quic_send_packet() into ngx_quic_send_frames(). This allows to avoid extra allocation and use two static buffers instead. Adjusted maximum paket size calculation: need to account a tag. --- src/event/ngx_event_quic.c | 67 +++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a479fabfd..185330600 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -114,10 +114,6 @@ static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, static ngx_int_t ngx_quic_output(ngx_connection_t *c); ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_quic_frame_t *end, size_t total); -static ngx_int_t ngx_quic_send_packet(ngx_connection_t *c, - ngx_quic_connection_t *qc, enum ssl_encryption_level_t level, - ngx_str_t *payload); - static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); @@ -1283,6 +1279,7 @@ ngx_quic_output(ngx_connection_t *c) hlen = (lvl == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER : NGX_QUIC_MAX_LONG_HEADER; + hlen += EVP_GCM_TLS_TAG_LEN; do { /* process same-level group of frames */ @@ -1333,20 +1330,23 @@ ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_quic_frame_t *end, size_t total) { - ssize_t len; - u_char *p; - ngx_str_t out; - ngx_quic_frame_t *f; + ssize_t len; + u_char *p; + ngx_str_t out, res; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + static ngx_str_t initial_token = ngx_null_string; + static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char dst[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "sending frames %p...%p", start, end); - p = ngx_pnalloc(c->pool, total); - if (p == NULL) { - return NGX_ERROR; - } + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - out.data = p; + p = src; + out.data = src; for (f = start; f != end; f = f->next) { @@ -1366,41 +1366,15 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, "packet ready: %ui bytes at level %d", out.len, start->level); - // IOVEC/sendmsg_chain ? - if (ngx_quic_send_packet(c, c->quic, start->level, &out) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, - enum ssl_encryption_level_t level, ngx_str_t *payload) -{ - ngx_str_t res; - ngx_quic_header_t pkt; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; - - static ngx_str_t initial_token = ngx_null_string; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - ngx_quic_hexdump0(c->log, "payload", payload->data, payload->len); - - pkt.log = c->log; - pkt.level = level; - pkt.dcid = qc->dcid; - pkt.scid = qc->scid; - pkt.payload = *payload; + qc = c->quic; - if (level == ssl_encryption_initial) { + if (start->level == ssl_encryption_initial) { pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; pkt.secret = &qc->secrets.server.in; pkt.token = initial_token; - } else if (level == ssl_encryption_handshake) { + } else if (start->level == ssl_encryption_handshake) { pkt.number = &qc->handshake_pn; pkt.flags = NGX_QUIC_PKT_HANDSHAKE; pkt.secret = &qc->secrets.server.hs; @@ -1410,10 +1384,13 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc, pkt.secret = &qc->secrets.server.ad; } - // TODO: ensure header size + payload.len + crypto tail fits into packet - // (i.e. limit payload while pushing frames to < 65k) + pkt.log = c->log; + pkt.level = start->level; + pkt.dcid = qc->dcid; + pkt.scid = qc->scid; + pkt.payload = out; - res.data = buf; + res.data = dst; if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) { return NGX_ERROR; -- cgit v1.2.3 From 3fbdc04072afc261d096450b4e391d7f3fe01f54 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 27 Mar 2020 12:52:08 +0300 Subject: Unbreak sending CONNECTION_CLOSE from the send_alert callback. --- src/event/ngx_event_quic.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 185330600..aff97b2db 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -304,10 +304,6 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - if (c->quic->closing) { - return 1; - } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); -- cgit v1.2.3 From 81f7cff632d7db267be42f5451c6e1f29d021685 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 15:50:42 +0300 Subject: Fixed buffer overflow. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 542fc387d..b00b93ce2 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -263,7 +263,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - len = 0; + len = 2; if (r->headers_out.status == NGX_HTTP_OK) { len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); -- cgit v1.2.3 From 80a38580bd04f499d72ab3b6f7776e275e47a2b3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 19:46:54 +0300 Subject: Chunked response body in HTTP/3. --- src/http/modules/ngx_http_chunked_filter_module.c | 58 ++++++++++++++++++----- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_request.c | 35 ++++++++++++-- 3 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index 4d6fd3eed..87b032496 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -18,7 +18,7 @@ typedef struct { static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf); static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r, - ngx_http_chunked_filter_ctx_t *ctx); + ngx_http_chunked_filter_ctx_t *ctx, size_t size); static ngx_http_module_t ngx_http_chunked_filter_module_ctx = { @@ -106,6 +106,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; off_t size; + size_t n; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; @@ -161,29 +162,50 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) chunk = b->start; if (chunk == NULL) { - /* the "0000000000000000" is 64-bit hexadecimal string */ - chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1); +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + n = NGX_HTTP_V3_VARLEN_INT_LEN * 2; + + } else +#endif + { + /* the "0000000000000000" is 64-bit hexadecimal string */ + n = sizeof("0000000000000000" CRLF) - 1; + } + + chunk = ngx_palloc(r->pool, n); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; - b->end = chunk + sizeof("0000000000000000" CRLF) - 1; + b->end = chunk + n; } b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->memory = 0; b->temporary = 1; b->pos = chunk; - b->last = ngx_sprintf(chunk, "%xO" CRLF, size); + +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + } else +#endif + { + b->last = ngx_sprintf(chunk, "%xO" CRLF, size); + } tl->next = out; out = tl; } if (cl->buf->last_buf) { - tl = ngx_http_chunked_create_trailers(r, ctx); + tl = ngx_http_chunked_create_trailers(r, ctx, size); if (tl == NULL) { return NGX_ERROR; } @@ -192,11 +214,12 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) *ll = tl; - if (size == 0) { - tl->buf->pos += 2; - } - - } else if (size > 0) { + } else if (size > 0 +#if (NGX_HTTP_V3) + && r->http_version != NGX_HTTP_VERSION_30 +#endif + ) + { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; @@ -227,7 +250,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) static ngx_chain_t * ngx_http_chunked_create_trailers(ngx_http_request_t *r, - ngx_http_chunked_filter_ctx_t *ctx) + ngx_http_chunked_filter_ctx_t *ctx, size_t size) { size_t len; ngx_buf_t *b; @@ -236,6 +259,12 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r, ngx_list_part_t *part; ngx_table_elt_t *header; +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return ngx_http_v3_create_trailers(r); + } +#endif + len = 0; part = &r->headers_out.trailers.part; @@ -288,7 +317,10 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r, b->last = b->pos; - *b->last++ = CR; *b->last++ = LF; + if (size > 0) { + *b->last++ = CR; *b->last++ = LF; + } + *b->last++ = '0'; *b->last++ = CR; *b->last++ = LF; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 80bdaebb5..d28074d37 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -69,6 +69,7 @@ typedef struct { ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b); ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); +ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r); uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b00b93ce2..756a6f90d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -258,11 +258,6 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - /* XXX support chunked body in the chunked filter */ - if (!r->header_only && r->headers_out.content_length_n == -1) { - return NULL; - } - len = 2; if (r->headers_out.status == NGX_HTTP_OK) { @@ -578,3 +573,33 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return hl; } + + +ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create trailers"); + + /* XXX */ + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->last_buf = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + return cl; +} -- cgit v1.2.3 From 89a6a4f19827317186fa807106454b024406abd0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 19:08:24 +0300 Subject: Push QUIC stream frames in send() and cleanup handler. --- src/event/ngx_event_quic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index aff97b2db..2a3e72803 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1649,6 +1649,8 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(qc, frame); + ngx_quic_output(pc); + return size; } @@ -1703,6 +1705,8 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_sprintf(frame->info, "stream %xi fin=1 level=%d", qs->id, frame->level); ngx_quic_queue_frame(qc, frame); + + (void) ngx_quic_output(pc); } -- cgit v1.2.3 From 84a783501590e13ab27277e11f179067c08d38b3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 10:02:45 +0300 Subject: Fixed handling QUIC stream eof. Set r->pending_eof flag for a new QUIC stream with the fin bit. Also, keep r->ready set when r->pending_eof is set and buffer is empty. --- src/event/ngx_event_quic.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2a3e72803..458484a5f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1157,7 +1157,12 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, b = sn->b; b->last = ngx_cpymem(b->last, f->data, f->length); - sn->c->read->ready = 1; + rev = sn->c->read; + rev->ready = 1; + + if (f->fin) { + rev->pending_eof = 1; + } qc->streams.handler(sn->c); @@ -1595,7 +1600,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) if (b->pos == b->last) { b->pos = b->start; b->last = b->start; - rev->ready = 0; + rev->ready = rev->pending_eof; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From fa1e1beadca8b1ea900ec654919aea58762ab746 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 19:41:06 +0300 Subject: Parsing HTTP/3 request body. --- src/http/ngx_http.h | 3 ++ src/http/ngx_http_parse.c | 11 +++++++ src/http/ngx_http_request.c | 1 + src/http/ngx_http_request_body.c | 29 +++++++++++------- src/http/v3/ngx_http_v3.h | 2 ++ src/http/v3/ngx_http_v3_parse.c | 58 +++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_parse.h | 10 +++++++ src/http/v3/ngx_http_v3_request.c | 63 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 167 insertions(+), 10 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 8772001c0..a0946c95a 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -63,6 +63,9 @@ struct ngx_http_chunked_s { ngx_uint_t state; off_t size; off_t length; +#if (NGX_HTTP_V3) + void *h3_parse; +#endif }; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 28aa8b0dd..92bcf12ad 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -2185,6 +2185,12 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, sw_trailer_header_almost_done } state; +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return ngx_http_v3_parse_request_body(r, b, ctx); + } +#endif + state = ctx->state; if (state == sw_chunk_data && ctx->size == 0) { @@ -2371,6 +2377,11 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, } } + if (b->last_buf) { + /* XXX client prematurely closed connection */ + return NGX_ERROR; + } + data: ctx->state = state; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 6f168c8bd..4368e79c0 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -709,6 +709,7 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_V3) if (hc->quic) { r->http_version = NGX_HTTP_VERSION_30; + r->headers_in.chunked = 1; } #endif diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index c4f092e59..b07d8562f 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -343,11 +343,10 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) } if (n == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client prematurely closed connection"); + rb->buf->last_buf = 1; } - if (n == 0 || n == NGX_ERROR) { + if (n == NGX_ERROR) { c->error = 1; return NGX_HTTP_BAD_REQUEST; } @@ -355,7 +354,7 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) rb->buf->last += n; r->request_length += n; - if (n == rest) { + if (n == rest || n == 0) { /* pass buffer to request body filter chain */ out.buf = rb->buf; @@ -805,11 +804,7 @@ ngx_http_test_expect(ngx_http_request_t *r) if (r->expect_tested || r->headers_in.expect == NULL - || r->http_version < NGX_HTTP_VERSION_11 -#if (NGX_HTTP_V2) - || r->stream != NULL -#endif - ) + || r->http_version != NGX_HTTP_VERSION_11) { return NGX_OK; } @@ -914,6 +909,11 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) b->last_buf = 1; } + if (cl->buf->last_buf && rb->rest > 0) { + /* XXX client prematurely closed connection */ + return NGX_ERROR; + } + *ll = tl; ll = &tl->next; } @@ -950,7 +950,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) } r->headers_in.content_length_n = 0; - rb->rest = 3; + +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rb->rest = 1; + + } else +#endif + { + rb->rest = 3; + } } out = NULL; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index d28074d37..3f35e985e 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -68,6 +68,8 @@ typedef struct { ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, + ngx_http_chunked_t *ctx); ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 0fd44bc40..3be3802ed 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1421,3 +1421,61 @@ done: st->state = sw_start; return NGX_DONE; } + + +ngx_int_t +ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, + u_char ch) +{ + enum { + sw_start = 0, + sw_type, + sw_length + }; + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) { + return NGX_ERROR; + } + + st->state = sw_length; + break; + + case sw_length: + + if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { + break; + } + + st->length = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data frame len:%ui", st->length); + + goto done; + } + + return NGX_AGAIN; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); + + st->state = sw_start; + return NGX_DONE; +} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index 959da7941..ec78c7c35 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -105,6 +105,13 @@ typedef struct { } ngx_http_v3_parse_control_t; +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_data_t; + + ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, u_char ch); ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, @@ -141,5 +148,8 @@ ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch); +ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, + ngx_http_v3_parse_data_t *st, u_char ch); + #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 756a6f90d..911dbab36 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -241,6 +241,69 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, } +ngx_int_t +ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, + ngx_http_chunked_t *ctx) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_parse_data_t *st; + + c = r->connection; + st = ctx->h3_parse; + + if (st == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse request body"); + + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t)); + if (st == NULL) { + goto failed; + } + + r->h3_parse = st; + } + + if (ctx->size) { + return NGX_OK; + } + + while (b->pos < b->last) { + rc = ngx_http_v3_parse_data(c, st, *b->pos++); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_AGAIN) { + continue; + } + + /* rc == NGX_DONE */ + + ctx->size = st->length; + return NGX_OK; + } + + if (!b->last_buf) { + ctx->length = 1; + return NGX_AGAIN; + } + + if (st->state) { + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + + return NGX_DONE; + +failed: + + return NGX_ERROR; +} + + ngx_chain_t * ngx_http_v3_create_header(ngx_http_request_t *r) { -- cgit v1.2.3 From 7a0b840c51f7ed60453d7fb06731ae024295327b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 28 Mar 2020 18:02:20 +0300 Subject: HTTP/3: static table cleanup. --- src/http/v3/ngx_http_v3_tables.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index b4fc90b38..f58f190f1 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -17,7 +17,7 @@ static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c); static ngx_http_v3_header_t ngx_http_v3_static_table[] = { { ngx_string(":authority"), ngx_string("") }, - { ngx_string(":path"), ngx_string("/") }, + { ngx_string(":path"), ngx_string("/") }, { ngx_string("age"), ngx_string("0") }, { ngx_string("content-disposition"), ngx_string("") }, { ngx_string("content-length"), ngx_string("0") }, @@ -47,8 +47,8 @@ static ngx_http_v3_header_t ngx_http_v3_static_table[] = { { ngx_string(":status"), ngx_string("503") }, { ngx_string("accept"), ngx_string("*/*") }, { ngx_string("accept"), - ngx_string("application/dns-message ") }, - { ngx_string("accept-encoding"), ngx_string("gzip,") }, + ngx_string("application/dns-message") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, { ngx_string("accept-ranges"), ngx_string("bytes") }, { ngx_string("access-control-allow-headers"), ngx_string("cache-control") }, @@ -62,7 +62,7 @@ static ngx_http_v3_header_t ngx_http_v3_static_table[] = { { ngx_string("cache-control"), ngx_string("no-cache") }, { ngx_string("cache-control"), ngx_string("no-store") }, { ngx_string("cache-control"), - ngx_string("public, max-age=31536000 ") }, + ngx_string("public, max-age=31536000") }, { ngx_string("content-encoding"), ngx_string("br") }, { ngx_string("content-encoding"), ngx_string("gzip") }, { ngx_string("content-type"), @@ -90,7 +90,8 @@ static ngx_http_v3_header_t ngx_http_v3_static_table[] = { ngx_string("max-age=31536000;includesubdomains;preload") }, { ngx_string("vary"), ngx_string("accept-encoding") }, { ngx_string("vary"), ngx_string("origin") }, - { ngx_string("x-content-type-options"),ngx_string("nosniff") }, + { ngx_string("x-content-type-options"), + ngx_string("nosniff") }, { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, { ngx_string(":status"), ngx_string("100") }, { ngx_string(":status"), ngx_string("204") }, @@ -123,9 +124,9 @@ static ngx_http_v3_header_t ngx_http_v3_static_table[] = { { ngx_string("access-control-request-method"), ngx_string("post") }, { ngx_string("alt-svc"), ngx_string("clear") }, - { ngx_string("horization"), ngx_string("") }, + { ngx_string("authorization"), ngx_string("") }, { ngx_string("content-security-policy"), - ngx_string("script-src") }, + ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, { ngx_string("early-data"), ngx_string("1") }, { ngx_string("expect-ct"), ngx_string("") }, { ngx_string("forwarded"), ngx_string("") }, -- cgit v1.2.3 From 4502e5b1e98412ae261d3440e2962519e3e71d5d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 28 Mar 2020 18:41:31 +0300 Subject: HTTP/3: http3 variable. --- src/http/v3/ngx_http_v3_module.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 45bfc5b1c..279210337 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -102,6 +102,8 @@ static ngx_command_t ngx_http_v3_commands[] = { static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, @@ -143,6 +145,9 @@ static ngx_http_variable_t ngx_http_v3_vars[] = { { ngx_string("quic"), NULL, ngx_http_variable_quic, 0, 0, 0 }, + { ngx_string("http3"), NULL, ngx_http_variable_http3, + 0, 0, 0 }, + ngx_http_null_variable }; @@ -167,6 +172,25 @@ ngx_http_variable_quic(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_variable_http3(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + v->data = ngx_pnalloc(r->pool, sizeof("h3-xx") - 1); + if (v->data == NULL) { + return NGX_ERROR; + } + + v->len = ngx_sprintf(v->data, "h3-%d", NGX_QUIC_DRAFT_VERSION) - v->data; + + return NGX_OK; +} + + static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf) { -- cgit v1.2.3 From c3b7927e24f0376b7eac63491ff5d869eec38cc0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 31 Mar 2020 13:13:12 +0300 Subject: Removed unused field from ngx_quic_header_t. --- src/event/ngx_event_quic_transport.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 3afe8688c..c12f6c66a 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -232,7 +232,6 @@ typedef struct { ngx_log_t *log; struct ngx_quic_secret_s *secret; - ngx_uint_t type; ngx_uint_t *number; uint8_t flags; uint32_t version; -- cgit v1.2.3 From 86029005a5a899dde91fe7bc5f1b527436580d18 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:41 +0300 Subject: Style. --- src/event/ngx_event_quic.c | 4 ++-- src/event/ngx_event_quic_protection.c | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 458484a5f..7231d64ad 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1058,10 +1058,10 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, c->quic->state = NGX_QUIC_ST_APPLICATION; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "handshake completed successfully"); + "handshake completed successfully"); #if (NGX_QUIC_DRAFT_VERSION >= 27) { diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index efbf5191f..10c94ff9b 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -53,9 +53,8 @@ static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, static ssize_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); - static ssize_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, - ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); + ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); static ngx_int_t @@ -622,7 +621,7 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, switch (level) { case ssl_encryption_handshake: - peer_secret= &qsec->hs; + peer_secret = &qsec->hs; break; case ssl_encryption_application: -- cgit v1.2.3 From e9d67086c76fd571e51cfbb4f098827619bd9e1a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:42 +0300 Subject: Improved SSL_do_handshake() error handling in QUIC. It can either return a recoverable SSL_ERROR_WANT_READ or fatal errors. --- src/event/ngx_event_quic.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7231d64ad..ae85b31d0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -508,6 +508,11 @@ ngx_quic_init_connection(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1050,8 +1055,9 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr == SSL_ERROR_SSL) { + if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; } } else if (n == 1) { -- cgit v1.2.3 From 833a28244fb3a950c93cb5af9001683aea6b30ca Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:42 +0300 Subject: QUIC packet padding to fulfil header protection sample demands. --- src/event/ngx_event_quic.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ae85b31d0..8c577514e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1369,6 +1369,11 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, out.len = p - out.data; + while (out.len < 4) { + *p++ = NGX_QUIC_FT_PADDING; + out.len++; + } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "packet ready: %ui bytes at level %d", out.len, start->level); -- cgit v1.2.3 From 22671b37e3720af223bb2e563a940eaefe28aeb7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:42 +0300 Subject: Sending HANDSHAKE_DONE just once with BoringSSL. If early data is accepted, SSL_do_handshake() completes as soon as ClientHello is processed. SSL_in_init() will report the handshake is still in progress. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8c577514e..be86cf3bc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1060,7 +1060,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - } else if (n == 1) { + } else if (n == 1 && !SSL_in_init(ssl_conn)) { c->quic->state = NGX_QUIC_ST_APPLICATION; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From 140a89ce0173599cd014507f73359dafa1cc44a9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:42 +0300 Subject: TLS Early Data key derivation support. --- src/event/ngx_event_quic.c | 7 ++++++- src/event/ngx_event_quic_protection.c | 4 ++++ src/event/ngx_event_quic_protection.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index be86cf3bc..98474b3dc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -195,7 +195,6 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); - ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, @@ -204,6 +203,12 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return rc; } + if (level == ssl_encryption_early_data) { + return 1; + } + + ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, wsecret, secret_len, &c->quic->secrets.server); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 10c94ff9b..ba846e63e 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -620,6 +620,10 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, switch (level) { + case ssl_encryption_early_data: + peer_secret = &qsec->ed; + break; + case ssl_encryption_handshake: peer_secret = &qsec->hs; break; diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index 2763375e4..cf9cd479d 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -18,6 +18,7 @@ typedef struct ngx_quic_secret_s { typedef struct { ngx_quic_secret_t in; + ngx_quic_secret_t ed; ngx_quic_secret_t hs; ngx_quic_secret_t ad; } ngx_quic_peer_secrets_t; -- cgit v1.2.3 From 6abff71fc443bb7a32692147c0f48c7e8a94ed64 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Apr 2020 13:27:42 +0300 Subject: TLS Early Data support. --- src/event/ngx_event_quic.c | 110 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 98474b3dc..fa2b52d21 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -12,6 +12,7 @@ typedef enum { NGX_QUIC_ST_INITIAL, /* connection just created */ NGX_QUIC_ST_HANDSHAKE, /* handshake started */ + NGX_QUIC_ST_EARLY_DATA, /* handshake in progress */ NGX_QUIC_ST_APPLICATION /* handshake complete */ } ngx_quic_state_t; @@ -82,7 +83,8 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_tp_t *tp, ngx_quic_header_t *pkt); + ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, + ngx_connection_handler_pt handler); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_close_connection(ngx_connection_t *c); @@ -92,6 +94,8 @@ static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, @@ -159,6 +163,10 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "level:%d read secret", rsecret, secret_len, level); + if (level == ssl_encryption_early_data) { + c->quic->state = NGX_QUIC_ST_EARLY_DATA; + } + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, &c->quic->secrets.client); @@ -204,6 +212,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } if (level == ssl_encryption_early_data) { + c->quic->state = NGX_QUIC_ST_EARLY_DATA; return 1; } @@ -352,14 +361,11 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, pkt.data = b->start; pkt.len = b->last - b->start; - if (ngx_quic_new_connection(c, ssl, tp, &pkt) != NGX_OK) { + if (ngx_quic_new_connection(c, ssl, tp, &pkt, handler) != NGX_OK) { ngx_quic_close_connection(c); return; } - // we don't need stream handler for initial packet processing - c->quic->streams.handler = handler; - ngx_add_timer(c->read, c->quic->tp.max_idle_timeout); c->read->handler = ngx_quic_input_handler; @@ -370,7 +376,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, - ngx_quic_header_t *pkt) + ngx_quic_header_t *pkt, ngx_connection_handler_pt handler) { ngx_quic_tp_t *ctp; ngx_quic_connection_t *qc; @@ -410,6 +416,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, c->quic = qc; qc->ssl = ssl; qc->tp = *tp; + qc->streams.handler = handler; ctp = &qc->ctp; ctp->max_packet_size = NGX_QUIC_DEFAULT_MAX_PACKET_SIZE; @@ -456,7 +463,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - return ngx_quic_payload_handler(c, pkt); + if (ngx_quic_payload_handler(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + /* pos is at header end, adjust by actual packet length */ + pkt->raw->pos += pkt->len; + + return ngx_quic_input(c, pkt->raw); } @@ -483,6 +497,12 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (SSL_CTX_get_max_early_data(qc->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp); /* always succeeds */ @@ -666,9 +686,9 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) ngx_int_t rc; ngx_quic_header_t pkt; - p = b->start; + p = b->pos; - do { + while (p < b->last) { c->log->action = "processing quic packet"; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -693,6 +713,9 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) } else if (ngx_quic_pkt_hs(pkt.flags)) { rc = ngx_quic_handshake_input(c, &pkt); + } else if (ngx_quic_pkt_zrtt(pkt.flags)) { + rc = ngx_quic_early_input(c, &pkt); + } else { ngx_log_error(NGX_LOG_INFO, c->log, 0, "BUG: unknown quic state"); @@ -710,8 +733,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) /* b->pos is at header end, adjust by actual packet length */ p = b->pos + pkt.len; b->pos = p; /* reset b->pos to the next packet start */ - - } while (p < b->last); + } return NGX_OK; } @@ -806,6 +828,68 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + + c->log->action = "processing early data quic packet"; + + qc = c->quic; + + /* extract cleartext data into pkt */ + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (pkt->dcid.len != qc->dcid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); + return NGX_ERROR; + } + + if (pkt->scid.len != qc->scid.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); + return NGX_ERROR; + } + + if (!ngx_quic_pkt_zrtt(pkt->flags)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid packet type: 0x%xi", pkt->flags); + return NGX_ERROR; + } + + if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (c->quic->state != NGX_QUIC_ST_EARLY_DATA) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected 0-RTT packet"); + return NGX_OK; + } + + pkt->secret = &qc->secrets.client.ed; + pkt->level = ssl_encryption_early_data; + pkt->plaintext = buf; + + if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_quic_payload_handler(c, pkt); +} + + static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -1000,7 +1084,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_frame->level = pkt->level; + ack_frame->level = (pkt->level == ssl_encryption_early_data) + ? ssl_encryption_application + : pkt->level; ack_frame->type = NGX_QUIC_FT_ACK; ack_frame->u.ack.pn = pkt->pn; -- cgit v1.2.3 From c6859361e3ce423aee254eedcaaec6df381db1da Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Apr 2020 17:21:52 +0300 Subject: Added missing debug description. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index fa2b52d21..d41920a4c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -330,6 +330,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, frame->level = level; frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; frame->u.close.error_code = 0x100 + alert; + ngx_sprintf(frame->info, "cc from send_alert level=%d", frame->level); ngx_quic_queue_frame(c->quic, frame); -- cgit v1.2.3 From 95954173969e185c03e784b658e941986ea85a95 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Apr 2020 14:25:25 +0300 Subject: Refactored QUIC secrets storage. The quic->keys[4] array now contains secrets related to the corresponding encryption level. All protection-level functions get proper keys and do not need to switch manually between levels. --- src/event/ngx_event_quic.c | 79 ++++++++++++++++++++++------------- src/event/ngx_event_quic_protection.c | 73 +++++++++++++------------------- src/event/ngx_event_quic_protection.h | 20 ++++----- 3 files changed, 87 insertions(+), 85 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d41920a4c..fc9499666 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -42,7 +42,9 @@ struct ngx_quic_connection_s { ngx_uint_t handshake_pn; ngx_uint_t appdata_pn; - ngx_quic_secrets_t secrets; + ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; + uint64_t crypto_offset[NGX_QUIC_ENCRYPTION_LAST]; + ngx_ssl_t *ssl; ngx_quic_frame_t *frames; ngx_quic_frame_t *free_frames; @@ -56,9 +58,6 @@ struct ngx_quic_connection_s { unsigned send_timer_set:1; unsigned closing:1; - -#define SSL_ECRYPTION_LAST ((ssl_encryption_application) + 1) - uint64_t crypto_offset[SSL_ECRYPTION_LAST]; }; @@ -156,20 +155,23 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *rsecret, size_t secret_len) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_secrets_t *keys; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d read secret", rsecret, secret_len, level); + keys = &c->quic->keys[level]; + if (level == ssl_encryption_early_data) { c->quic->state = NGX_QUIC_ST_EARLY_DATA; } return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, - &c->quic->secrets.client); + &keys->client); } @@ -178,16 +180,19 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *wsecret, size_t secret_len) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_secrets_t *keys; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d write secret", wsecret, secret_len, level); + keys = &c->quic->keys[level]; + return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, wsecret, secret_len, - &c->quic->secrets.server); + &keys->server); } #else @@ -197,16 +202,19 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *rsecret, const uint8_t *wsecret, size_t secret_len) { - ngx_int_t rc; - ngx_connection_t *c; + ngx_int_t rc; + ngx_connection_t *c; + ngx_quic_secrets_t *keys; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); + keys = &c->quic->secrets[level]; + rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, - &c->quic->secrets.client); + &keys->client); if (rc != 1) { return rc; } @@ -220,7 +228,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, wsecret, secret_len, - &c->quic->secrets.server); + &keys->server); } #endif @@ -380,6 +388,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler) { ngx_quic_tp_t *ctp; + ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -445,14 +454,16 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, } ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); + keys = &c->quic->keys[ssl_encryption_initial]; - if (ngx_quic_set_initial_secret(c->pool, &qc->secrets, &qc->dcid) + if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, + &qc->dcid) != NGX_OK) { return NGX_ERROR; } - pkt->secret = &qc->secrets.client.in; + pkt->secret = &keys->client; pkt->level = ssl_encryption_initial; pkt->plaintext = buf; @@ -743,13 +754,12 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_secrets_t *keys; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; c->log->action = "processing initial quic packet"; - qc = c->quic; ssl_conn = c->ssl->connection; if (ngx_quic_parse_long_header(pkt) != NGX_OK) { @@ -760,7 +770,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - pkt->secret = &qc->secrets.client.in; + keys = &c->quic->keys[ssl_encryption_initial]; + + pkt->secret = &keys->client; pkt->level = ssl_encryption_initial; pkt->plaintext = buf; @@ -775,6 +787,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -817,7 +830,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - pkt->secret = &qc->secrets.client.hs; + keys = &c->quic->keys[ssl_encryption_handshake]; + + pkt->secret = &keys->client; pkt->level = ssl_encryption_handshake; pkt->plaintext = buf; @@ -832,6 +847,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -879,7 +895,9 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_OK; } - pkt->secret = &qc->secrets.client.ed; + keys = &c->quic->keys[ssl_encryption_early_data]; + + pkt->secret = &keys->client; pkt->level = ssl_encryption_early_data; pkt->plaintext = buf; @@ -894,6 +912,7 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -901,7 +920,9 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; - if (qc->secrets.client.ad.key.len == 0) { + keys = &c->quic->keys[ssl_encryption_application]; + + if (keys->client.key.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "no read keys yet, packet ignored"); return NGX_DECLINED; @@ -911,7 +932,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - pkt->secret = &qc->secrets.client.ad; + pkt->secret = &keys->client; pkt->level = ssl_encryption_application; pkt->plaintext = buf; @@ -1085,9 +1106,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_frame->level = (pkt->level == ssl_encryption_early_data) - ? ssl_encryption_application - : pkt->level; + ack_frame->level = pkt->level; ack_frame->type = NGX_QUIC_FT_ACK; ack_frame->u.ack.pn = pkt->pn; @@ -1434,6 +1453,7 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_str_t out, res; ngx_quic_frame_t *f; ngx_quic_header_t pkt; + ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static ngx_str_t initial_token = ngx_null_string; static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -1472,20 +1492,21 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, qc = c->quic; + keys = &c->quic->keys[start->level]; + + pkt.secret = &keys->server; + if (start->level == ssl_encryption_initial) { pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; - pkt.secret = &qc->secrets.server.in; pkt.token = initial_token; } else if (start->level == ssl_encryption_handshake) { pkt.number = &qc->handshake_pn; pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - pkt.secret = &qc->secrets.server.hs; } else { pkt.number = &qc->appdata_pn; - pkt.secret = &qc->secrets.server.ad; } pkt.log = c->log; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index ba846e63e..34289d856 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -118,8 +118,8 @@ ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, ngx_int_t -ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secrets_t *qsec, - ngx_str_t *secret) +ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, + ngx_quic_secret_t *server, ngx_str_t *secret) { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -152,17 +152,17 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secrets_t *qsec, ngx_quic_hexdump0(pool->log, "initial secret", is, is_len); /* draft-ietf-quic-tls-23#section-5.2 */ - qsec->client.in.secret.len = SHA256_DIGEST_LENGTH; - qsec->server.in.secret.len = SHA256_DIGEST_LENGTH; + client->secret.len = SHA256_DIGEST_LENGTH; + server->secret.len = SHA256_DIGEST_LENGTH; - qsec->client.in.key.len = EVP_CIPHER_key_length(cipher); - qsec->server.in.key.len = EVP_CIPHER_key_length(cipher); + client->key.len = EVP_CIPHER_key_length(cipher); + server->key.len = EVP_CIPHER_key_length(cipher); - qsec->client.in.hp.len = EVP_CIPHER_key_length(cipher); - qsec->server.in.hp.len = EVP_CIPHER_key_length(cipher); + client->hp.len = EVP_CIPHER_key_length(cipher); + server->hp.len = EVP_CIPHER_key_length(cipher); - qsec->client.in.iv.len = EVP_CIPHER_iv_length(cipher); - qsec->server.in.iv.len = EVP_CIPHER_iv_length(cipher); + client->iv.len = EVP_CIPHER_iv_length(cipher); + server->iv.len = EVP_CIPHER_iv_length(cipher); struct { ngx_str_t label; @@ -171,40 +171,40 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secrets_t *qsec, } seq[] = { /* draft-ietf-quic-tls-23#section-5.2 */ - { ngx_string("tls13 client in"), &qsec->client.in.secret, &iss }, + { ngx_string("tls13 client in"), &client->secret, &iss }, { ngx_string("tls13 quic key"), - &qsec->client.in.key, - &qsec->client.in.secret, + &client->key, + &client->secret, }, { ngx_string("tls13 quic iv"), - &qsec->client.in.iv, - &qsec->client.in.secret, + &client->iv, + &client->secret, }, { /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), - &qsec->client.in.hp, - &qsec->client.in.secret, + &client->hp, + &client->secret, }, - { ngx_string("tls13 server in"), &qsec->server.in.secret, &iss }, + { ngx_string("tls13 server in"), &server->secret, &iss }, { /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ ngx_string("tls13 quic key"), - &qsec->server.in.key, - &qsec->server.in.secret, + &server->key, + &server->secret, }, { ngx_string("tls13 quic iv"), - &qsec->server.in.iv, - &qsec->server.in.secret, + &server->iv, + &server->secret, }, { /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), - &qsec->server.in.hp, - &qsec->server.in.secret, + &server->hp, + &server->secret, }, }; @@ -604,12 +604,11 @@ failed: int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *secret, - size_t secret_len, ngx_quic_peer_secrets_t *qsec) + size_t secret_len, ngx_quic_secret_t *peer_secret) { - ngx_int_t key_len; - ngx_uint_t i; - ngx_quic_secret_t *peer_secret; - ngx_quic_ciphers_t ciphers; + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_ciphers_t ciphers; key_len = ngx_quic_ciphers(ssl_conn, &ciphers, level); @@ -618,21 +617,7 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, return 0; } - switch (level) { - - case ssl_encryption_early_data: - peer_secret = &qsec->ed; - break; - - case ssl_encryption_handshake: - peer_secret = &qsec->hs; - break; - - case ssl_encryption_application: - peer_secret = &qsec->ad; - break; - - default: + if (level == ssl_encryption_initial) { return 0; } diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index cf9cd479d..c9473f575 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -8,6 +8,9 @@ #define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) + + typedef struct ngx_quic_secret_s { ngx_str_t secret; ngx_str_t key; @@ -17,25 +20,18 @@ typedef struct ngx_quic_secret_s { typedef struct { - ngx_quic_secret_t in; - ngx_quic_secret_t ed; - ngx_quic_secret_t hs; - ngx_quic_secret_t ad; -} ngx_quic_peer_secrets_t; - - -typedef struct { - ngx_quic_peer_secrets_t client; - ngx_quic_peer_secrets_t server; + ngx_quic_secret_t client; + ngx_quic_secret_t server; } ngx_quic_secrets_t; ngx_int_t ngx_quic_set_initial_secret(ngx_pool_t *pool, - ngx_quic_secrets_t *secrets, ngx_str_t *secret); + ngx_quic_secret_t *client, ngx_quic_secret_t *server, + ngx_str_t *secret); int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len, - ngx_quic_peer_secrets_t *qsec); + ngx_quic_secret_t *peer_secret); ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); -- cgit v1.2.3 From d7eeb2e30b4e90739d5784dd5369834fc7d390f6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Apr 2020 14:31:08 +0300 Subject: Introduced packet namespace in QUIC connection. The structure contains all data that is related to the namespace: packet number and output queue (next patch). --- src/event/ngx_event_quic.c | 45 +++++++++++++++++++++++++---------- src/event/ngx_event_quic_protection.c | 4 ++-- src/event/ngx_event_quic_transport.c | 4 ++-- src/event/ngx_event_quic_transport.h | 2 +- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index fc9499666..f3d46a590 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -9,6 +9,20 @@ #include +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_ns(level) \ + ((level) == ssl_encryption_initial) ? 0 \ + : (((level) == ssl_encryption_handshake) ? 1 : 2) + +#define NGX_QUIC_NAMESPACE_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + + typedef enum { NGX_QUIC_ST_INITIAL, /* connection just created */ NGX_QUIC_ST_HANDSHAKE, /* handshake started */ @@ -26,6 +40,14 @@ typedef struct { } ngx_quic_streams_t; +typedef struct { + ngx_quic_secret_t client_secret; + ngx_quic_secret_t server_secret; + + ngx_uint_t pnum; +} ngx_quic_namespace_t; + + struct ngx_quic_connection_s { ngx_str_t scid; ngx_str_t dcid; @@ -37,11 +59,7 @@ struct ngx_quic_connection_s { ngx_quic_state_t state; - /* current packet numbers for each namespace */ - ngx_uint_t initial_pn; - ngx_uint_t handshake_pn; - ngx_uint_t appdata_pn; - + ngx_quic_namespace_t ns[NGX_QUIC_NAMESPACE_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; uint64_t crypto_offset[NGX_QUIC_ENCRYPTION_LAST]; @@ -1106,11 +1124,14 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_frame->level = pkt->level; + ack_frame->level = (pkt->level == ssl_encryption_early_data) + ? ssl_encryption_application + : pkt->level; + ack_frame->type = NGX_QUIC_FT_ACK; ack_frame->u.ack.pn = pkt->pn; - ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, pkt->level); + ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level); ngx_quic_queue_frame(qc, ack_frame); return ngx_quic_output(c); @@ -1454,6 +1475,7 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_quic_frame_t *f; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; + ngx_quic_namespace_t *ns; ngx_quic_connection_t *qc; static ngx_str_t initial_token = ngx_null_string; static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -1493,20 +1515,17 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, qc = c->quic; keys = &c->quic->keys[start->level]; + ns = &c->quic->ns[ngx_quic_ns(start->level)]; pkt.secret = &keys->server; + pkt.number = ns->pnum; if (start->level == ssl_encryption_initial) { - pkt.number = &qc->initial_pn; pkt.flags = NGX_QUIC_PKT_INITIAL; pkt.token = initial_token; } else if (start->level == ssl_encryption_handshake) { - pkt.number = &qc->handshake_pn; pkt.flags = NGX_QUIC_PKT_HANDSHAKE; - - } else { - pkt.number = &qc->appdata_pn; } pkt.log = c->log; @@ -1525,7 +1544,7 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, c->send(c, res.data, res.len); // TODO: err handling - (*pkt.number)++; + ns->pnum++; return NGX_OK; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 34289d856..bf831109d 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -673,7 +673,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); - pn = *pkt->number; + pn = pkt->number; nonce[11] ^= pn; ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); @@ -731,7 +731,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (pkt->level == ssl_encryption_handshake || pkt->level == ssl_encryption_application) { - nonce[11] ^= *pkt->number; + nonce[11] ^= pkt->number; } ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 467a41157..bfa3867ea 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -366,7 +366,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, *pnp = p; - *p++ = (uint64_t) (*pkt->number); + *p++ = pkt->number; // XXX: uint64 return p - start; } @@ -386,7 +386,7 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, *pnp = p; - *p++ = (*pkt->number); + *p++ = pkt->number; // XXX: uint64 return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index c12f6c66a..8268d0410 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -232,7 +232,7 @@ typedef struct { ngx_log_t *log; struct ngx_quic_secret_s *secret; - ngx_uint_t *number; + uint64_t number; uint8_t flags; uint32_t version; ngx_str_t token; -- cgit v1.2.3 From 7b1a3df37c97821a4378c4341be9db6d7d95bba0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Apr 2020 17:06:26 +0300 Subject: Implemented retransmission and retransmit queue. All frames collected to packet are moved into a per-namespace send queue. QUIC connection has a timer which fires on the closest max_ack_delay time. The frame is deleted from the queue when a corresponding packet is acknowledged. The NGX_QUIC_MAX_RETRANSMISSION is a timeout that defines maximum length of retransmission of a frame. --- src/event/ngx_event_quic.c | 396 +++++++++++++++++++++++++++++------ src/event/ngx_event_quic_transport.c | 20 +- src/event/ngx_event_quic_transport.h | 14 +- 3 files changed, 356 insertions(+), 74 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f3d46a590..fd704ae36 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -44,7 +44,11 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; - ngx_uint_t pnum; + uint64_t pnum; + uint64_t largest; + + ngx_queue_t frames; + ngx_queue_t sent; } ngx_quic_namespace_t; @@ -64,8 +68,9 @@ struct ngx_quic_connection_s { uint64_t crypto_offset[NGX_QUIC_ENCRYPTION_LAST]; ngx_ssl_t *ssl; - ngx_quic_frame_t *frames; - ngx_quic_frame_t *free_frames; + + ngx_event_t retry; + ngx_queue_t free_frames; #if (NGX_DEBUG) ngx_uint_t nframes; @@ -133,8 +138,13 @@ static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_output(ngx_connection_t *c); -ngx_int_t ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, - ngx_quic_frame_t *end, size_t total); +static ngx_int_t ngx_quic_output_ns(ngx_connection_t *c, + ngx_quic_namespace_t *ns, ngx_uint_t nsi); +static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); +static void ngx_quic_retransmit_handler(ngx_event_t *ev); +static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c, + ngx_quic_namespace_t *ns, ngx_msec_t *waitp); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); @@ -405,6 +415,7 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler) { + ngx_uint_t i; ngx_quic_tp_t *ctp; ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; @@ -441,6 +452,18 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); + for (i = 0; i < 3; i++) { + ngx_queue_init(&qc->ns[i].frames); + ngx_queue_init(&qc->ns[i].sent); + } + + ngx_queue_init(&qc->free_frames); + + qc->retry.log = c->log; + qc->retry.data = c; + qc->retry.handler = ngx_quic_retransmit_handler; + qc->retry.cancelable = 1; + c->quic = qc; qc->ssl = ssl; qc->tp = *tp; @@ -689,6 +712,10 @@ ngx_quic_close_connection(ngx_connection_t *c) qc->closing = 1; return; } + + if (qc->retry.timer_set) { + ngx_del_timer(&qc->retry); + } } if (c->ssl) { @@ -1129,7 +1156,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) : pkt->level; ack_frame->type = NGX_QUIC_FT_ACK; - ack_frame->u.ack.pn = pkt->pn; + ack_frame->u.ack.largest = pkt->pn; + /* only ack immediate packet ]*/ + ack_frame->u.ack.first_range = 0; ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level); ngx_quic_queue_frame(qc, ack_frame); @@ -1140,9 +1169,66 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_ack_frame_t *f) + ngx_quic_ack_frame_t *ack) { - /* TODO: handle ACK here */ + ngx_uint_t found, min; + ngx_queue_t *q, range; + ngx_quic_frame_t *f; + ngx_quic_namespace_t *ns; + + ns = &c->quic->ns[ngx_quic_ns(pkt->level)]; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_handle_ack_frame in namespace %d", + ngx_quic_ns(pkt->level)); + + if (ack->first_range > ack->largest) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + + found = 0; + + ngx_queue_init(&range); + + q = ngx_queue_head(&ns->sent); + + while (q != ngx_queue_sentinel(&ns->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum >= min && f->pnum <= ack->largest) { + q = ngx_queue_next(q); + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + + } else { + q = ngx_queue_next(q); + } + } + + if (!found) { + + if (ack->largest <= ns->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "ACK for the packet not in sent queue "); + // TODO: handle error properly: PROTOCOL VIOLATION? + return NGX_ERROR; + } + + /* 13.2.3. Receiver Tracking of ACK Frames */ + if (ns->largest < ack->largest) { + ack->largest = ns->largest; + } + return NGX_OK; } @@ -1380,99 +1466,146 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - ngx_quic_frame_t **f; + ngx_quic_namespace_t *ns; - for (f = &qc->frames; *f; f = &(*f)->next) { - if ((*f)->level > frame->level) { - break; + ns = &qc->ns[ngx_quic_ns(frame->level)]; + + ngx_queue_insert_tail(&ns->frames, &frame->queue); +} + + +static ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_quic_namespace_t *ns; + ngx_quic_connection_t *qc; + + c->log->action = "sending frames"; + + qc = c->quic; + + for (i = 0; i < 3; i++) { + ns = &qc->ns[i]; + if (ngx_quic_output_ns(c, ns, i) != NGX_OK) { + return NGX_ERROR; } } - frame->next = *f; - *f = frame; + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + if (!qc->retry.timer_set && !qc->closing) { + ngx_add_timer(&qc->retry, qc->tp.max_ack_delay * 1000); + } + + return NGX_OK; } static ngx_int_t -ngx_quic_output(ngx_connection_t *c) +ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, + ngx_uint_t nsi) { size_t len, hlen, n; - ngx_uint_t lvl; - ngx_quic_frame_t *f, *start, *next; + ngx_int_t rc; + ngx_queue_t *q, range; + ngx_quic_frame_t *f; ngx_quic_connection_t *qc; qc = c->quic; - if (qc->frames == NULL) { + if (ngx_queue_empty(&ns->frames)) { return NGX_OK; } - c->log->action = "sending frames"; + hlen = (nsi == 2) ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; - lvl = qc->frames->level; - start = qc->frames; - f = start; + hlen += EVP_GCM_TLS_TAG_LEN; + + q = ngx_queue_head(&ns->frames); do { len = 0; - - hlen = (lvl == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; - hlen += EVP_GCM_TLS_TAG_LEN; + ngx_queue_init(&range); do { - /* process same-level group of frames */ + /* process group of frames that fits into packet */ + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - n = ngx_quic_create_frame(NULL, NULL, f); + n = ngx_quic_create_frame(NULL, f); if (len && hlen + len + n > qc->ctp.max_packet_size) { break; } + q = ngx_queue_next(q); + + f->first = ngx_current_msec; + + ngx_queue_remove(&f->queue); + ngx_queue_insert_tail(&range, &f->queue); + len += n; - f = f->next; - } while (f && f->level == lvl); + } while (q != ngx_queue_sentinel(&ns->frames)); + + rc = ngx_quic_send_frames(c, &range); + + if (rc == NGX_OK) { + /* + * frames are moved into the sent queue + * to wait for ack/be retransmitted + */ + ngx_queue_add(&ns->sent, &range); + + } else if (rc == NGX_DONE) { + /* no ack is expected for this frames, can free them */ + ngx_quic_free_frames(c, &range); - if (ngx_quic_frames_send(c, start, f, len) != NGX_OK) { + } else { return NGX_ERROR; } - while (start != f) { - next = start->next; - ngx_quic_free_frame(c, start); - start = next; - } - if (f == NULL) { - break; - } + } while (q != ngx_queue_sentinel(&ns->frames)); - lvl = f->level; // TODO: must not decrease (ever, also between calls) + return NGX_OK; +} - } while (1); - qc->frames = NULL; +static void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; - if (!qc->send_timer_set) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - } + q = ngx_queue_head(frames); - return NGX_OK; + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_next(q); + + ngx_quic_free_frame(c, f); + + } while (q != ngx_queue_sentinel(frames)); } /* pack a group of frames [start; end) into memory p and send as single packet */ -ngx_int_t -ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, - ngx_quic_frame_t *end, size_t total) +static ngx_int_t +ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) { ssize_t len; u_char *p; + ngx_msec_t now; ngx_str_t out, res; - ngx_quic_frame_t *f; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; ngx_quic_namespace_t *ns; @@ -1481,24 +1614,41 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; static u_char dst[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sending frames %p...%p", start, end); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_send_frames"); + + q = ngx_queue_head(frames); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ns = &c->quic->ns[ngx_quic_ns(start->level)]; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); p = src; out.data = src; - for (f = start; f != end; f = f->next) { + for (q = ngx_queue_head(frames); + q != ngx_queue_sentinel(frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); - len = ngx_quic_create_frame(p, p + total, f); + len = ngx_quic_create_frame(p, f); if (len == -1) { return NGX_ERROR; } p += len; + f->pnum = ns->pnum; + } + + if (start->level == ssl_encryption_initial) { + /* ack will not be sent in initial packets due to initial keys being + * discarded when handshake start. + * Thus consider initial packets as non-ack-eliciting + */ + pkt.need_ack = 0; } out.len = p - out.data; @@ -1508,14 +1658,13 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, out.len++; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "packet ready: %ui bytes at level %d", - out.len, start->level); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "packet ready: %ui bytes at level %d need_ack: %ui", + out.len, start->level, pkt.need_ack); qc = c->quic; keys = &c->quic->keys[start->level]; - ns = &c->quic->ns[ngx_quic_ns(start->level)]; pkt.secret = &keys->server; pkt.number = ns->pnum; @@ -1542,10 +1691,124 @@ ngx_quic_frames_send(ngx_connection_t *c, ngx_quic_frame_t *start, ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); - c->send(c, res.data, res.len); // TODO: err handling + len = c->send(c, res.data, res.len); + if (len == NGX_ERROR || (size_t) len != res.len) { + return NGX_ERROR; + } + /* len == NGX_OK || NGX_AGAIN */ ns->pnum++; + now = ngx_current_msec; + start->last = now; + + return pkt.need_ack ? NGX_OK : NGX_DONE; +} + + +static void +ngx_quic_retransmit_handler(ngx_event_t *ev) +{ + ngx_uint_t i; + ngx_msec_t wait, nswait; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "retransmit timer"); + + c = ev->data; + qc = c->quic; + + wait = 0; + + for (i = 0; i < NGX_QUIC_NAMESPACE_LAST; i++) { + if (ngx_quic_retransmit_ns(c, &qc->ns[i], &nswait) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } + + if (i == 0) { + wait = nswait; + + } else if (nswait > 0 && nswait < wait) { + wait = nswait; + } + } + + if (wait > 0) { + ngx_add_timer(&qc->retry, wait); + } +} + + +static ngx_int_t +ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, + ngx_msec_t *waitp) +{ + uint64_t pn; + ngx_msec_t now, wait; + ngx_queue_t *q, range; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; + + qc = c->quic; + + now = ngx_current_msec; + wait = 0; + + if (ngx_queue_empty(&ns->sent)) { + *waitp = 0; + return NGX_OK; + } + + q = ngx_queue_head(&ns->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + pn = start->pnum; + f = start; + + do { + ngx_queue_init(&range); + + /* send frames with same packet number to the wire */ + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->first + qc->tp.max_idle_timeout < now) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "retransmission timeout"); + return NGX_DECLINED; + } + + if (f->pnum != pn) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + ngx_queue_insert_tail(&range, &f->queue); + + } while (q != ngx_queue_sentinel(&ns->sent)); + + wait = start->last + qc->tp.max_ack_delay - now; + + if ((ngx_msec_int_t) wait > 0) { + break; + } + + /* NGX_DONE is impossible here, such frames don't get into this queue */ + if (ngx_quic_send_frames(c, &range) != NGX_OK) { + return NGX_ERROR; + } + + /* move frames group to the end of queue */ + ngx_queue_add(&ns->sent, &range); + + } while (q != ngx_queue_sentinel(&ns->sent)); + + *waitp = wait; + return NGX_OK; } @@ -1903,6 +2166,7 @@ static ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) { u_char *p; + ngx_queue_t *q; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -1917,10 +2181,13 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) } qc = c->quic; - frame = qc->free_frames; - if (frame) { - qc->free_frames = frame->next; + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "reuse quic frame n:%ui", qc->nframes); @@ -1959,8 +2226,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) ngx_free(frame->data); } - frame->next = qc->free_frames; - qc->free_frames = frame; + ngx_queue_insert_head(&qc->free_frames, &frame->queue); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "free quic frame n:%ui", qc->nframes); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index bfa3867ea..115ba3fe7 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1080,12 +1080,19 @@ not_allowed: ssize_t -ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) +ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) { - // TODO: handle end arg + /* + * QUIC-recovery, section 2: + * + * Ack-eliciting Frames: All frames other than ACK, PADDING, and + * CONNECTION_CLOSE are considered ack-eliciting. + */ + f->need_ack = 1; switch (f->type) { case NGX_QUIC_FT_ACK: + f->need_ack = 0; return ngx_quic_create_ack(p, &f->u.ack); case NGX_QUIC_FT_CRYPTO: @@ -1105,6 +1112,7 @@ ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f) return ngx_quic_create_stream(p, &f->u.stream); case NGX_QUIC_FT_CONNECTION_CLOSE: + f->need_ack = 0; return ngx_quic_create_close(p, &f->u.close); case NGX_QUIC_FT_MAX_STREAMS: @@ -1130,10 +1138,10 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); - len += ngx_quic_varint_len(ack->pn); - len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->largest); len += ngx_quic_varint_len(0); len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->first_range); return len; } @@ -1141,10 +1149,10 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) start = p; ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); - ngx_quic_build_int(&p, ack->pn); - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->largest); ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->first_range); return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 8268d0410..b96519e5a 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -97,7 +97,6 @@ typedef struct { - ngx_uint_t pn; uint64_t largest; uint64_t delay; uint64_t range_count; @@ -204,7 +203,13 @@ typedef struct ngx_quic_frame_s ngx_quic_frame_t; struct ngx_quic_frame_s { ngx_uint_t type; enum ssl_encryption_level_t level; - ngx_quic_frame_t *next; + ngx_queue_t queue; + uint64_t pnum; + ngx_msec_t first; + ngx_msec_t last; + ngx_uint_t need_ack; + /* unsigned need_ack:1; */ + u_char *data; union { ngx_quic_ack_frame_t ack; @@ -250,6 +255,9 @@ typedef struct { uint64_t pn; u_char *plaintext; ngx_str_t payload; /* decrypted data */ + + ngx_uint_t need_ack; + /* unsigned need_ack:1; */ } ngx_quic_header_t; @@ -269,7 +277,7 @@ ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); -ssize_t ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f); +ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log); -- cgit v1.2.3 From 01bddf4533d02338d560de9d4445196a337d8302 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Apr 2020 17:09:11 +0300 Subject: Output buffering. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the output is called periodically, each 200 ms to invoke ngx_quic_output() that will push all pending frames into packets. TODO: implement flags a-là Nagle & co (NO_DELAY/NO_PUSH...) --- src/event/ngx_event_quic.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index fd704ae36..4b50bfe01 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -69,6 +69,7 @@ struct ngx_quic_connection_s { ngx_ssl_t *ssl; + ngx_event_t push; ngx_event_t retry; ngx_queue_t free_frames; @@ -145,6 +146,7 @@ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); static void ngx_quic_retransmit_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, ngx_msec_t *waitp); +static void ngx_quic_push_handler(ngx_event_t *ev); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); @@ -464,6 +466,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->retry.handler = ngx_quic_retransmit_handler; qc->retry.cancelable = 1; + qc->push.log = c->log; + qc->push.data = c; + qc->push.handler = ngx_quic_push_handler; + qc->push.cancelable = 1; + c->quic = qc; qc->ssl = ssl; qc->tp = *tp; @@ -713,6 +720,10 @@ ngx_quic_close_connection(ngx_connection_t *c) return; } + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + if (qc->retry.timer_set) { ngx_del_timer(&qc->retry); } @@ -1163,7 +1174,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level); ngx_quic_queue_frame(qc, ack_frame); - return ngx_quic_output(c); + // TODO: call output() after processing some special frames? + return NGX_OK; } @@ -1471,6 +1483,12 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) ns = &qc->ns[ngx_quic_ns(frame->level)]; ngx_queue_insert_tail(&ns->frames, &frame->queue); + + /* TODO: check PUSH flag on stream and call output */ + + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, qc->tp.max_ack_delay); + } } @@ -1742,6 +1760,26 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) } +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "push timer"); + + c = ev->data; + qc = c->quic; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c); + return; + } + + ngx_add_timer(&qc->push, qc->tp.max_ack_delay); +} + + static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, ngx_msec_t *waitp) @@ -2060,8 +2098,6 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(qc, frame); - ngx_quic_output(pc); - return size; } -- cgit v1.2.3 From 9c8a7a52e1346b183b06bf7b86c69084c428580f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 2 Apr 2020 11:40:25 +0300 Subject: Fixed computing nonce by xoring all packet number bytes. Previously, the stub worked only with pnl=0. --- src/event/ngx_event_quic_protection.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index bf831109d..cdab7fcd4 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -37,6 +37,7 @@ static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, const u_char *salt, size_t salt_len); static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); +static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); @@ -654,7 +655,6 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) { u_char *pnp, *sample; - uint64_t pn; ngx_str_t ad, out; ngx_quic_ciphers_t ciphers; u_char nonce[12], mask[16]; @@ -673,8 +673,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); - pn = pkt->number; - nonce[11] ^= pn; + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); @@ -728,11 +727,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); - if (pkt->level == ssl_encryption_handshake - || pkt->level == ssl_encryption_application) - { - nonce[11] ^= pkt->number; - } + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); @@ -789,6 +784,16 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) } +static void +ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) +{ + nonce[len - 4] ^= pn & 0xff000000; + nonce[len - 3] ^= pn & 0x00ff0000; + nonce[len - 2] ^= pn & 0x0000ff00; + nonce[len - 1] ^= pn & 0x000000ff; +} + + ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) @@ -875,7 +880,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) } while (--pnl); ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); - nonce[11] ^= pn; + ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); -- cgit v1.2.3 From 932bfe7b360cd5e25491105fcc10809cd6c6645f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 2 Apr 2020 14:53:01 +0300 Subject: Fixed excessive push timer firing. The timer is set when an output frame is generated; there is no need to arm it after it was fired. --- src/event/ngx_event_quic.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4b50bfe01..3c1097455 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1764,19 +1764,15 @@ static void ngx_quic_push_handler(ngx_event_t *ev) { ngx_connection_t *c; - ngx_quic_connection_t *qc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "push timer"); c = ev->data; - qc = c->quic; if (ngx_quic_output(c) != NGX_OK) { ngx_quic_close_connection(c); return; } - - ngx_add_timer(&qc->push, qc->tp.max_ack_delay); } -- cgit v1.2.3 From 723c276a7bb97fe8c4bd17839d5eb34ea2683e97 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 3 Apr 2020 09:53:51 +0300 Subject: Fixed missing propagation of need_ack flag from frames to packet. --- src/event/ngx_event_quic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3c1097455..9cb26261e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1657,6 +1657,10 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) return NGX_ERROR; } + if (f->need_ack) { + pkt.need_ack = 1; + } + p += len; f->pnum = ns->pnum; } -- cgit v1.2.3 From 569da72e4b46d6701cfc8f2e4eb985c55cffb44e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 3 Apr 2020 13:49:40 +0300 Subject: Fixed computing nonce again, by properly shifting packet number. --- src/event/ngx_event_quic_protection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index cdab7fcd4..b4c6ef3f3 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -787,10 +787,10 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) { - nonce[len - 4] ^= pn & 0xff000000; - nonce[len - 3] ^= pn & 0x00ff0000; - nonce[len - 2] ^= pn & 0x0000ff00; - nonce[len - 1] ^= pn & 0x000000ff; + nonce[len - 4] ^= (pn & 0xff000000) >> 24; + nonce[len - 3] ^= (pn & 0x00ff0000) >> 16; + nonce[len - 2] ^= (pn & 0x0000ff00) >> 8; + nonce[len - 1] ^= (pn & 0x000000ff); } -- cgit v1.2.3 From b4719e8ea44c5704b4b7d6ea815ee67bb1da5290 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 3 Apr 2020 13:49:44 +0300 Subject: Advertizing MAX_STREAMS (0x12) credit in advance. This makes sending large number of bidirectional stream work within ngtcp2, which doesn't bother sending optional STREAMS_BLOCKED when exhausted. This also introduces tracking currently opened and maximum allowed streams. --- src/event/ngx_event_quic.c | 53 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9cb26261e..4358f2972 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -22,6 +22,9 @@ #define NGX_QUIC_NAMESPACE_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) +#define NGX_QUIC_STREAMS_INC 16 +#define NGX_QUIC_STREAMS_LIMIT (1ULL < 60) + typedef enum { NGX_QUIC_ST_INITIAL, /* connection just created */ @@ -80,6 +83,9 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_uint_t max_data; + uint64_t cur_streams; + uint64_t max_streams; + unsigned send_timer_set:1; unsigned closing:1; }; @@ -130,6 +136,7 @@ static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); +static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, @@ -582,6 +589,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } + qc->max_streams = qc->tp.initial_max_streams_bidi; qc->state = NGX_QUIC_ST_HANDSHAKE; n = SSL_do_handshake(ssl_conn); @@ -1400,12 +1408,53 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, rev->pending_eof = 1; } + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + ngx_quic_handle_max_streams(c); + } + qc->streams.handler(sn->c); return NGX_OK; } +static ngx_int_t +ngx_quic_handle_max_streams(ngx_connection_t *c) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = c->quic; + qc->cur_streams++; + + if (qc->cur_streams + NGX_QUIC_STREAMS_INC / 2 < qc->max_streams) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + qc->max_streams = ngx_max(qc->max_streams + NGX_QUIC_STREAMS_INC, + NGX_QUIC_STREAMS_LIMIT); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + frame->u.max_streams.limit = qc->max_streams; + frame->u.max_streams.bidi = 1; + + ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d", + (int) frame->u.max_streams.limit, + (int) frame->u.max_streams.bidi, + frame->level); + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) @@ -1419,9 +1468,11 @@ ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, frame->level = pkt->level; frame->type = NGX_QUIC_FT_MAX_STREAMS; - frame->u.max_streams.limit = f->limit * 2; + frame->u.max_streams.limit = ngx_max(f->limit * 2, NGX_QUIC_STREAMS_LIMIT); frame->u.max_streams.bidi = f->bidi; + c->quic->max_streams = frame->u.max_streams.limit; + ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d", (int) frame->u.max_streams.limit, (int) frame->u.max_streams.bidi, -- cgit v1.2.3 From b3300b522075d57d8d5ba60dee780f3ad6333f88 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 3 Apr 2020 14:02:16 +0300 Subject: Proper handling of packet number in header. - fixed setting of largest received packet number. - sending properly truncated packet number - added support for multi-byte packet number --- src/event/ngx_event_quic.c | 49 ++++++++++++++++++++++++++++++++--- src/event/ngx_event_quic_protection.c | 20 +++++++++----- src/event/ngx_event_quic_transport.c | 39 +++++++++++++++++++++++++--- src/event/ngx_event_quic_transport.h | 2 ++ 4 files changed, 96 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4358f2972..e746a375a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -48,7 +48,7 @@ typedef struct { ngx_quic_secret_t server_secret; uint64_t pnum; - uint64_t largest; + uint64_t largest; /* number received from peer */ ngx_queue_t frames; ngx_queue_t sent; @@ -150,6 +150,9 @@ static ngx_int_t ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, ngx_uint_t nsi); static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); + +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_namespace_t *ns); static void ngx_quic_retransmit_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, ngx_msec_t *waitp); @@ -1235,7 +1238,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (ack->largest <= ns->pnum) { /* duplicate ACK or ACK for non-ack-eliciting frame */ - return NGX_OK; + goto done; } ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1244,9 +1247,13 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } +done: + /* 13.2.3. Receiver Tracking of ACK Frames */ if (ns->largest < ack->largest) { - ack->largest = ns->largest; + ns->largest = ack->largest; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "updated largest received: %ui", ns->largest); } return NGX_OK; @@ -1740,7 +1747,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) keys = &c->quic->keys[start->level]; pkt.secret = &keys->server; - pkt.number = ns->pnum; if (start->level == ssl_encryption_initial) { pkt.flags = NGX_QUIC_PKT_INITIAL; @@ -1748,8 +1754,13 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) } else if (start->level == ssl_encryption_handshake) { pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + + } else { + pkt.flags = 0x40; // TODO: macro, set FIXED bit } + ngx_quic_set_packet_number(&pkt, ns); + pkt.log = c->log; pkt.level = start->level; pkt.dcid = qc->dcid; @@ -1779,6 +1790,36 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) } +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_namespace_t *ns) +{ + uint64_t delta; + + delta = ns->pnum - ns->largest; + pkt->number = ns->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ns->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ns->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ns->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ns->pnum & 0xffffffff; + } +} + + static void ngx_quic_retransmit_handler(ngx_event_t *ev) { diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index b4c6ef3f3..5480afbf2 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -656,6 +656,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, { u_char *pnp, *sample; ngx_str_t ad, out; + ngx_uint_t i; ngx_quic_ciphers_t ciphers; u_char nonce[12], mask[16]; @@ -685,7 +686,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } - sample = &out.data[3]; // pnl=0 + sample = &out.data[4 - pkt->num_len]; if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { @@ -696,9 +697,12 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(pkt->log, "mask", mask, 16); ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); - // header protection, pnl = 0 + /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x0f; - *pnp ^= mask[1]; + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } res->len = ad.len + out.len; @@ -712,6 +716,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, { u_char *pnp, *sample; ngx_str_t ad, out; + ngx_uint_t i; ngx_quic_ciphers_t ciphers; u_char nonce[12], mask[16]; @@ -743,7 +748,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(pkt->log, "out", out.data, out.len); - sample = &out.data[3]; // pnl=0 + sample = &out.data[4 - pkt->num_len]; if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) { @@ -754,9 +759,12 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(pkt->log, "mask", mask, 16); ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); - // header protection, pnl = 0 + /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x1f; - *pnp ^= mask[1]; + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } res->len = ad.len + out.len; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 115ba3fe7..55686df25 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -37,6 +37,11 @@ #endif +#define ngx_quic_write_uint24(p, s) \ + ((p)[0] = (u_char) ((s) >> 16), \ + (p)[1] = (u_char) ((s) >> 8), \ + (p)[2] = (u_char) (s), \ + (p) + 3) #define ngx_quic_write_uint16_aligned(p, s) \ (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) @@ -362,11 +367,24 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ngx_quic_build_int(&p, pkt->token.len); } - ngx_quic_build_int(&p, pkt_len + 1); // length (inc. pnl) + ngx_quic_build_int(&p, pkt_len + pkt->num_len); *pnp = p; - *p++ = pkt->number; // XXX: uint64 + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } return p - start; } @@ -380,13 +398,26 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, p = start = out; - *p++ = 0x40; + *p++ = pkt->flags; p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); *pnp = p; - *p++ = pkt->number; // XXX: uint64 + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index b96519e5a..ddf255a60 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -238,6 +238,8 @@ typedef struct { struct ngx_quic_secret_s *secret; uint64_t number; + uint8_t num_len; + uint32_t trunc; uint8_t flags; uint32_t version; ngx_str_t token; -- cgit v1.2.3 From 4cfc98c63f624d79366e71c14dbc1d063e2f333d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 3 Apr 2020 16:33:59 +0300 Subject: Removed unneccesary milliseconds conversion. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e746a375a..3278176ca 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1574,7 +1574,7 @@ ngx_quic_output(ngx_connection_t *c) } if (!qc->retry.timer_set && !qc->closing) { - ngx_add_timer(&qc->retry, qc->tp.max_ack_delay * 1000); + ngx_add_timer(&qc->retry, qc->tp.max_ack_delay); } return NGX_OK; -- cgit v1.2.3 From 671ed4b3b1f67f630953aa9b0c76b7a27c2e60f0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 4 Apr 2020 17:34:04 +0300 Subject: Logging of packet numbers in QUIC packet creation. --- src/event/ngx_event_quic_protection.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 5480afbf2..5db96a781 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -673,6 +673,10 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "ngx_quic_create_long_packet: number %L, encoded %d:0x%xD", + pkt->number, (int) pkt->num_len, pkt->trunc); + ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); @@ -731,6 +735,10 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "ngx_quic_create_short_packet: number %L, encoded %d:0x%xD", + pkt->number, (int) pkt->num_len, pkt->trunc); + ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); -- cgit v1.2.3 From 3b77fb705b79d5696d8c15dcd076bbd877492c9e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 4 Apr 2020 17:34:39 +0300 Subject: Removed excessive debugging in QUIC packet creation. While here, eliminated further difference in between. --- src/event/ngx_event_quic_protection.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 5db96a781..747fc2e4a 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -698,8 +698,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } ngx_quic_hexdump0(pkt->log, "sample", sample, 16); - ngx_quic_hexdump0(pkt->log, "mask", mask, 16); - ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); + ngx_quic_hexdump0(pkt->log, "mask", mask, 5); /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x0f; @@ -729,6 +728,8 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ad.data = res->data; ad.len = ngx_quic_create_short_header(pkt, ad.data, out.len, &pnp); + out.data = res->data + ad.len; + ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { @@ -745,17 +746,13 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); - out.data = res->data + ad.len; - - if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, - &ad, pkt->log) + if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, + nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; } - ngx_quic_hexdump0(pkt->log, "out", out.data, out.len); - sample = &out.data[4 - pkt->num_len]; if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) != NGX_OK) @@ -764,8 +761,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } ngx_quic_hexdump0(pkt->log, "sample", sample, 16); - ngx_quic_hexdump0(pkt->log, "mask", mask, 16); - ngx_quic_hexdump0(pkt->log, "hp_key", pkt->secret->hp.data, 16); + ngx_quic_hexdump0(pkt->log, "mask", mask, 5); /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x1f; @@ -776,8 +772,6 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, res->len = ad.len + out.len; - ngx_quic_hexdump0(pkt->log, "packet", res->data, res->len); - return NGX_OK; } -- cgit v1.2.3 From 1381982aaa600e2ce501dc22a273d5ae11f93de9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 6 Apr 2020 14:54:08 +0300 Subject: TLS Key Update in QUIC. Old keys retention is yet to be implemented. --- src/event/ngx_event_quic.c | 49 ++++++++++++++- src/event/ngx_event_quic_protection.c | 108 ++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic_protection.h | 3 + src/event/ngx_event_quic_transport.h | 7 ++- 4 files changed, 156 insertions(+), 11 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3278176ca..ebb9b24af 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -68,6 +68,7 @@ struct ngx_quic_connection_s { ngx_quic_namespace_t ns[NGX_QUIC_NAMESPACE_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; uint64_t crypto_offset[NGX_QUIC_ENCRYPTION_LAST]; ngx_ssl_t *ssl; @@ -88,6 +89,7 @@ struct ngx_quic_connection_s { unsigned send_timer_set:1; unsigned closing:1; + unsigned key_phase:1; }; @@ -979,7 +981,8 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_quic_secrets_t *keys; + ngx_int_t rc; + ngx_quic_secrets_t *keys, *next, tmp; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -988,6 +991,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; keys = &c->quic->keys[ssl_encryption_application]; + next = &c->quic->next_key; if (keys->client.key.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1000,6 +1004,8 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } pkt->secret = &keys->client; + pkt->next = &next->client; + pkt->key_phase = c->quic->key_phase; pkt->level = ssl_encryption_application; pkt->plaintext = buf; @@ -1007,7 +1013,31 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - return ngx_quic_payload_handler(c, pkt); + /* switch keys on Key Phase change */ + + if (pkt->key_update) { + c->quic->key_phase ^= 1; + + tmp = *keys; + *keys = *next; + *next = tmp; + } + + rc = ngx_quic_payload_handler(c, pkt); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* generate next keys */ + + if (pkt->key_update) { + if (ngx_quic_key_update(c, keys, next) != NGX_OK) { + return NGX_ERROR; + } + } + + return rc; } @@ -1330,6 +1360,18 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_queue_frame(c->quic, frame); } #endif + + /* + * Generating next keys before a key update is received. + * See quic-tls 9.4 Header Protection Timing Side-Channels. + */ + + if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application], + &c->quic->next_key) + != NGX_OK) + { + return NGX_ERROR; + } } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1756,7 +1798,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) pkt.flags = NGX_QUIC_PKT_HANDSHAKE; } else { - pkt.flags = 0x40; // TODO: macro, set FIXED bit + // TODO: macro, set FIXED bit + pkt.flags = 0x40 | (c->quic->key_phase ? NGX_QUIC_PKT_KPHASE : 0); } ngx_quic_set_packet_number(&pkt, ns); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 747fc2e4a..4359c9311 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -232,9 +232,11 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, uint8_t *p; uint8_t info[20]; - out->data = ngx_pnalloc(pool, out->len); if (out->data == NULL) { - return NGX_ERROR; + out->data = ngx_pnalloc(pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } } info_len = 2 + 1 + label->len + 1; @@ -622,6 +624,14 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, return 0; } + peer_secret->secret.data = ngx_pnalloc(pool, secret_len); + if (peer_secret->secret.data == NULL) { + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + peer_secret->key.len = key_len; peer_secret->iv.len = NGX_QUIC_IV_LEN; peer_secret->hp.len = key_len; @@ -650,6 +660,83 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, } +ngx_int_t +ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, + ngx_quic_secrets_t *next) +{ + ngx_uint_t i; + ngx_quic_ciphers_t ciphers; + + ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + + if (ngx_quic_ciphers(c->ssl->connection, &ciphers, + ssl_encryption_application) + == NGX_ERROR) + { + return NGX_ERROR; + } + + next->client.secret.len = current->client.secret.len; + next->client.key.len = current->client.key.len; + next->client.iv.len = current->client.iv.len; + next->client.hp = current->client.hp; + + next->server.secret.len = current->server.secret.len; + next->server.key.len = current->server.key.len; + next->server.iv.len = current->server.iv.len; + next->server.hp = current->server.hp; + + struct { + ngx_str_t label; + ngx_str_t *key; + ngx_str_t *secret; + } seq[] = { + { + ngx_string("tls13 quic ku"), + &next->client.secret, + ¤t->client.secret, + }, + { + ngx_string("tls13 quic key"), + &next->client.key, + &next->client.secret, + }, + { + ngx_string("tls13 quic iv"), + &next->client.iv, + &next->client.secret, + }, + { + ngx_string("tls13 quic ku"), + &next->server.secret, + ¤t->server.secret, + }, + { + ngx_string("tls13 quic key"), + &next->server.key, + &next->server.secret, + }, + { + ngx_string("tls13 quic iv"), + &next->server.iv, + &next->server.secret, + }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret->data, seq[i].secret->len) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + static ssize_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) @@ -821,8 +908,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) { u_char clearflags, *p, *sample; uint64_t pn; - ngx_int_t pnl, rc; + ngx_int_t pnl, rc, key_phase; ngx_str_t in, ad; + ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; uint8_t mask[16], nonce[12]; @@ -830,6 +918,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) return NGX_ERROR; } + secret = pkt->secret; + p = pkt->raw->pos; /* draft-ietf-quic-tls-23#section-5.4.2: @@ -844,7 +934,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) /* header protection */ - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { return NGX_ERROR; @@ -855,6 +945,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) } else { clearflags = pkt->flags ^ (mask[0] & 0x1f); + key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0; + + if (key_phase != pkt->key_phase) { + secret = pkt->next; + pkt->key_update = 1; + } } pnl = (clearflags & 0x03) + 1; @@ -889,7 +985,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; } while (--pnl); - ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); @@ -903,7 +999,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) pkt->payload.data = pkt->plaintext + ad.len; - rc = ngx_quic_tls_open(ciphers.c, pkt->secret, &pkt->payload, + rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, nonce, &in, &ad, pkt->log); ngx_quic_hexdump0(pkt->log, "packet payload", diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index c9473f575..800184182 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -33,6 +33,9 @@ int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len, ngx_quic_secret_t *peer_secret); +ngx_int_t ngx_quic_key_update(ngx_connection_t *c, + ngx_quic_secrets_t *current, ngx_quic_secrets_t *next); + ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index ddf255a60..f6bd96d04 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -19,6 +19,7 @@ #define NGX_QUIC_PKT_ZRTT 0xD0 /* 17.2.3 */ #define NGX_QUIC_PKT_HANDSHAKE 0xE0 /* 17.2.4 */ #define NGX_QUIC_PKT_RETRY 0xF0 /* 17.2.5 */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* 17.3 */ #define ngx_quic_pkt_in(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_INITIAL) #define ngx_quic_pkt_zrtt(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_ZRTT) @@ -237,6 +238,7 @@ typedef struct { ngx_log_t *log; struct ngx_quic_secret_s *secret; + struct ngx_quic_secret_s *next; uint64_t number; uint8_t num_len; uint32_t trunc; @@ -258,8 +260,9 @@ typedef struct { u_char *plaintext; ngx_str_t payload; /* decrypted data */ - ngx_uint_t need_ack; - /* unsigned need_ack:1; */ + unsigned need_ack:1; + unsigned key_phase:1; + unsigned key_update:1; } ngx_quic_header_t; -- cgit v1.2.3 From cc704a8c319130687285a49c53f263a1fe880943 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 6 Apr 2020 14:54:10 +0300 Subject: Rejecting new connections with non-zero Initial packet. --- src/event/ngx_event_quic.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ebb9b24af..b96af808d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -531,6 +531,12 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + if (pkt->pn != 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid initial packet number %L", pkt->pn); + return NGX_ERROR; + } + if (ngx_quic_init_connection(c) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 3f3315aea64c1f5968fce127b6ee8cadc783d6d3 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 6 Apr 2020 14:54:10 +0300 Subject: Discarding Handshake packets if no Handshake keys yet. Found with a previously received Initial packet with ACK only, which instantiates a new connection but do not produce the handshake keys. This can be triggered by a fairly well behaving client, if the server stands behind a load balancer that stripped Initial packets exchange. Found by F5 test suite. --- src/event/ngx_event_quic.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b96af808d..ab0cf2cd7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -870,6 +870,14 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; + keys = &c->quic->keys[ssl_encryption_handshake]; + + if (keys->client.key.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "no read keys yet, packet ignored"); + return NGX_DECLINED; + } + /* extract cleartext data into pkt */ if (ngx_quic_parse_long_header(pkt) != NGX_OK) { return NGX_ERROR; @@ -905,8 +913,6 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - keys = &c->quic->keys[ssl_encryption_handshake]; - pkt->secret = &keys->client; pkt->level = ssl_encryption_handshake; pkt->plaintext = buf; -- cgit v1.2.3 From 5b41275219497a73d1ecf820d1f5b0a2c18850e3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Sat, 4 Apr 2020 22:27:29 +0300 Subject: Do not set timers after the connection is closed. The qc->closing flag is set when a connection close is initiated for the first time. No timers will be set if the flag is active. TODO: this is a temporary solution to avoid running timer handlers after connection (and it's pool) was destroyed. It looks like currently we have no clear policy of connection closing in regard to timers. --- src/event/ngx_event_quic.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ab0cf2cd7..d1757246d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -704,6 +704,7 @@ ngx_quic_close_connection(ngx_connection_t *c) qc = c->quic; if (qc) { + qc->closing = 1; tree = &qc->streams.tree; if (tree->root != tree->sentinel) { @@ -727,6 +728,10 @@ ngx_quic_close_connection(ngx_connection_t *c) ngx_post_event(rev, &ngx_posted_events); + if (rev->timer_set) { + ngx_del_timer(rev); + } + #if (NGX_DEBUG) ns++; #endif @@ -735,7 +740,6 @@ ngx_quic_close_connection(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic connection has %ui active streams", ns); - qc->closing = 1; return; } @@ -1622,7 +1626,7 @@ ngx_quic_output(ngx_connection_t *c) } } - if (!qc->send_timer_set) { + if (!qc->send_timer_set && !qc->closing) { qc->send_timer_set = 1; ngx_add_timer(c->read, qc->tp.max_idle_timeout); } -- cgit v1.2.3 From 61aa190cfb529f0c9f7b2fc55bd403de70dd33c4 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Apr 2020 11:17:14 +0300 Subject: Added a bit more debugging in STREAM frame parser. --- src/event/ngx_event_quic_transport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 55686df25..a91fcfc0f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -836,7 +836,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, &f->u.stream.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stream frame data"); + "failed to parse stream frame data len=%ui " + "offset=%ui", f->u.stream.length, f->u.stream.offset); return NGX_ERROR; } -- cgit v1.2.3 From 757b3e7bcf76f310df6769727f6a2d32864318f5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Sat, 4 Apr 2020 22:25:41 +0300 Subject: Added check for SSL_get_current_cipher() results. The function may return NULL and result need to be checked before use. --- src/event/ngx_event_quic_protection.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 4359c9311..86300e68c 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -62,13 +62,19 @@ static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level) { - ngx_int_t id, len; + ngx_int_t id, len; + const SSL_CIPHER *cipher; if (level == ssl_encryption_initial) { id = NGX_AES_128_GCM_SHA256; } else { - id = SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff; + cipher = SSL_get_current_cipher(ssl_conn); + if (cipher == NULL) { + return NGX_ERROR; + } + + id = SSL_CIPHER_get_id(cipher) & 0xffff; } switch (id) { -- cgit v1.2.3 From 1c9ddadd76bf41a1682f512046bfedd44fe902ad Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Apr 2020 11:16:45 +0300 Subject: Ignore non-yet-implemented frames. Such frames are grouped together in a switch and just ignored, instead of closing the connection This may improve test coverage. All such frames require acknowledgment. --- src/event/ngx_event_quic.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d1757246d..76ff3a94b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1117,19 +1117,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_PADDING: + /* no action required */ break; case NGX_QUIC_FT_PING: ack_this = 1; break; - case NGX_QUIC_FT_NEW_CONNECTION_ID: - ack_this = 1; - break; - case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE2: - do_close = 1; break; @@ -1156,14 +1152,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; - case NGX_QUIC_FT_RESET_STREAM: - /* TODO: handle */ - break; - - case NGX_QUIC_FT_STOP_SENDING: - /* TODO: handle; need ack ? */ - break; - case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: @@ -1189,6 +1177,20 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; + case NGX_QUIC_FT_NEW_CONNECTION_ID: + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + case NGX_QUIC_FT_NEW_TOKEN: + case NGX_QUIC_FT_RESET_STREAM: + case NGX_QUIC_FT_STOP_SENDING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + + /* TODO: handle */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "frame handler not implemented"); + ack_this = 1; + break; + default: return NGX_ERROR; } -- cgit v1.2.3 From 35878c37968db98fedf7eac0cfedf24785b1d32f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Apr 2020 16:19:26 +0300 Subject: ACK ranges processing. + since number of ranges in unknown, provide a function to parse them once again in handler to avoid memory allocation + ack handler now processes all ranges, not only the first + ECN counters are parsed and saved into frame if present --- src/event/ngx_event_quic.c | 86 +++++++++++++++++++++++++++++------- src/event/ngx_event_quic_transport.c | 67 +++++++++++++++++++++++----- src/event/ngx_event_quic_transport.h | 10 ++++- 3 files changed, 134 insertions(+), 29 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 76ff3a94b..8c9e8da2e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -134,6 +134,8 @@ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_namespace_t *ns, uint64_t min, uint64_t max); static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, @@ -1242,9 +1244,10 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) { - ngx_uint_t found, min; - ngx_queue_t *q, range; - ngx_quic_frame_t *f; + ssize_t n; + u_char *pos, *end; + uint64_t gap, range; + ngx_uint_t i, min, max; ngx_quic_namespace_t *ns; ns = &c->quic->ns[ngx_quic_ns(pkt->level)]; @@ -1253,6 +1256,12 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, "ngx_quic_handle_ack_frame in namespace %d", ngx_quic_ns(pkt->level)); + /* + * TODO: If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + * (19.3.1) + */ + if (ack->first_range > ack->largest) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid first range in ack frame"); @@ -1260,6 +1269,62 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } min = ack->largest - ack->first_range; + max = ack->largest; + + if (ngx_quic_handle_ack_frame_range(c, ns, min, max) != NGX_OK) { + return NGX_ERROR; + } + + /* 13.2.3. Receiver Tracking of ACK Frames */ + if (ns->largest < max) { + ns->largest = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "updated largest received: %ui", max); + } + + pos = ack->ranges_start; + end = ack->ranges_end; + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap >= min) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid range %ui in ack frame", i); + return NGX_ERROR; + } + + max = min - 1 - gap; + + if (range > max + 1) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "invalid range %ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range + 1; + + if (ngx_quic_handle_ack_frame_range(c, ns, min, max) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_namespace_t *ns, + uint64_t min, uint64_t max) +{ + ngx_uint_t found; + ngx_queue_t *q, range; + ngx_quic_frame_t *f; found = 0; @@ -1271,7 +1336,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (f->pnum >= min && f->pnum <= ack->largest) { + if (f->pnum >= min && f->pnum <= max) { q = ngx_queue_next(q); ngx_queue_remove(&f->queue); ngx_quic_free_frame(c, f); @@ -1284,9 +1349,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (!found) { - if (ack->largest <= ns->pnum) { + if (max <= ns->pnum) { /* duplicate ACK or ACK for non-ack-eliciting frame */ - goto done; + return NGX_OK; } ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1295,15 +1360,6 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } -done: - - /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ns->largest < ack->largest) { - ns->largest = ack->largest; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "updated largest received: %ui", ns->largest); - } - return NGX_OK; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index a91fcfc0f..99c474de9 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -561,9 +561,10 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *f) { - u_char *p; - uint8_t flags; - uint64_t varint; + u_char *p; + uint8_t flags; + uint64_t varint; + ngx_uint_t i; flags = pkt->flags; p = start; @@ -641,21 +642,19 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } - if (f->u.ack.range_count) { - p = ngx_quic_parse_int(p, end, &f->u.ack.ranges[0]); + f->u.ack.ranges_start = p; + + /* process all ranges to get bounds, values are ignored */ + for (i = 0; i < f->u.ack.range_count; i++) { + p = ngx_quic_parse_int_multi(p, end, &varint, &varint, NULL); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame first range"); + "failed to parse ack frame range %ui", i); return NGX_ERROR; } } - if (f->type == NGX_QUIC_FT_ACK_ECN) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "TODO: parse ECN ack frames"); - /* TODO: add parsing of such frames */ - return NGX_ERROR; - } + f->u.ack.ranges_end = p; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "ACK: { largest=%ui delay=%ui count=%ui first=%ui}", @@ -664,6 +663,21 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ack.range_count, f->u.ack.first_range); + if (f->type == NGX_QUIC_FT_ACK_ECN) { + + p = ngx_quic_parse_int_multi(p, end, &f->u.ack.ect0, + &f->u.ack.ect1, &f->u.ack.ce, NULL); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse ack frame ECT counts", i); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "ACK ECN counters: %ui %ui %ui", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; case NGX_QUIC_FT_PING: @@ -1111,6 +1125,35 @@ not_allowed: } +ssize_t +ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, + uint64_t *gap, uint64_t *range) +{ + u_char *p; + + p = start; + + p = ngx_quic_parse_int(p, end, gap); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse ack frame gap"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, range); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse ack frame range"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "ACK range: gap %ui range %ui", *gap, *range); + + return p - start; +} + + ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index f6bd96d04..c44db4144 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -102,8 +102,11 @@ typedef struct { uint64_t delay; uint64_t range_count; uint64_t first_range; - uint64_t ranges[20]; - /* TODO: ecn counts */ + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + u_char *ranges_start; + u_char *ranges_end; } ngx_quic_ack_frame_t; @@ -284,6 +287,9 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); +ssize_t ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, + u_char *end, uint64_t *gap, uint64_t *range); + ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log); ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, -- cgit v1.2.3 From f540602ad217bce65d311546337200e915cf2676 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 7 Apr 2020 12:54:34 +0300 Subject: Fixed build with OpenSSL using old callbacks API. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8c9e8da2e..65d71a286 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -254,7 +254,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); - keys = &c->quic->secrets[level]; + keys = &c->quic->keys[level]; rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, -- cgit v1.2.3 From c64a3939e4225060ef9768d637de7e2e7726d328 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 7 Apr 2020 15:50:38 +0300 Subject: Added basic offset support in client CRYPTO frames. The offset in client CRYPTO frames is tracked in c->quic->crypto_offset_in. This means that CRYPTO frames with non-zero offset are now accepted making possible to finish handshake with client certificates that exceed max packet size (if no reordering happens). The c->quic->crypto_offset field is renamed to crypto_offset_out to avoid confusion with tracking of incoming CRYPTO stream. --- src/event/ngx_event_quic.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 65d71a286..b71cb6e0d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -69,7 +69,8 @@ struct ngx_quic_connection_s { ngx_quic_namespace_t ns[NGX_QUIC_NAMESPACE_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; ngx_quic_secrets_t next_key; - uint64_t crypto_offset[NGX_QUIC_ENCRYPTION_LAST]; + uint64_t crypto_offset_out[NGX_QUIC_ENCRYPTION_LAST]; + uint64_t crypto_offset_in[NGX_QUIC_ENCRYPTION_LAST]; ngx_ssl_t *ssl; @@ -334,11 +335,11 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset += qc->crypto_offset[level]; + frame->u.crypto.offset += qc->crypto_offset_out[level]; frame->u.crypto.len = len; frame->u.crypto.data = frame->data; - qc->crypto_offset[level] += len; + qc->crypto_offset_out[level] += len; ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); @@ -1368,14 +1369,21 @@ static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *f) { - int sslerr; - ssize_t n; - ngx_ssl_conn_t *ssl_conn; + int sslerr; + ssize_t n; + uint64_t *curr_offset; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = c->quic; + + curr_offset = &qc->crypto_offset_in[pkt->level]; - if (f->offset != 0x0) { + if (f->offset != *curr_offset) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "crypto frame with non-zero offset"); - // TODO: add support for crypto frames spanning packets + "crypto frame with unexpected offset"); + + /* TODO: support reordering/buffering of data */ return NGX_ERROR; } @@ -1394,6 +1402,8 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } + *curr_offset += f->len; + n = SSL_do_handshake(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); -- cgit v1.2.3 From b77fd3dc58b8398bf85d7c11901f5497f1abdf9e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Apr 2020 17:54:23 +0300 Subject: HTTP/3: fixed reading request body. --- src/http/v3/ngx_http_v3_request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 911dbab36..e0ee0c882 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -265,7 +265,8 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, } if (ctx->size) { - return NGX_OK; + ctx->length = ctx->size + 1; + return (b->pos == b->last) ? NGX_AGAIN : NGX_OK; } while (b->pos < b->last) { -- cgit v1.2.3 From fbf6494f5fef81a402b58f695edd2b285b27c53a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 14 Apr 2020 12:06:32 +0300 Subject: Rename types and variables used for packet number space. Quote: Conceptually, a packet number space is the context in which a packet can be processed and acknowledged. ngx_quic_namespace_t => ngx_quic_send_ctx_t qc->ns => qc->send_ctx ns->largest => send_ctx->largest_ack The ngx_quic_ns(level) macro now returns pointer, not just index: ngx_quic_get_send_ctx(c->quic, level) ngx_quic_retransmit_ns() => ngx_quic_retransmit() ngx_quic_output_ns() => ngx_quic_output_frames() --- src/event/ngx_event_quic.c | 138 +++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b71cb6e0d..7937f5ae3 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -16,11 +16,12 @@ * 1 - Handshake * 2 - 0-RTT and 1-RTT */ -#define ngx_quic_ns(level) \ - ((level) == ssl_encryption_initial) ? 0 \ - : (((level) == ssl_encryption_handshake) ? 1 : 2) +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) -#define NGX_QUIC_NAMESPACE_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) #define NGX_QUIC_STREAMS_INC 16 #define NGX_QUIC_STREAMS_LIMIT (1ULL < 60) @@ -43,16 +44,24 @@ typedef struct { } ngx_quic_streams_t; +/* + * 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets which + * are also Initial packets. +*/ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; uint64_t pnum; - uint64_t largest; /* number received from peer */ + uint64_t largest_ack; /* number received from peer */ ngx_queue_t frames; ngx_queue_t sent; -} ngx_quic_namespace_t; +} ngx_quic_send_ctx_t; struct ngx_quic_connection_s { @@ -66,7 +75,7 @@ struct ngx_quic_connection_s { ngx_quic_state_t state; - ngx_quic_namespace_t ns[NGX_QUIC_NAMESPACE_LAST]; + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; ngx_quic_secrets_t next_key; uint64_t crypto_offset_out[NGX_QUIC_ENCRYPTION_LAST]; @@ -136,7 +145,7 @@ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, - ngx_quic_namespace_t *ns, uint64_t min, uint64_t max); + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max); static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, @@ -151,16 +160,16 @@ static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_output(ngx_connection_t *c); -static ngx_int_t ngx_quic_output_ns(ngx_connection_t *c, - ngx_quic_namespace_t *ns, ngx_uint_t nsi); +static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_uint_t nsi); static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, - ngx_quic_namespace_t *ns); + ngx_quic_send_ctx_t *ctx); static void ngx_quic_retransmit_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_retransmit_ns(ngx_connection_t *c, - ngx_quic_namespace_t *ns, ngx_msec_t *waitp); +static ngx_int_t ngx_quic_retransmit(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_msec_t *waitp); static void ngx_quic_push_handler(ngx_event_t *ev); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, @@ -470,8 +479,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_rbtree_insert_stream); for (i = 0; i < 3; i++) { - ngx_queue_init(&qc->ns[i].frames); - ngx_queue_init(&qc->ns[i].sent); + ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sent); } ngx_queue_init(&qc->free_frames); @@ -1245,17 +1254,16 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) { - ssize_t n; - u_char *pos, *end; - uint64_t gap, range; - ngx_uint_t i, min, max; - ngx_quic_namespace_t *ns; + ssize_t n; + u_char *pos, *end; + uint64_t gap, range; + ngx_uint_t i, min, max; + ngx_quic_send_ctx_t *ctx; - ns = &c->quic->ns[ngx_quic_ns(pkt->level)]; + ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_handle_ack_frame in namespace %d", - ngx_quic_ns(pkt->level)); + "ngx_quic_handle_ack_frame level %d", pkt->level); /* * TODO: If any computed packet number is negative, an endpoint MUST @@ -1272,13 +1280,13 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, min = ack->largest - ack->first_range; max = ack->largest; - if (ngx_quic_handle_ack_frame_range(c, ns, min, max) != NGX_OK) { + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max) != NGX_OK) { return NGX_ERROR; } /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ns->largest < max) { - ns->largest = max; + if (ctx->largest_ack < max) { + ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "updated largest received: %ui", max); } @@ -1310,7 +1318,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, min = max - range + 1; - if (ngx_quic_handle_ack_frame_range(c, ns, min, max) != NGX_OK) { + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max) != NGX_OK) { return NGX_ERROR; } } @@ -1320,20 +1328,18 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t -ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_namespace_t *ns, +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max) { ngx_uint_t found; - ngx_queue_t *q, range; + ngx_queue_t *q; ngx_quic_frame_t *f; found = 0; - ngx_queue_init(&range); + q = ngx_queue_head(&ctx->sent); - q = ngx_queue_head(&ns->sent); - - while (q != ngx_queue_sentinel(&ns->sent)) { + while (q != ngx_queue_sentinel(&ctx->sent)) { f = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -1350,7 +1356,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_namespace_t *ns, if (!found) { - if (max <= ns->pnum) { + if (max <= ctx->pnum) { /* duplicate ACK or ACK for non-ack-eliciting frame */ return NGX_OK; } @@ -1662,11 +1668,11 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { - ngx_quic_namespace_t *ns; + ngx_quic_send_ctx_t *ctx; - ns = &qc->ns[ngx_quic_ns(frame->level)]; + ctx = ngx_quic_get_send_ctx(qc, frame->level); - ngx_queue_insert_tail(&ns->frames, &frame->queue); + ngx_queue_insert_tail(&ctx->frames, &frame->queue); /* TODO: check PUSH flag on stream and call output */ @@ -1680,16 +1686,14 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { ngx_uint_t i; - ngx_quic_namespace_t *ns; ngx_quic_connection_t *qc; c->log->action = "sending frames"; qc = c->quic; - for (i = 0; i < 3; i++) { - ns = &qc->ns[i]; - if (ngx_quic_output_ns(c, ns, i) != NGX_OK) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + if (ngx_quic_output_frames(c, &qc->send_ctx[i], i) != NGX_OK) { return NGX_ERROR; } } @@ -1708,7 +1712,7 @@ ngx_quic_output(ngx_connection_t *c) static ngx_int_t -ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, +ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_uint_t nsi) { size_t len, hlen, n; @@ -1719,7 +1723,7 @@ ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, qc = c->quic; - if (ngx_queue_empty(&ns->frames)) { + if (ngx_queue_empty(&ctx->frames)) { return NGX_OK; } @@ -1728,7 +1732,7 @@ ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, hlen += EVP_GCM_TLS_TAG_LEN; - q = ngx_queue_head(&ns->frames); + q = ngx_queue_head(&ctx->frames); do { len = 0; @@ -1753,7 +1757,7 @@ ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, len += n; - } while (q != ngx_queue_sentinel(&ns->frames)); + } while (q != ngx_queue_sentinel(&ctx->frames)); rc = ngx_quic_send_frames(c, &range); @@ -1762,7 +1766,7 @@ ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, * frames are moved into the sent queue * to wait for ack/be retransmitted */ - ngx_queue_add(&ns->sent, &range); + ngx_queue_add(&ctx->sent, &range); } else if (rc == NGX_DONE) { @@ -1774,7 +1778,7 @@ ngx_quic_output_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, } - } while (q != ngx_queue_sentinel(&ns->frames)); + } while (q != ngx_queue_sentinel(&ctx->frames)); return NGX_OK; } @@ -1810,7 +1814,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; - ngx_quic_namespace_t *ns; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static ngx_str_t initial_token = ngx_null_string; static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -1821,7 +1825,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); - ns = &c->quic->ns[ngx_quic_ns(start->level)]; + ctx = ngx_quic_get_send_ctx(c->quic, start->level); ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -1846,7 +1850,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) } p += len; - f->pnum = ns->pnum; + f->pnum = ctx->pnum; } if (start->level == ssl_encryption_initial) { @@ -1886,7 +1890,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) pkt.flags = 0x40 | (c->quic->key_phase ? NGX_QUIC_PKT_KPHASE : 0); } - ngx_quic_set_packet_number(&pkt, ns); + ngx_quic_set_packet_number(&pkt, ctx); pkt.log = c->log; pkt.level = start->level; @@ -1908,7 +1912,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) } /* len == NGX_OK || NGX_AGAIN */ - ns->pnum++; + ctx->pnum++; now = ngx_current_msec; start->last = now; @@ -1918,31 +1922,31 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) static void -ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_namespace_t *ns) +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) { uint64_t delta; - delta = ns->pnum - ns->largest; - pkt->number = ns->pnum; + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; if (delta <= 0x7F) { pkt->num_len = 1; - pkt->trunc = ns->pnum & 0xff; + pkt->trunc = ctx->pnum & 0xff; } else if (delta <= 0x7FFF) { pkt->num_len = 2; pkt->flags |= 0x1; - pkt->trunc = ns->pnum & 0xffff; + pkt->trunc = ctx->pnum & 0xffff; } else if (delta <= 0x7FFFFF) { pkt->num_len = 3; pkt->flags |= 0x2; - pkt->trunc = ns->pnum & 0xffffff; + pkt->trunc = ctx->pnum & 0xffffff; } else { pkt->num_len = 4; pkt->flags |= 0x3; - pkt->trunc = ns->pnum & 0xffffffff; + pkt->trunc = ctx->pnum & 0xffffffff; } } @@ -1963,8 +1967,8 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) wait = 0; - for (i = 0; i < NGX_QUIC_NAMESPACE_LAST; i++) { - if (ngx_quic_retransmit_ns(c, &qc->ns[i], &nswait) != NGX_OK) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + if (ngx_quic_retransmit(c, &qc->send_ctx[i], &nswait) != NGX_OK) { ngx_quic_close_connection(c); return; } @@ -2000,7 +2004,7 @@ ngx_quic_push_handler(ngx_event_t *ev) static ngx_int_t -ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, +ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_msec_t *waitp) { uint64_t pn; @@ -2014,12 +2018,12 @@ ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, now = ngx_current_msec; wait = 0; - if (ngx_queue_empty(&ns->sent)) { + if (ngx_queue_empty(&ctx->sent)) { *waitp = 0; return NGX_OK; } - q = ngx_queue_head(&ns->sent); + q = ngx_queue_head(&ctx->sent); start = ngx_queue_data(q, ngx_quic_frame_t, queue); pn = start->pnum; f = start; @@ -2046,7 +2050,7 @@ ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, ngx_queue_remove(&f->queue); ngx_queue_insert_tail(&range, &f->queue); - } while (q != ngx_queue_sentinel(&ns->sent)); + } while (q != ngx_queue_sentinel(&ctx->sent)); wait = start->last + qc->tp.max_ack_delay - now; @@ -2060,9 +2064,9 @@ ngx_quic_retransmit_ns(ngx_connection_t *c, ngx_quic_namespace_t *ns, } /* move frames group to the end of queue */ - ngx_queue_add(&ns->sent, &range); + ngx_queue_add(&ctx->sent, &range); - } while (q != ngx_queue_sentinel(&ns->sent)); + } while (q != ngx_queue_sentinel(&ctx->sent)); *waitp = wait; -- cgit v1.2.3 From 76e99f668d0e84273da0dcfdd5a65a7199b4bb2e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Apr 2020 14:57:58 +0300 Subject: Cleaned up magic numbers in ngx_quic_output_frames(). --- src/event/ngx_event_quic.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7937f5ae3..157fb0da6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -161,7 +161,7 @@ static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, ngx_uint_t nsi); + ngx_quic_send_ctx_t *ctx); static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); @@ -1693,7 +1693,7 @@ ngx_quic_output(ngx_connection_t *c) qc = c->quic; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ngx_quic_output_frames(c, &qc->send_ctx[i], i) != NGX_OK) { + if (ngx_quic_output_frames(c, &qc->send_ctx[i]) != NGX_OK) { return NGX_ERROR; } } @@ -1712,8 +1712,7 @@ ngx_quic_output(ngx_connection_t *c) static ngx_int_t -ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_uint_t nsi) +ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { size_t len, hlen, n; ngx_int_t rc; @@ -1727,13 +1726,14 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return NGX_OK; } - hlen = (nsi == 2) ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + /* all frames in same send_ctx share same level */ + hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; - q = ngx_queue_head(&ctx->frames); - do { len = 0; ngx_queue_init(&range); -- cgit v1.2.3 From 081682cd3ce97323f1d7aeb1c0db2044fc245c88 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 14 Apr 2020 12:16:25 +0300 Subject: Crypto buffer frames reordering. If offset in CRYPTO frame doesn't match expected, following actions are taken: a) Duplicate frames or frames within [0...current offset] are ignored b) New data from intersecting ranges (starts before current_offset, ends after) is consumed c) "Future" frames are stored in a sorted queue (min offset .. max offset) Once a frame is consumed, current offset is updated and the queue is inspected: we iterate the queue until the gap is found and act as described above for each frame. The amount of data in buffered frames is limited by corresponding macro. The CRYPTO and STREAM frame structures are now compatible: they share the same set of initial fields. This allows to have code that deals with both of this frames. The ordering layer now processes the frame with offset and invokes the handler when it can organise an ordered stream of data. --- src/event/ngx_event_quic.c | 311 +++++++++++++++++++++++++++++++---- src/event/ngx_event_quic_transport.c | 16 +- src/event/ngx_event_quic_transport.h | 30 ++-- 3 files changed, 307 insertions(+), 50 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 157fb0da6..b76a09f67 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -26,6 +26,12 @@ #define NGX_QUIC_STREAMS_INC 16 #define NGX_QUIC_STREAMS_LIMIT (1ULL < 60) +/* + * 7.4. Cryptographic Message Buffering + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + typedef enum { NGX_QUIC_ST_INITIAL, /* connection just created */ @@ -64,6 +70,15 @@ typedef struct { } ngx_quic_send_ctx_t; +/* ordered frames stream context */ +typedef struct { + uint64_t sent; + uint64_t received; + ngx_queue_t frames; + size_t total; /* size of buffered data */ +} ngx_quic_frames_stream_t; + + struct ngx_quic_connection_s { ngx_str_t scid; ngx_str_t dcid; @@ -78,8 +93,7 @@ struct ngx_quic_connection_s { ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; ngx_quic_secrets_t next_key; - uint64_t crypto_offset_out[NGX_QUIC_ENCRYPTION_LAST]; - uint64_t crypto_offset_in[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; ngx_ssl_t *ssl; @@ -147,7 +161,21 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max); static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *frame); + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, + ngx_quic_frame_t *f, uint64_t offset_in); +static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame); + +static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, + ngx_quic_frame_handler_pt handler); + +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, + ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); @@ -292,12 +320,13 @@ static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char *p, *end; - size_t client_params_len; - const uint8_t *client_params; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; + u_char *p, *end; + size_t client_params_len; + const uint8_t *client_params; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + ngx_quic_frames_stream_t *fs; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = c->quic; @@ -335,6 +364,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } } + fs = &qc->crypto[level]; + frame = ngx_quic_alloc_frame(c, len); if (frame == NULL) { return 0; @@ -344,11 +375,11 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset += qc->crypto_offset_out[level]; - frame->u.crypto.len = len; + frame->u.crypto.offset += fs->sent; + frame->u.crypto.length = len; frame->u.crypto.data = frame->data; - qc->crypto_offset_out[level] += len; + fs->sent += len; ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); @@ -478,10 +509,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); - for (i = 0; i < 3; i++) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ngx_queue_init(&qc->send_ctx[i].frames); ngx_queue_init(&qc->send_ctx[i].sent); - } + } + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_queue_init(&qc->crypto[i].frames); + } ngx_queue_init(&qc->free_frames); @@ -713,6 +748,8 @@ ngx_quic_close_connection(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection"); + // TODO: free frames from reorder queue if any + qc = c->quic; if (qc) { @@ -829,10 +866,26 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) rc = ngx_quic_app_input(c, &pkt); } - if (rc != NGX_OK) { - return rc; + if (rc == NGX_ERROR) { + return NGX_ERROR; } + /* NGX_OK || NGX_DECLINED */ + + /* + * we get NGX_DECLINED when there are no keys [yet] available + * to decrypt packet. + * Instead of queueing it, we ignore it and rely on the sender's + * retransmission: + * + * 12.2. Coalescing Packets: + * + * For example, if decryption fails (because the keys are + * not available or any other reason), the receiver MAY either + * discard or buffer the packet for later processing and MUST + * attempt to process the remaining packets. + */ + /* b->pos is at header end, adjust by actual packet length */ p = b->pos + pkt.len; b->pos = p; /* reset b->pos to the next packet start */ @@ -1119,9 +1172,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_CRYPTO: - if (ngx_quic_handle_crypto_frame(c, pkt, &frame.u.crypto) - != NGX_OK) - { + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { return NGX_ERROR; } @@ -1373,26 +1424,224 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_crypto_frame_t *f) + ngx_quic_frame_t *frame) { - int sslerr; - ssize_t n; - uint64_t *curr_offset; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; + ngx_quic_connection_t *qc; + ngx_quic_frames_stream_t *fs; qc = c->quic; + fs = &qc->crypto[pkt->level]; + + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input); +} + - curr_offset = &qc->crypto_offset_in[pkt->level]; +static ngx_int_t +ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler) +{ + size_t full_len; + ngx_queue_t *q; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + if (f->offset > fs->received) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "out-of-order frame: expecting %ui got %ui", + fs->received, f->offset); + + return ngx_quic_buffer_frame(c, fs, frame); + } + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + return NGX_OK; + } + + /* intersecting data range, frame modified */ + } + + /* f->offset == fs->received */ + + if (handler(c, frame) != NGX_OK) { + return NGX_ERROR; + } - if (f->offset != *curr_offset) { + fs->received += f->length; + + /* now check the queue if we can continue with buffered frames */ + + do { + q = ngx_queue_head(&fs->frames); + if (q == ngx_queue_sentinel(&fs->frames)) { + break; + } + + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &frame->u.ord; + + if (f->offset > fs->received) { + /* gap found, nothing more to do */ + break; + } + + full_len = f->length; + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + ngx_queue_remove(q); + fs->total -= f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "skipped buffered frame, total %ui", fs->total); + ngx_quic_free_frame(c, frame); + continue; + } + + /* frame was adjusted, proceed to input */ + } + + /* f->offset == fs->received */ + + if (handler(c, frame) != NGX_OK) { + return NGX_ERROR; + } + + fs->received += f->length; + fs->total -= full_len; + + ngx_queue_remove(q); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "consumed buffered frame, total %ui", fs->total); + + ngx_quic_free_frame(c, frame); + + } while (1); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, + uint64_t offset_in) +{ + size_t tail; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + tail = offset_in - f->offset; + + if (tail >= f->length) { + /* range preceeding already received data or duplicate, ignore */ + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "old or duplicate data in ordered frame, ignored"); + return NGX_DONE; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "adjusted ordered frame data start to expected offset"); + + /* intersecting range: adjust data size */ + + f->offset += tail; + f->data += tail; + f->length -= tail; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame) +{ + u_char *data; + ngx_queue_t *q; + ngx_quic_frame_t *dst, *item; + ngx_quic_ordered_frame_t *f, *df; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_buffer_frame"); + + f = &frame->u.ord; + + /* frame start offset is in the future, buffer it */ + + /* check limit on total size used by all buffered frames, not actual data */ + if (NGX_QUIC_MAX_BUFFERED - fs->total < f->length) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "crypto frame with unexpected offset"); + "ordered input buffer limit exceeded"); + return NGX_ERROR; + } - /* TODO: support reordering/buffering of data */ + dst = ngx_quic_alloc_frame(c, f->length); + if (dst == NULL) { return NGX_ERROR; } + data = dst->data; + ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); + dst->data = data; + + ngx_memcpy(dst->data, f->data, f->length); + + df = &dst->u.ord; + df->data = dst->data; + + fs->total += f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ordered frame with unexpected offset: buffered, total %ui", + fs->total); + + /* TODO: do we need some timeout for this queue ? */ + + if (ngx_queue_empty(&fs->frames)) { + ngx_queue_insert_after(&fs->frames, &dst->queue); + return NGX_OK; + } + + for (q = ngx_queue_last(&fs->frames); + q != ngx_queue_sentinel(&fs->frames); + q = ngx_queue_prev(q)) + { + item = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &item->u.ord; + + if (f->offset < df->offset) { + ngx_queue_insert_after(q, &dst->queue); + return NGX_OK; + } + } + + ngx_queue_insert_after(&fs->frames, &dst->queue); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + int sslerr; + ssize_t n; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_crypto_frame_t *f; + + f = &frame->u.crypto; + ssl_conn = c->ssl->connection; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1401,15 +1650,13 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, (int) SSL_quic_write_level(ssl_conn)); if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - f->data, f->len)) + f->data, f->length)) { ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "SSL_provide_quic_data() failed"); return NGX_ERROR; } - *curr_offset += f->len; - n = SSL_do_handshake(ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 99c474de9..1e5a7ea8f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -593,14 +593,14 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } - p = ngx_quic_parse_int(p, end, &f->u.crypto.len); + p = ngx_quic_parse_int(p, end, &f->u.crypto.length); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse crypto frame len"); return NGX_ERROR; } - p = ngx_quic_read_bytes(p, end, f->u.crypto.len, &f->u.crypto.data); + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &f->u.crypto.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse crypto frame data"); @@ -609,11 +609,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic CRYPTO frame length: %uL off:%uL pp:%p", - f->u.crypto.len, f->u.crypto.offset, + f->u.crypto.length, f->u.crypto.offset, f->u.crypto.data); ngx_quic_hexdump0(pkt->log, "CRYPTO frame contents", - f->u.crypto.data, f->u.crypto.len); + f->u.crypto.data, f->u.crypto.length); break; case NGX_QUIC_FT_PADDING: @@ -1242,8 +1242,8 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); len += ngx_quic_varint_len(crypto->offset); - len += ngx_quic_varint_len(crypto->len); - len += crypto->len; + len += ngx_quic_varint_len(crypto->length); + len += crypto->length; return len; } @@ -1252,8 +1252,8 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); ngx_quic_build_int(&p, crypto->offset); - ngx_quic_build_int(&p, crypto->len); - p = ngx_cpymem(p, crypto->data, crypto->len); + ngx_quic_build_int(&p, crypto->length); + p = ngx_cpymem(p, crypto->data, crypto->length); return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index c44db4144..116ec2a85 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -110,13 +110,6 @@ typedef struct { } ngx_quic_ack_frame_t; -typedef struct { - uint64_t offset; - uint64_t len; - u_char *data; -} ngx_quic_crypto_frame_t; - - typedef struct { uint64_t seqnum; uint64_t retire; @@ -126,15 +119,31 @@ typedef struct { } ngx_quic_new_conn_id_frame_t; +/* + * common layout for CRYPTO and STREAM frames; + * conceptually, CRYPTO frame is also a stream + * frame lacking some properties + */ typedef struct { - uint8_t type; - uint64_t stream_id; uint64_t offset; uint64_t length; + u_char *data; +} ngx_quic_ordered_frame_t; + +typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; + + +typedef struct { + /* initial fields same as in ngx_quic_ordered_frame_t */ + uint64_t offset; + uint64_t length; + u_char *data; + + uint8_t type; + uint64_t stream_id; unsigned off:1; unsigned len:1; unsigned fin:1; - u_char *data; } ngx_quic_stream_frame_t; @@ -218,6 +227,7 @@ struct ngx_quic_frame_s { union { ngx_quic_ack_frame_t ack; ngx_quic_crypto_frame_t crypto; + ngx_quic_ordered_frame_t ord; ngx_quic_new_conn_id_frame_t ncid; ngx_quic_stream_frame_t stream; ngx_quic_max_data_frame_t max_data; -- cgit v1.2.3 From 9542c975b7786a9f799093963720022ea88c74f1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 15 Apr 2020 11:11:54 +0300 Subject: Added reordering support for STREAM frames. Each stream node now includes incoming frames queue and sent/received counters for tracking offset. The sent counter is not used, c->sent is used, not like in crypto buffers, which have no connections. --- src/event/ngx_event_quic.c | 180 ++++++++++++++++++++++++++++++++------------- src/event/ngx_event_quic.h | 51 +++++++------ 2 files changed, 159 insertions(+), 72 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b76a09f67..aabbb6114 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -70,15 +70,6 @@ typedef struct { } ngx_quic_send_ctx_t; -/* ordered frames stream context */ -typedef struct { - uint64_t sent; - uint64_t received; - ngx_queue_t frames; - size_t total; /* size of buffered data */ -} ngx_quic_frames_stream_t; - - struct ngx_quic_connection_s { ngx_str_t scid; ngx_str_t dcid; @@ -177,7 +168,12 @@ static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *frame); + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static ngx_quic_stream_t *ngx_quic_add_stream(ngx_connection_t *c, + ngx_quic_stream_frame_t *f); + static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); @@ -739,6 +735,7 @@ ngx_quic_close_connection(ngx_connection_t *c) #if (NGX_DEBUG) ngx_uint_t ns; #endif + ngx_uint_t i; ngx_pool_t *pool; ngx_event_t *rev; ngx_rbtree_t *tree; @@ -748,11 +745,14 @@ ngx_quic_close_connection(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection"); - // TODO: free frames from reorder queue if any - qc = c->quic; if (qc) { + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_quic_free_frames(c, &qc->crypto[i].frames); + } + qc->closing = 1; tree = &qc->streams.tree; @@ -1201,9 +1201,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - if (ngx_quic_handle_stream_frame(c, pkt, &frame.u.stream) - != NGX_OK) - { + if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { return NGX_ERROR; } @@ -1441,6 +1439,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler) { size_t full_len; + ngx_int_t rc; ngx_queue_t *q; ngx_quic_ordered_frame_t *f; @@ -1468,10 +1467,17 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* f->offset == fs->received */ - if (handler(c, frame) != NGX_OK) { + rc = handler(c, frame); + if (rc == NGX_ERROR) { return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; } + /* rc == NGX_OK */ + fs->received += f->length; /* now check the queue if we can continue with buffered frames */ @@ -1512,8 +1518,14 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* f->offset == fs->received */ - if (handler(c, frame) != NGX_OK) { + rc = handler(c, frame); + + if (rc == NGX_ERROR) { return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; } fs->received += f->length; @@ -1721,20 +1733,54 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) static ngx_int_t -ngx_quic_handle_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f) +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) { - size_t n; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + ngx_quic_frames_stream_t *fs; qc = c->quic; + f = &frame->u.stream; sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - if (sn) { + if (sn == NULL) { + sn = ngx_quic_add_stream(c, f); + if (sn == NULL) { + return NGX_ERROR; + } + } + + fs = &sn->fs; + + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input); +} + + +static ngx_int_t +ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_buf_t *b; + ngx_event_t *rev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = c->quic; + + f = &frame->u.stream; + + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + + if (sn == NULL) { + // TODO: possible? + // deleted while stream is in reordering queue? + return NGX_ERROR; + } + + if (sn->fs.received != 0) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); b = sn->b; @@ -1761,9 +1807,50 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, rev->handler(rev); } + /* check if stream was destroyed */ + if (ngx_quic_find_stream(&qc->streams.tree, f->stream_id) == NULL) { + return NGX_DONE; + } + return NGX_OK; } + b = sn->b; + b->last = ngx_cpymem(b->last, f->data, f->length); + + rev = sn->c->read; + rev->ready = 1; + + if (f->fin) { + rev->pending_eof = 1; + } + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + ngx_quic_handle_max_streams(c); + } + + qc->streams.handler(sn->c); + + /* check if stream was destroyed */ + if (ngx_quic_find_stream(&qc->streams.tree, f->stream_id) == NULL) { + return NGX_DONE; + } + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_add_stream(ngx_connection_t *c, ngx_quic_stream_frame_t *f) +{ + size_t n; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = c->quic; + + // TODO: check increasing IDs + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) @@ -1776,31 +1863,15 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, if (n < f->length) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); - return NGX_ERROR; + return NULL; } sn = ngx_quic_create_stream(c, f->stream_id, n); if (sn == NULL) { - return NGX_ERROR; - } - - b = sn->b; - b->last = ngx_cpymem(b->last, f->data, f->length); - - rev = sn->c->read; - rev->ready = 1; - - if (f->fin) { - rev->pending_eof = 1; - } - - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - ngx_quic_handle_max_streams(c); + return NULL; } - qc->streams.handler(sn->c); - - return NGX_OK; + return sn; } @@ -2024,7 +2095,6 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) return NGX_ERROR; } - } while (q != ngx_queue_sentinel(&ctx->frames)); return NGX_OK; @@ -2037,15 +2107,19 @@ ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_queue_t *q; ngx_quic_frame_t *f; - q = ngx_queue_head(frames); - do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - q = ngx_queue_next(q); ngx_quic_free_frame(c, f); - - } while (q != ngx_queue_sentinel(frames)); + } while (1); } @@ -2237,7 +2311,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) static void ngx_quic_push_handler(ngx_event_t *ev) { - ngx_connection_t *c; + ngx_connection_t *c; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "push timer"); @@ -2430,6 +2504,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) return NULL; } + ngx_queue_init(&sn->fs.frames); + log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { ngx_destroy_pool(pool); @@ -2595,6 +2671,8 @@ ngx_quic_stream_cleanup_handler(void *data) return; } + ngx_quic_free_frames(pc, &qs->fs.frames); + if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) { /* do not send fin for client unidirectional streams */ return; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index baf7fb36c..c843fbabe 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -29,33 +29,42 @@ typedef struct { /* configurable */ - ngx_msec_t max_idle_timeout; - ngx_msec_t max_ack_delay; - - ngx_uint_t max_packet_size; - ngx_uint_t initial_max_data; - ngx_uint_t initial_max_stream_data_bidi_local; - ngx_uint_t initial_max_stream_data_bidi_remote; - ngx_uint_t initial_max_stream_data_uni; - ngx_uint_t initial_max_streams_bidi; - ngx_uint_t initial_max_streams_uni; - ngx_uint_t ack_delay_exponent; - ngx_uint_t disable_active_migration; - ngx_uint_t active_connection_id_limit; + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + ngx_uint_t max_packet_size; + ngx_uint_t initial_max_data; + ngx_uint_t initial_max_stream_data_bidi_local; + ngx_uint_t initial_max_stream_data_bidi_remote; + ngx_uint_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t disable_active_migration; + ngx_uint_t active_connection_id_limit; /* TODO */ - ngx_uint_t original_connection_id; - u_char stateless_reset_token[16]; - void *preferred_address; + ngx_uint_t original_connection_id; + u_char stateless_reset_token[16]; + void *preferred_address; } ngx_quic_tp_t; +typedef struct { + uint64_t sent; + uint64_t received; + ngx_queue_t frames; /* reorder queue */ + size_t total; /* size of buffered data */ +} ngx_quic_frames_stream_t; + + struct ngx_quic_stream_s { - ngx_rbtree_node_t node; - ngx_connection_t *parent; - ngx_connection_t *c; - uint64_t id; - ngx_buf_t *b; + ngx_rbtree_node_t node; + ngx_connection_t *parent; + ngx_connection_t *c; + uint64_t id; + ngx_buf_t *b; + ngx_quic_frames_stream_t fs; }; -- cgit v1.2.3 From 53d47318c5fc181a8fa05f2ecc785fd14a4e5a8d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 14 Apr 2020 16:30:41 +0300 Subject: Sorted functions and functions declarations. --- src/event/ngx_event_quic.c | 47 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index aabbb6114..42a29e342 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -108,6 +108,10 @@ struct ngx_quic_connection_s { }; +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame); + + #if BORINGSSL_API_VERSION >= 10 static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, @@ -151,20 +155,17 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max); -static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, - ngx_quic_frame_t *f, uint64_t offset_in); -static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); - -typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, - ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler); +static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, + ngx_quic_frame_t *f, uint64_t offset_in); +static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); +static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, @@ -1420,20 +1421,6 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } -static ngx_int_t -ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) -{ - ngx_quic_connection_t *qc; - ngx_quic_frames_stream_t *fs; - - qc = c->quic; - fs = &qc->crypto[pkt->level]; - - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input); -} - - static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler) @@ -1644,6 +1631,20 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, } +static ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + ngx_quic_frames_stream_t *fs; + + qc = c->quic; + fs = &qc->crypto[pkt->level]; + + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input); +} + + static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) { -- cgit v1.2.3 From 001b81af88c5f67b789142cd07ba3f08e9f91102 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 15 Apr 2020 13:09:39 +0300 Subject: Free remaining frames on connection close. Frames can still float in the following queues: - crypto frames reordering queues (one per encryption level) - moved crypto frames cleanup to the moment where all streams are closed - stream frames reordering queues (one per packet number namespace) - frames retransmit queues (one per packet number namespace) --- src/event/ngx_event_quic.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 42a29e342..a85c14934 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -750,10 +750,6 @@ ngx_quic_close_connection(ngx_connection_t *c) if (qc) { - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { - ngx_quic_free_frames(c, &qc->crypto[i].frames); - } - qc->closing = 1; tree = &qc->streams.tree; @@ -793,6 +789,15 @@ ngx_quic_close_connection(ngx_connection_t *c) return; } + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_quic_free_frames(c, &qc->crypto[i].frames); + } + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); + } + if (qc->push.timer_set) { ngx_del_timer(&qc->push); } -- cgit v1.2.3 From 2e2d1843c3ec3057ed6b8657a9b483ef16cbae80 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 15 Apr 2020 14:29:00 +0300 Subject: Create new stream immediately on receiving new stream id. Before the patch, full STREAM frame handling was delayed until the frame with zero offset is received. Only node in the streams tree was created. This lead to problems when such stream was deleted, in particular, it had no handlers set for read events. This patch creates new stream immediately, but delays data delivery until the proper offset will arrive. This is somewhat similar to how accept() operation works. The ngx_quic_add_stream() function is no longer needed and merged into stream handler. The ngx_quic_stream_input() now only handles frames for existing streams and does not deal with stream creation. --- src/event/ngx_event_quic.c | 149 ++++++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 76 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a85c14934..1373e0cf7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -172,8 +172,6 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame); -static ngx_quic_stream_t *ngx_quic_add_stream(ngx_connection_t *c, - ngx_quic_stream_frame_t *f); static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, @@ -1742,6 +1740,9 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { + size_t n; + ngx_buf_t *b; + ngx_event_t *rev; ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -1753,10 +1754,66 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); if (sn == NULL) { - sn = ngx_quic_add_stream(c, f); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); + + n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->tp.initial_max_stream_data_uni + : qc->tp.initial_max_stream_data_bidi_remote; + + if (n < NGX_QUIC_STREAM_BUFSIZE) { + n = NGX_QUIC_STREAM_BUFSIZE; + } + + if (n < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + return NGX_ERROR; + } + + /* + * TODO: check IDs are increasing ? create all lower-numbered? + * + * 2.1. Stream Types and Identifiers + * + * Within each type, streams are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being + * opened. + */ + sn = ngx_quic_create_stream(c, f->stream_id, n); if (sn == NULL) { return NGX_ERROR; } + + rev = sn->c->read; + + if (f->offset == 0) { + + b = sn->b; + b->last = ngx_cpymem(b->last, f->data, f->length); + + sn->fs.received += f->length; + + rev->ready = 1; + + if (f->fin) { + rev->pending_eof = 1; + } + + } else { + rev->ready = 0; + } + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + ngx_quic_handle_max_streams(c); + } + + qc->streams.handler(sn->c); + + if (f->offset == 0) { + return NGX_OK; + } + + /* out-of-order stream: proceed to buffering */ } fs = &sn->fs; @@ -1779,49 +1836,26 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) f = &frame->u.stream; sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - if (sn == NULL) { // TODO: possible? - // deleted while stream is in reordering queue? + // stream was deleted while in reordering queue ? return NGX_ERROR; } - if (sn->fs.received != 0) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); - b = sn->b; - - if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); - return NGX_ERROR; - } - - if ((size_t) (b->end - b->last) < f->length) { - b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); - b->pos = b->start; - } - - b->last = ngx_cpymem(b->last, f->data, f->length); - - rev = sn->c->read; - rev->ready = 1; - - if (f->fin) { - rev->pending_eof = 1; - } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); - if (rev->active) { - rev->handler(rev); - } + b = sn->b; - /* check if stream was destroyed */ - if (ngx_quic_find_stream(&qc->streams.tree, f->stream_id) == NULL) { - return NGX_DONE; - } + if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + return NGX_ERROR; + } - return NGX_OK; + if ((size_t) (b->end - b->last) < f->length) { + b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; } - b = sn->b; b->last = ngx_cpymem(b->last, f->data, f->length); rev = sn->c->read; @@ -1831,13 +1865,11 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) rev->pending_eof = 1; } - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - ngx_quic_handle_max_streams(c); + if (rev->active) { + rev->handler(rev); } - qc->streams.handler(sn->c); - - /* check if stream was destroyed */ + /* check if stream was destroyed by handler */ if (ngx_quic_find_stream(&qc->streams.tree, f->stream_id) == NULL) { return NGX_DONE; } @@ -1846,41 +1878,6 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) } -static ngx_quic_stream_t * -ngx_quic_add_stream(ngx_connection_t *c, ngx_quic_stream_frame_t *f) -{ - size_t n; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = c->quic; - - // TODO: check increasing IDs - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); - - n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - ? qc->tp.initial_max_stream_data_uni - : qc->tp.initial_max_stream_data_bidi_remote; - - if (n < NGX_QUIC_STREAM_BUFSIZE) { - n = NGX_QUIC_STREAM_BUFSIZE; - } - - if (n < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); - return NULL; - } - - sn = ngx_quic_create_stream(c, f->stream_id, n); - if (sn == NULL) { - return NULL; - } - - return sn; -} - - static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c) { -- cgit v1.2.3 From 29b6ad00a2166e3b6e9fd159af18c11467e5ea08 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 15 Apr 2020 18:54:03 +0300 Subject: Added primitive flow control mechanisms. + MAX_STREAM_DATA frame is sent when recv() is performed on stream The new value is a sum of total bytes received by stream + free space in a buffer; The sending of MAX_STREM_DATA frame in response to STREAM_DATA_BLOCKED frame is adjusted to follow the same logic as above. + MAX_DATA frame is sent when total amount of received data is 2x of current limit. The limit is doubled. + Default values of transport parameters are adjusted to more meaningful values: initial stream limits are set to quic buffer size instead of unrealistically small 255. initial max data is decreased to 16 buffer sizes, in an assumption that this is enough for a relatively short connection, instead of randomly chosen big number. All this allows to initiate a stable flow of streams that does not block on stream/connection limits (tested with FF 77.0a1 and 100K requests) --- src/event/ngx_event_quic.c | 65 +++++++++++++++++++++++++++++++++--- src/event/ngx_event_quic_transport.c | 26 +++++++++++++++ src/http/v3/ngx_http_v3_module.c | 10 +++--- 3 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1373e0cf7..db28cd548 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -47,6 +47,9 @@ typedef struct { ngx_connection_handler_pt handler; ngx_uint_t id_counter; + + uint64_t total_received; + uint64_t max_data; } ngx_quic_streams_t; @@ -535,6 +538,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + qc->streams.max_data = qc->tp.initial_max_data; + qc->dcid.len = pkt->dcid.len; qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); if (qc->dcid.data == NULL) { @@ -1963,7 +1968,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, } b = sn->b; - n = (b->pos - b->start) + (b->end - b->last); + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { @@ -2559,13 +2564,18 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ssize_t len; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_quic_stream_t *qs; + ssize_t len; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; qs = c->qs; b = qs->b; + pc = qs->parent; + qc = pc->quic; rev = c->read; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -2589,6 +2599,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_memcpy(buf, b->pos, len); b->pos += len; + qc->streams.total_received += len; if (b->pos == b->last) { b->pos = b->start; @@ -2599,6 +2610,50 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv: %z of %uz", len, size); + if (!rev->pending_eof) { + frame = ngx_quic_alloc_frame(pc, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + + (b->end - b->last); + + ngx_sprintf(frame->info, "MAX_STREAM_DATA id:%d limit:%d l=%d on recv", + (int) frame->u.max_stream_data.id, + (int) frame->u.max_stream_data.limit, + frame->level); + + ngx_quic_queue_frame(pc->quic, frame); + } + + if ((qc->streams.max_data / 2) < qc->streams.total_received) { + + frame = ngx_quic_alloc_frame(pc, 0); + + if (frame == NULL) { + return NGX_ERROR; + } + + qc->streams.max_data *= 2; + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.max_data; + + ngx_sprintf(frame->info, "MAX_DATA max_data:%d level=%d on recv", + (int) frame->u.max_data.max_data, frame->level); + + ngx_quic_queue_frame(pc->quic, frame); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recv: increased max data: %ui", + qc->streams.max_data); + } + return len; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1e5a7ea8f..5dc0f1d72 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -77,6 +77,8 @@ static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); static size_t ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms); +static size_t ngx_quic_create_max_data(u_char *p, + ngx_quic_max_data_frame_t *md); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, @@ -1196,6 +1198,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_MAX_STREAM_DATA: return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + case NGX_QUIC_FT_MAX_DATA: + return ngx_quic_create_max_data(p, &f->u.max_data); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -1616,6 +1621,27 @@ ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) } +static size_t +ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); + len += ngx_quic_varint_len(md->max_data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); + ngx_quic_build_int(&p, md->max_data); + + return p - start; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) { diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 279210337..4306206c9 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -266,18 +266,20 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_data, - prev->quic.initial_max_data, 10000000); + prev->quic.initial_max_data, + 16 * NGX_QUIC_STREAM_BUFSIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local, prev->quic.initial_max_stream_data_bidi_local, - 255); + NGX_QUIC_STREAM_BUFSIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote, prev->quic.initial_max_stream_data_bidi_remote, - 255); + NGX_QUIC_STREAM_BUFSIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni, - prev->quic.initial_max_stream_data_uni, 255); + prev->quic.initial_max_stream_data_uni, + NGX_QUIC_STREAM_BUFSIZE); ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi, prev->quic.initial_max_streams_bidi, 16); -- cgit v1.2.3 From df01c0c13c0080986cf26222ba929d41eb47c286 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 16 Apr 2020 12:46:48 +0300 Subject: Parsing of truncated packet numbers. For sample decoding algorithm, see quic-transport-27#appendix-A. --- src/event/ngx_event_quic.c | 35 ++++++++++++++++++++-------- src/event/ngx_event_quic_protection.c | 43 ++++++++++++++++++++++++++++------- src/event/ngx_event_quic_protection.h | 3 ++- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index db28cd548..696f329b8 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -65,8 +65,9 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; - uint64_t pnum; + uint64_t pnum; /* packet number to send */ uint64_t largest_ack; /* number received from peer */ + uint64_t largest_pn; /* number received from peer */ ngx_queue_t frames; ngx_queue_t sent; @@ -473,6 +474,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_uint_t i; ngx_quic_tp_t *ctp; ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -510,6 +512,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ngx_queue_init(&qc->send_ctx[i].frames); ngx_queue_init(&qc->send_ctx[i].sent); + qc->send_ctx[i].largest_pn = (uint64_t) -1; } for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { @@ -574,7 +577,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, pkt->level = ssl_encryption_initial; pkt->plaintext = buf; - if (ngx_quic_decrypt(pkt, NULL) != NGX_OK) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { return NGX_ERROR; } @@ -907,9 +912,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_ssl_conn_t *ssl_conn; - ngx_quic_secrets_t *keys; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; c->log->action = "processing initial quic packet"; @@ -929,7 +935,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->level = ssl_encryption_initial; pkt->plaintext = buf; - if (ngx_quic_decrypt(pkt, ssl_conn) != NGX_OK) { + ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + + if (ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn) != NGX_OK) { return NGX_ERROR; } @@ -941,6 +949,7 @@ static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -995,7 +1004,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->level = ssl_encryption_handshake; pkt->plaintext = buf; - if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { return NGX_ERROR; } @@ -1007,6 +1018,7 @@ static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -1060,7 +1072,9 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->level = ssl_encryption_early_data; pkt->plaintext = buf; - if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { return NGX_ERROR; } @@ -1073,6 +1087,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_int_t rc; ngx_quic_secrets_t *keys, *next, tmp; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; @@ -1099,7 +1114,9 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->level = ssl_encryption_application; pkt->plaintext = buf; - if (ngx_quic_decrypt(pkt, c->ssl->connection) != NGX_OK) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 86300e68c..2ad5a72df 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -36,7 +36,8 @@ static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, const u_char *secret, size_t secret_len, const u_char *salt, size_t salt_len); -static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask); +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn); static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); @@ -870,20 +871,45 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, static uint64_t -ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn) { u_char *p; - uint64_t value; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = ngx_min(len * 8, 62); p = *pos; - value = *p++ ^ *mask++; + truncated_pn = *p++ ^ *mask++; while (--len) { - value = (value << 8) + (*p++ ^ *mask++); + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); } *pos = p; - return value; + + expected_pn = *largest_pn + 1; + pn_win = 1 << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) + && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + + } else if (candidate_pn > expected_pn + pn_hwin + && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); + + return candidate_pn; } @@ -910,7 +936,8 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_int_t -ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) +ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + uint64_t *largest_pn) { u_char clearflags, *p, *sample; uint64_t pn; @@ -960,7 +987,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn) } pnl = (clearflags & 0x03) + 1; - pn = ngx_quic_parse_pn(&p, pnl, &mask[1]); + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], largest_pn); pkt->pn = pn; diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index 800184182..b19f9f210 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -39,7 +39,8 @@ ngx_int_t ngx_quic_key_update(ngx_connection_t *c, ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); -ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, + uint64_t *largest_pn); #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ -- cgit v1.2.3 From f901747f29e0c9c65db06ae522469ad5d916611e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Apr 2020 12:17:41 +0300 Subject: Added handling of incorrect values in TP configuration. Some parameters have minimal/maximum values defined by standard. --- src/http/v3/ngx_http_v3_module.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 4306206c9..4b5f2f370 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -255,16 +255,30 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->quic.max_idle_timeout, prev->quic.max_idle_timeout, 60000); - // > 2 ^ 14 is invalid ngx_conf_merge_msec_value(conf->quic.max_ack_delay, prev->quic.max_ack_delay, NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - // < 1200 is invalid + if (conf->quic.max_ack_delay > 16384) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_ack_delay\" greater than" + " 16384 is invalid"); + return NGX_CONF_ERROR; + } + ngx_conf_merge_uint_value(conf->quic.max_packet_size, prev->quic.max_packet_size, NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); + if (conf->quic.max_packet_size < 1200 + || conf->quic.max_packet_size > 65527) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_packet_size\" less than" + " 1200 or greater than 65527 is invalid"); + return NGX_CONF_ERROR; + } + ngx_conf_merge_uint_value(conf->quic.initial_max_data, prev->quic.initial_max_data, 16 * NGX_QUIC_STREAM_BUFSIZE); @@ -287,18 +301,30 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni, prev->quic.initial_max_streams_uni, 16); - // > 20 is invalid ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent, prev->quic.ack_delay_exponent, NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); + if (conf->quic.ack_delay_exponent > 20) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_ack_delay_exponent\" greater than" + " 20 is invalid"); + return NGX_CONF_ERROR; + } + ngx_conf_merge_uint_value(conf->quic.disable_active_migration, prev->quic.disable_active_migration, 1); - // < 2 is invalid ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, prev->quic.active_connection_id_limit, 2); + if (conf->quic.active_connection_id_limit < 2) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_active_connection_id_limit\" less than" + " 2 is invalid"); + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } -- cgit v1.2.3 From d8ab79f6dbe5c1719cb0d579714489e659244e14 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Apr 2020 13:28:43 +0300 Subject: Removed outdated TODO. If required, frame handler can invoke output itself. There is no need to call output directly in the payload handler, queuing is enough. --- src/event/ngx_event_quic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 696f329b8..34a51f71f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1320,7 +1320,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level); ngx_quic_queue_frame(qc, ack_frame); - // TODO: call output() after processing some special frames? return NGX_OK; } -- cgit v1.2.3 From 7288ac486271ed598a662a4e5ebc9833d4f6b8db Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Apr 2020 16:54:22 +0300 Subject: Fixed error descriptions. The check for array bound is done inside function that returns error description. Missing initialization element is added. --- src/event/ngx_event_quic_transport.c | 17 ++++++++--------- src/event/ngx_event_quic_transport.h | 1 + 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 5dc0f1d72..e291c1215 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -99,9 +99,10 @@ static char *ngx_quic_errors[] = { "CONNECTION_ID_LIMIT_ERROR", "PROTOCOL_VIOLATION", "INVALID_TOKEN", - "", + "unknown error 0xC", "CRYPTO_BUFFER_EXCEEDED", - "", + "unknown error 0xE", + "unknown error 0xF", "CRYPTO_ERROR", }; @@ -266,6 +267,11 @@ ngx_quic_build_int(u_char **pos, uint64_t value) u_char * ngx_quic_error_text(uint64_t error_code) { + + if (error_code >= NGX_QUIC_ERR_LAST) { + return (u_char *) "unknown error"; + } + return (u_char *) ngx_quic_errors[error_code]; } @@ -777,13 +783,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - if (f->u.close.error_code >= NGX_QUIC_ERR_LAST) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "unkown error code: %ui, truncated", - f->u.close.error_code); - f->u.close.error_code = NGX_QUIC_ERR_LAST - 1; - } - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", ngx_quic_error_text(f->u.close.error_code), diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 116ec2a85..2c3753e50 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -75,6 +75,7 @@ /* 0xC is not defined */ #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D /* 0xE is not defined */ +/* 0xF is not defined */ #define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 #define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR -- cgit v1.2.3 From fa264b46b13d4065341cf5cb578a9b0c6ed2b1d3 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 17 Apr 2020 12:01:45 +0300 Subject: Revert "Rejecting new connections with non-zero Initial packet." chrome-unstable 83.0.4103.7 starts with Initial packet number 1. I couldn't find a proper explanation besides this text in quic-transport: An endpoint MAY skip packet numbers when sending packets to detect this (Optimistic ACK Attack) behavior. --- src/event/ngx_event_quic.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 34a51f71f..4c9a658a9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -583,12 +583,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - if (pkt->pn != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet number %L", pkt->pn); - return NGX_ERROR; - } - if (ngx_quic_init_connection(c) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 67aa3b91919096506b8504dc681264dd2f08fd0c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 20 Apr 2020 12:12:17 +0300 Subject: Removed source/destination swap from the function creating header. The function now creates a header according to fileds provided in the "pkt" argument without applying any logic regarding sending side. --- src/event/ngx_event_quic.c | 4 ++-- src/event/ngx_event_quic_transport.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4c9a658a9..990c1ec14 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2233,8 +2233,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) pkt.log = c->log; pkt.level = start->level; - pkt.dcid = qc->dcid; - pkt.scid = qc->scid; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; pkt.payload = out; res.data = dst; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index e291c1215..1a2cada35 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -365,12 +365,12 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION); - *p++ = pkt->scid.len; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); - *p++ = pkt->dcid.len; p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + if (pkt->level == ssl_encryption_initial) { ngx_quic_build_int(&p, pkt->token.len); } @@ -408,7 +408,7 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, *p++ = pkt->flags; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); *pnp = p; -- cgit v1.2.3 From baacacd845f8f9408e4515359021df23f7468352 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 20 Apr 2020 22:25:22 +0300 Subject: Respecting maximum packet size. The header size macros for long and short packets were fixed to provide correct values in bytes. Currently the sending code limits frames so they don't exceed max_packet_size. But it does not account the case when a single frame can exceed the limit. As a result of this patch, big payload (CRYPTO and STREAM) will be split into a number of smaller frames that fit into advertised max_packet_size (which specifies final packet size, after encryption). --- src/event/ngx_event_quic.c | 111 +++++++++++++++++++++++++++++++-------------- src/event/ngx_event_quic.h | 7 ++- 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 990c1ec14..2dd415f99 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -320,7 +320,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { u_char *p, *end; - size_t client_params_len; + size_t client_params_len, fsize, limit; const uint8_t *client_params; ngx_quic_frame_t *frame; ngx_connection_t *c; @@ -359,30 +359,55 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; } + if (qc->ctp.max_packet_size < NGX_QUIC_MIN_INITIAL_SIZE + || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "maximum packet size is invalid"); + return NGX_ERROR; + } + qc->client_tp_done = 1; } } + /* + * we need to fit at least 1 frame into a packet, thus account head/tail; + * 17 = 1 + 8x2 is max header for CRYPTO frame, with 1 byte for frame type + */ + limit = qc->ctp.max_packet_size - NGX_QUIC_MAX_LONG_HEADER - 17 + - EVP_GCM_TLS_TAG_LEN; + fs = &qc->crypto[level]; - frame = ngx_quic_alloc_frame(c, len); - if (frame == NULL) { - return 0; - } + p = (u_char *) data; + end = (u_char *) data + len; - ngx_memcpy(frame->data, data, len); + while (p < end) { - frame->level = level; - frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset += fs->sent; - frame->u.crypto.length = len; - frame->u.crypto.data = frame->data; + fsize = ngx_min(limit, (size_t) (end - p)); - fs->sent += len; + frame = ngx_quic_alloc_frame(c, fsize); + if (frame == NULL) { + return 0; + } - ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", len, level); + ngx_memcpy(frame->data, p, fsize); - ngx_quic_queue_frame(qc, frame); + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = fs->sent; + frame->u.crypto.length = fsize; + frame->u.crypto.data = frame->data; + + fs->sent += fsize; + p += fsize; + + ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", + fsize, level); + + ngx_quic_queue_frame(qc, frame); + } return 1; } @@ -478,7 +503,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; - if (ngx_buf_size(pkt->raw) < 1200) { + if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); return NGX_ERROR; } @@ -2671,6 +2696,8 @@ 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) { + u_char *p, *end; + size_t fsize, limit; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -2686,31 +2713,47 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); - frame = ngx_quic_alloc_frame(pc, size); - if (frame == NULL) { - return 0; - } + /* + * we need to fit at least 1 frame into a packet, thus account head/tail; + * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type + */ + limit = qc->ctp.max_packet_size - NGX_QUIC_MAX_SHORT_HEADER - 25 + - EVP_GCM_TLS_TAG_LEN; - ngx_memcpy(frame->data, buf, size); + p = (u_char *) buf; + end = (u_char *) buf + size; - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; + while (p < end) { - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = size; - frame->u.stream.data = frame->data; + fsize = ngx_min(limit, (size_t) (end - p)); - c->sent += size; + frame = ngx_quic_alloc_frame(pc, fsize); + if (frame == NULL) { + return 0; + } - ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", - qs->id, size, frame->level); + ngx_memcpy(frame->data, p, fsize); - ngx_quic_queue_frame(qc, frame); + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = fsize; + frame->u.stream.data = frame->data; + + c->sent += fsize; + p += fsize; + + ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", + qs->id, fsize, frame->level); + + ngx_quic_queue_frame(qc, frame); + } return size; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index c843fbabe..0dfc9b2e7 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -14,13 +14,16 @@ #define NGX_QUIC_DRAFT_VERSION 27 #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) -#define NGX_QUIC_MAX_SHORT_HEADER 25 -#define NGX_QUIC_MAX_LONG_HEADER 346 +#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ +#define NGX_QUIC_MAX_LONG_HEADER 56 + /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ #define NGX_QUIC_DEFAULT_MAX_PACKET_SIZE 65527 #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_MIN_INITIAL_SIZE 1200 + #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 -- cgit v1.2.3 From 07699053c03de69b5482c4cc2547993fae3982e6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 20 Apr 2020 17:18:04 +0300 Subject: Added MAX_STREAM_DATA stub handler. Currently sending code is ignoring this. --- src/event/ngx_event_quic.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2dd415f99..f3b76b3ee 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1290,6 +1290,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STOP_SENDING: case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_MAX_STREAM_DATA: /* TODO: handle */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1298,6 +1299,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; default: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "missing frame handler"); return NGX_ERROR; } } -- cgit v1.2.3 From 10bda6e11b91fe67c4489ab5e0347355fa6091cb Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Apr 2020 12:06:24 +0300 Subject: Fixed includes in quic headers. --- src/event/ngx_event_quic.h | 3 ++- src/event/ngx_event_quic_protection.h | 4 ++++ src/event/ngx_event_quic_transport.h | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 0dfc9b2e7..034cf48ca 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -8,7 +8,8 @@ #define _NGX_EVENT_QUIC_H_INCLUDED_ -#include +#include +#include #define NGX_QUIC_DRAFT_VERSION 27 diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index b19f9f210..befb7ef66 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -8,6 +8,10 @@ #define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#include +#include + + #define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 2c3753e50..761db57c1 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -8,7 +8,8 @@ #define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ -#include +#include +#include #define ngx_quic_long_pkt(flags) ((flags) & 0x80) /* 17.2 */ -- cgit v1.2.3 From f5036584840df54c066af8452fcc75b6af7ded97 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 20 Apr 2020 18:32:46 +0300 Subject: Fixed memory leak with reordered stream frames. --- src/event/ngx_event_quic.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f3b76b3ee..9bc7aab09 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2779,14 +2779,13 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream cleanup"); ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_quic_free_frames(pc, &qs->fs.frames); if (qc->closing) { ngx_post_event(pc->read, &ngx_posted_events); return; } - ngx_quic_free_frames(pc, &qs->fs.frames); - if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) { /* do not send fin for client unidirectional streams */ return; -- cgit v1.2.3 From 400eb1b628ce60625df5f6844899ca4cd6d8cbed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Apr 2020 17:11:49 +0300 Subject: HTTP/3: fixed encoding variable-length integers. --- src/http/v3/ngx_http_v3.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index cca84dbc1..f7f79b9f4 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -34,21 +34,25 @@ ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) if (value <= 1073741823) { if (p == NULL) { - return 3; + return 4; } - *p++ = 0x80 | (value >> 16); + *p++ = 0x80 | (value >> 24); + *p++ = (value >> 16); *p++ = (value >> 8); *p++ = value; return (uintptr_t) p; - } if (p == NULL) { - return 4; + return 8; } - *p++ = 0xc0 | (value >> 24); + *p++ = 0xc0 | (value >> 56); + *p++ = (value >> 48); + *p++ = (value >> 40); + *p++ = (value >> 32); + *p++ = (value >> 24); *p++ = (value >> 16); *p++ = (value >> 8); *p++ = value; -- cgit v1.2.3 From 9275f06a57fe43cbe79fc98c126817e591a0cef3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Apr 2020 17:52:32 +0300 Subject: Fixed QUIC buffer consumption in send_chain(). --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9bc7aab09..1965ed907 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2849,8 +2849,9 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, return in; } + b->pos += n; + if (n != (ssize_t) len) { - b->pos += n; return in; } } -- cgit v1.2.3 From db90ddcb9ed847b101ebb794bfadcd3ef829147c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Apr 2020 14:52:16 +0300 Subject: Improved ngx_quic_build_int() code and readability. The function now generates somewhat shorter assembler after inlining. --- src/event/ngx_event_quic_transport.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1a2cada35..19e7ba305 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -238,29 +238,23 @@ static void ngx_quic_build_int(u_char **pos, uint64_t value) { u_char *p; - ngx_uint_t len;//, len2; + ngx_uint_t bits, len; p = *pos; - len = 0; + bits = 0; - while (value >> ((1 << len) * 8 - 2)) { - len++; + while (value >> ((8 << bits) - 2)) { + bits++; } - *p = len << 6; + len = (1 << bits); -// len2 = - len = (1 << len); - len--; - *p |= value >> (len * 8); - p++; - - while (len) { - *p++ = value >> ((len-- - 1) * 8); + while (len--) { + *p++ = value >> (len * 8); } + **pos |= bits << 6; *pos = p; -// return len2; } -- cgit v1.2.3 From 89bba9bf7ddf0cfe448e817f9215686fbfb1ae94 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Apr 2020 15:48:39 +0300 Subject: HTTP/3: bytes holding directives changed to ngx_conf_set_size_slot. This allows to specify directive values with measurement units. --- src/event/ngx_event_quic.h | 10 +++++----- src/http/v3/ngx_http_v3_module.c | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 034cf48ca..9a42d2d41 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -36,11 +36,11 @@ typedef struct { ngx_msec_t max_idle_timeout; ngx_msec_t max_ack_delay; - ngx_uint_t max_packet_size; - ngx_uint_t initial_max_data; - ngx_uint_t initial_max_stream_data_bidi_local; - ngx_uint_t initial_max_stream_data_bidi_remote; - ngx_uint_t initial_max_stream_data_uni; + size_t max_packet_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; ngx_uint_t initial_max_streams_bidi; ngx_uint_t initial_max_streams_uni; ngx_uint_t ack_delay_exponent; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 4b5f2f370..1566706fc 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -28,35 +28,35 @@ static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("quic_max_packet_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.max_packet_size), NULL }, { ngx_string("quic_initial_max_data"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_data), NULL }, { ngx_string("quic_initial_max_stream_data_bidi_local"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_local), NULL }, { ngx_string("quic_initial_max_stream_data_bidi_remote"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_remote), NULL }, { ngx_string("quic_initial_max_stream_data_uni"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_uni), NULL }, @@ -231,11 +231,11 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC; v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC; - v3cf->quic.max_packet_size = NGX_CONF_UNSET_UINT; - v3cf->quic.initial_max_data = NGX_CONF_UNSET_UINT; - v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_UINT; - v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_UINT; - v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_UINT; + v3cf->quic.max_packet_size = NGX_CONF_UNSET_SIZE; + v3cf->quic.initial_max_data = NGX_CONF_UNSET_SIZE; + v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; + v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; + v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT; v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT; @@ -266,7 +266,7 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } - ngx_conf_merge_uint_value(conf->quic.max_packet_size, + ngx_conf_merge_size_value(conf->quic.max_packet_size, prev->quic.max_packet_size, NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); @@ -279,19 +279,19 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } - ngx_conf_merge_uint_value(conf->quic.initial_max_data, + ngx_conf_merge_size_value(conf->quic.initial_max_data, prev->quic.initial_max_data, 16 * NGX_QUIC_STREAM_BUFSIZE); - ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_local, + ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_local, prev->quic.initial_max_stream_data_bidi_local, NGX_QUIC_STREAM_BUFSIZE); - ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_bidi_remote, + ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_remote, prev->quic.initial_max_stream_data_bidi_remote, NGX_QUIC_STREAM_BUFSIZE); - ngx_conf_merge_uint_value(conf->quic.initial_max_stream_data_uni, + ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_uni, prev->quic.initial_max_stream_data_uni, NGX_QUIC_STREAM_BUFSIZE); -- cgit v1.2.3 From 37b95d545c396df2c4c7ef61a31d18e62288a019 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Apr 2020 15:59:19 +0300 Subject: HTTP/3: directives with limited values converted to post handler. The purpose is to show a precise line number with an invalid value. --- src/http/v3/ngx_http_v3_module.c | 80 +++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 1566706fc..9daaedb3e 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,6 +10,21 @@ #include +static char *ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data); +static char *ngx_http_v3_max_packet_size(ngx_conf_t *cf, void *post, + void *data); + + +static ngx_conf_post_t ngx_http_v3_max_ack_delay_post = + { ngx_http_v3_max_ack_delay }; +static ngx_conf_post_t ngx_http_v3_max_packet_size_post = + { ngx_http_v3_max_packet_size }; +static ngx_conf_num_bounds_t ngx_http_v3_ack_delay_exponent_bounds = + { ngx_conf_check_num_bounds, 0, 20 }; +static ngx_conf_num_bounds_t ngx_http_v3_active_connection_id_limit_bounds = + { ngx_conf_check_num_bounds, 2, -1 }; + + static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("quic_max_idle_timeout"), @@ -24,14 +39,14 @@ static ngx_command_t ngx_http_v3_commands[] = { ngx_conf_set_msec_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay), - NULL }, + &ngx_http_v3_max_ack_delay_post }, { ngx_string("quic_max_packet_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.max_packet_size), - NULL }, + &ngx_http_v3_max_packet_size_post }, { ngx_string("quic_initial_max_data"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, @@ -80,7 +95,7 @@ static ngx_command_t ngx_http_v3_commands[] = { ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.ack_delay_exponent), - NULL }, + &ngx_http_v3_ack_delay_exponent_bounds }, { ngx_string("quic_active_migration"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, @@ -94,7 +109,7 @@ static ngx_command_t ngx_http_v3_commands[] = { ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), - NULL }, + &ngx_http_v3_active_connection_id_limit_bounds }, ngx_null_command }; @@ -259,26 +274,10 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->quic.max_ack_delay, NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - if (conf->quic.max_ack_delay > 16384) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_ack_delay\" greater than" - " 16384 is invalid"); - return NGX_CONF_ERROR; - } - ngx_conf_merge_size_value(conf->quic.max_packet_size, prev->quic.max_packet_size, NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); - if (conf->quic.max_packet_size < 1200 - || conf->quic.max_packet_size > 65527) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_packet_size\" less than" - " 1200 or greater than 65527 is invalid"); - return NGX_CONF_ERROR; - } - ngx_conf_merge_size_value(conf->quic.initial_max_data, prev->quic.initial_max_data, 16 * NGX_QUIC_STREAM_BUFSIZE); @@ -305,26 +304,47 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->quic.ack_delay_exponent, NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - if (conf->quic.ack_delay_exponent > 20) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_ack_delay_exponent\" greater than" - " 20 is invalid"); - return NGX_CONF_ERROR; - } - ngx_conf_merge_uint_value(conf->quic.disable_active_migration, prev->quic.disable_active_migration, 1); ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, prev->quic.active_connection_id_limit, 2); - if (conf->quic.active_connection_id_limit < 2) { + return NGX_CONF_OK; +} + + +static char * +ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data) +{ + ngx_msec_t *sp = data; + + if (*sp > 16384) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_active_connection_id_limit\" less than" - " 2 is invalid"); + "\"quic_max_ack_delay\" must be less than 16384"); + return NGX_CONF_ERROR; } return NGX_CONF_OK; } + +static char * +ngx_http_v3_max_packet_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_QUIC_MIN_INITIAL_SIZE + || *sp > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_packet_size\" must be between %d and %d", + NGX_QUIC_MIN_INITIAL_SIZE, + NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} -- cgit v1.2.3 From c8edca31379455d4b2f7b6feae6536c3bd30a4de Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Apr 2020 11:15:44 +0300 Subject: Refactored ngx_quic_close_connection(). The function is split into three: ngx_quic_close_connection() itself cleans up all core nginx things ngx_quic_close_quic() deals with everything inside c->quic ngx_quic_close_streams() deals with streams cleanup The quic and streams cleanup functions may return NGX_AGAIN, thus signalling that cleanup is not ready yet, and the close cannot continue to next step. --- src/event/ngx_event_quic.c | 151 ++++++++++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 62 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1965ed907..5ef9cf47e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -141,7 +141,11 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_connection_handler_pt handler); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); + static void ngx_quic_close_connection(ngx_connection_t *c); +static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c); +static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, + ngx_quic_connection_t *qc); static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, @@ -760,95 +764,118 @@ ngx_quic_input_handler(ngx_event_t *rev) static void ngx_quic_close_connection(ngx_connection_t *c) { -#if (NGX_DEBUG) - ngx_uint_t ns; -#endif - ngx_uint_t i; - ngx_pool_t *pool; - ngx_event_t *rev; - ngx_rbtree_t *tree; - ngx_rbtree_node_t *node; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + ngx_pool_t *pool; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection"); - qc = c->quic; - - if (qc) { + if (c->quic && ngx_quic_close_quic(c) == NGX_AGAIN) { + return; + } - qc->closing = 1; - tree = &qc->streams.tree; + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } - if (tree->root != tree->sentinel) { - if (c->read->timer_set) { - ngx_del_timer(c->read); - } + if (c->read->timer_set) { + ngx_del_timer(c->read); + } -#if (NGX_DEBUG) - ns = 0; +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; + c->destroyed = 1; - rev = qs->c->read; - rev->ready = 1; - rev->pending_eof = 1; + pool = c->pool; - ngx_post_event(rev, &ngx_posted_events); + ngx_close_connection(c); - if (rev->timer_set) { - ngx_del_timer(rev); - } + ngx_destroy_pool(pool); +} -#if (NGX_DEBUG) - ns++; -#endif - } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic connection has %ui active streams", ns); +static ngx_int_t +ngx_quic_close_quic(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_quic_connection_t *qc; - return; - } + qc = c->quic; - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { - ngx_quic_free_frames(c, &qc->crypto[i].frames); - } + qc->closing = 1; - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ngx_quic_free_frames(c, &qc->send_ctx[i].frames); - ngx_quic_free_frames(c, &qc->send_ctx[i].sent); - } + if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + return NGX_AGAIN; + } - if (qc->push.timer_set) { - ngx_del_timer(&qc->push); - } + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_quic_free_frames(c, &qc->crypto[i].frames); + } - if (qc->retry.timer_set) { - ngx_del_timer(&qc->retry); - } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } - if (c->ssl) { - (void) ngx_ssl_shutdown(c); + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); } -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, -1); + if (qc->retry.timer_set) { + ngx_del_timer(&qc->retry); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_event_t *rev; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + +#if (NGX_DEBUG) + ngx_uint_t ns; #endif - c->destroyed = 1; + tree = &qc->streams.tree; - pool = c->pool; + if (tree->root == tree->sentinel) { + return NGX_OK; + } - ngx_close_connection(c); +#if (NGX_DEBUG) + ns = 0; +#endif - ngx_destroy_pool(pool); + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + rev = qs->c->read; + rev->ready = 1; + rev->pending_eof = 1; + + ngx_post_event(rev, &ngx_posted_events); + + if (rev->timer_set) { + ngx_del_timer(rev); + } + +#if (NGX_DEBUG) + ns++; +#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has %ui active streams", ns); + + return NGX_AGAIN; } -- cgit v1.2.3 From 26b7056972b52c210d43ff52d018b952cce109a5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Apr 2020 13:41:08 +0300 Subject: Added proper handling of connection close phases. There are following flags in quic connection: closing - true, when a connection close is initiated, for whatever reason draining - true, when a CC frame is received from peer The following state machine is used for closing: +------------------+ | I/HS/AD | +------------------+ | | | | | V | | immediate close initiated: | | reasons: close by top-level protocol, fatal error | | + sends CC (probably with app-level message) | | + starts close_timer: 3 * PTO (current probe timeout) | | | | | V | | +---------+ - Reply to input with CC (rate-limited) | | | CLOSING | - Close/Reset all streams | | +---------+ | | | | | V V | | receives CC | | | | idle | | timer | | | V | | +----------+ | - MUST NOT send anything (MAY send a single CC) | | DRAINING | | - if not already started, starts close_timer: 3 * PTO | +----------+ | - if not already done, close all streams | | | | | | | close_timer fires | | V V +------------------------+ | CLOSED | - clean up all the resources, drop connection +------------------------+ state completely The ngx_quic_close_connection() function gets an "rc" argument, that signals reason of connection closing: NGX_OK - initiated by application (i.e. http/3), follow state machine NGX_DONE - timedout (while idle or draining) NGX_ERROR - fatal error, destroy connection immediately The PTO calculations are not yet implemented, hardcoded value of 5s is used. --- src/event/ngx_event_quic.c | 215 +++++++++++++++++++++++++++++++++++++-------- src/event/ngx_event_quic.h | 3 + 2 files changed, 182 insertions(+), 36 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5ef9cf47e..603e88d8d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -94,7 +94,9 @@ struct ngx_quic_connection_s { ngx_event_t push; ngx_event_t retry; + ngx_event_t close; ngx_queue_t free_frames; + ngx_msec_t last_cc; #if (NGX_DEBUG) ngx_uint_t nframes; @@ -108,6 +110,7 @@ struct ngx_quic_connection_s { unsigned send_timer_set:1; unsigned closing:1; + unsigned draining:1; unsigned key_phase:1; }; @@ -142,8 +145,9 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); -static void ngx_quic_close_connection(ngx_connection_t *c); -static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c); +static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); +static void ngx_quic_close_timer_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc); @@ -158,6 +162,8 @@ static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, + enum ssl_encryption_level_t level, ngx_uint_t err); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); @@ -435,7 +441,6 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert) { ngx_connection_t *c; - ngx_quic_frame_t *frame; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -443,19 +448,11 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, "ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); - frame = ngx_quic_alloc_frame(c, 0); - if (frame == NULL) { - return 0; + if (c->quic == NULL) { + return 1; } - frame->level = level; - frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; - frame->u.close.error_code = 0x100 + alert; - ngx_sprintf(frame->info, "cc from send_alert level=%d", frame->level); - - ngx_quic_queue_frame(c->quic, frame); - - if (ngx_quic_output(c) != NGX_OK) { + if (ngx_quic_send_cc(c, level, 0x100 + alert) != NGX_OK) { return 0; } @@ -484,7 +481,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, pkt.len = b->last - b->start; if (ngx_quic_new_connection(c, ssl, tp, &pkt, handler) != NGX_OK) { - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -721,38 +718,36 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); - if (qc->closing) { - ngx_quic_close_connection(c); - return; - } - if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_DONE); return; } if (c->close) { - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } n = c->recv(c, b.start, b.end - b.start); if (n == NGX_AGAIN) { + if (qc->closing) { + ngx_quic_close_connection(c, NGX_OK); + } return; } if (n == NGX_ERROR) { c->read->eof = 1; - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } b.last += n; if (ngx_quic_input(c, &b) != NGX_OK) { - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -762,13 +757,18 @@ ngx_quic_input_handler(ngx_event_t *rev) static void -ngx_quic_close_connection(ngx_connection_t *c) +ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) { ngx_pool_t *pool; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "close quic connection"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection, rc: %i", rc); - if (c->quic && ngx_quic_close_quic(c) == NGX_AGAIN) { + if (!c->quic) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "close quic connection: early error"); + + } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { return; } @@ -795,19 +795,85 @@ ngx_quic_close_connection(ngx_connection_t *c) static ngx_int_t -ngx_quic_close_quic(ngx_connection_t *c) +ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { - ngx_uint_t i; - ngx_quic_connection_t *qc; + ngx_uint_t i; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; qc = c->quic; - qc->closing = 1; + if (!qc->closing) { + + if (rc == NGX_OK) { + + /* + * 10.3. Immediate Close + * + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) to + * terminate the connection immediately. + */ + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close, drain = %d", qc->draining); + + switch (qc->state) { + case NGX_QUIC_ST_INITIAL: + level = ssl_encryption_initial; + break; + + case NGX_QUIC_ST_HANDSHAKE: + level = ssl_encryption_handshake; + break; + + default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ + level = ssl_encryption_application; + break; + } + + if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR) == NGX_OK) { + + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_timer_handler; + qc->close.cancelable = 1; + + ngx_add_timer(&qc->close, 3 * NGX_QUIC_HARDCODED_PTO); + } + + } else if (rc == NGX_DONE) { + + /* + * 10.2. Idle Timeout + * + * If the idle timeout is enabled by either peer, a connection is + * silently closed and its state is discarded when it remains idle + */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic closing %s connection", + qc->draining ? "drained" : "idle"); + + } else { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to fatal error"); + } + + qc->closing = 1; + } + + if (rc == NGX_ERROR && qc->close.timer_set) { + /* do not wait for timer in case of fatal error */ + ngx_del_timer(&qc->close); + } if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { return NGX_AGAIN; } + if (qc->close.timer_set) { + return NGX_AGAIN; + } + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { ngx_quic_free_frames(c, &qc->crypto[i].frames); } @@ -825,10 +891,28 @@ ngx_quic_close_quic(ngx_connection_t *c) ngx_del_timer(&qc->retry); } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic part of connection is terminated"); + + /* may be tested from SSL callback during SSL shutdown */ + c->quic = NULL; + return NGX_OK; } +static void +ngx_quic_close_timer_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "close timer"); + + c = ev->data; + ngx_quic_close_connection(c, NGX_DONE); +} + + static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { @@ -1203,9 +1287,20 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_frame_t frame, *ack_frame; ngx_quic_connection_t *qc; - qc = c->quic; + if (qc->closing) { + /* + * 10.1 Closing and Draining Connection States + * ... delayed or reordered packets are properly discarded. + * + * An endpoint retains only enough information to generate + * a packet containing a CONNECTION_CLOSE frame and to identify + * packets as belonging to the connection. + */ + return ngx_quic_send_cc(c, pkt->level, NGX_QUIC_ERR_NO_ERROR); + } + p = pkt->payload.data; end = p + pkt->payload.len; @@ -1339,7 +1434,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (do_close) { - return NGX_DONE; + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + return NGX_OK; } if (ack_this == 0) { @@ -1373,6 +1470,45 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, + ngx_uint_t err) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (qc->draining) { + return NGX_OK; + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = err; + ngx_sprintf(frame->info, "cc from send_cc err=%ui level=%d", err, + frame->level); + + ngx_quic_queue_frame(c->quic, frame); + + qc->last_cc = ngx_current_msec; + + return ngx_quic_output(c); +} + + static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) @@ -2157,7 +2293,13 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) * frames are moved into the sent queue * to wait for ack/be retransmitted */ - ngx_queue_add(&ctx->sent, &range); + if (qc->closing) { + /* if we are closing, any ack will be discarded */ + ngx_quic_free_frames(c, &range); + + } else { + ngx_queue_add(&ctx->sent, &range); + } } else if (rc == NGX_DONE) { @@ -2363,7 +2505,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { if (ngx_quic_retransmit(c, &qc->send_ctx[i], &nswait) != NGX_OK) { - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -2391,7 +2533,7 @@ ngx_quic_push_handler(ngx_event_t *ev) c = ev->data; if (ngx_quic_output(c) != NGX_OK) { - ngx_quic_close_connection(c); + ngx_quic_close_connection(c, NGX_ERROR); return; } } @@ -2809,6 +2951,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_free_frames(pc, &qs->fs.frames); if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ ngx_post_event(pc->read, &ngx_posted_events); return; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 9a42d2d41..ec9e289fb 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -23,6 +23,9 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */ +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + #define NGX_QUIC_MIN_INITIAL_SIZE 1200 #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 -- cgit v1.2.3 From e34161c3d52f84ec9a07bcf7bb94e1ac5a81ce63 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Apr 2020 11:50:20 +0300 Subject: Removed support of drafts older than currently latest 27. --- src/event/ngx_event_quic.c | 6 --- src/event/ngx_event_quic_transport.c | 92 +----------------------------------- 2 files changed, 1 insertion(+), 97 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 603e88d8d..2e31ac9c4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1903,10 +1903,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "handshake completed successfully"); -#if (NGX_QUIC_DRAFT_VERSION >= 27) - { - ngx_quic_frame_t *frame; - frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; @@ -1917,8 +1913,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed"); ngx_quic_queue_frame(c->quic, frame); - } -#endif /* * Generating next keys before a key update is received. diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 19e7ba305..fdaf8b88e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -61,7 +61,6 @@ static u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...); static void ngx_quic_build_int(u_char **pos, uint64_t value); static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); -/*static*/ u_char *ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value); // usage depends on NGX_QUIC_VERSION static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out); @@ -182,19 +181,6 @@ ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) } -/*static*/ ngx_inline u_char * -ngx_quic_read_uint16(u_char *pos, u_char *end, uint16_t *value) -{ - if ((size_t)(end - pos) < sizeof(uint16_t)) { - return NULL; - } - - *value = ngx_quic_parse_uint16(pos); - - return pos + sizeof(uint16_t); -} - - static ngx_inline u_char * ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) { @@ -1452,55 +1438,9 @@ ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log) { + uint64_t id, len; ngx_int_t rc; -#if (NGX_QUIC_DRAFT_VERSION < 27) - - uint16_t id, len, tp_len; - - p = ngx_quic_read_uint16(p, end, &tp_len); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse total transport params length"); - return NGX_ERROR; - } - - while (p < end) { - - p = ngx_quic_read_uint16(p, end, &id); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id"); - return NGX_ERROR; - } - - p = ngx_quic_read_uint16(p, end, &len); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id 0x%xi length", id); - return NGX_ERROR; - } - - rc = ngx_quic_parse_transport_param(p, p + len, id, tp); - - if (rc == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id 0x%xi data", id); - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "unknown transport param id 0x%xi, skipped", id); - } - - p += len; - }; - -#else - - uint64_t id, len; - while (p < end) { p = ngx_quic_parse_int(p, end, &id); if (p == NULL) { @@ -1530,11 +1470,8 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, } p += len; - } -#endif - if (p != end) { ngx_log_error(NGX_LOG_INFO, log, 0, "trailing garbage in transport parameters: %ui bytes", @@ -1542,7 +1479,6 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, return NGX_ERROR; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "client transport parameters parsed successfully"); @@ -1641,22 +1577,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) u_char *p; size_t len; -#if (NGX_QUIC_DRAFT_VERSION < 27) - -/* older drafts with static transport parameters encoding */ - -#define ngx_quic_tp_len(id, value) \ - 4 + ngx_quic_varint_len(value) - -#define ngx_quic_tp_vint(id, value) \ - do { \ - p = ngx_quic_write_uint16(p, id); \ - p = ngx_quic_write_uint16(p, ngx_quic_varint_len(value)); \ - ngx_quic_build_int(&p, value); \ - } while (0) - -#else - /* recent drafts with variable integer transport parameters encoding */ #define ngx_quic_tp_len(id, value) \ @@ -1671,8 +1591,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_build_int(&p, value); \ } while (0) -#endif - p = pos; len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, @@ -1699,17 +1617,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) tp->max_idle_timeout); if (pos == NULL) { -#if (NGX_QUIC_DRAFT_VERSION < 27) - len += 2; -#endif return len; } -#if (NGX_QUIC_DRAFT_VERSION < 27) - /* TLS extension length */ - p = ngx_quic_write_uint16(p, len); -#endif - ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, tp->active_connection_id_limit); -- cgit v1.2.3 From 936f577967f6436fbe719274a1896fb4f0a1a9b5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Apr 2020 12:10:56 +0300 Subject: Retired the ngx_quic_parse_int_multi() function. It used variable-length arguments what is not really necessary. --- src/event/ngx_event_quic_transport.c | 111 ++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index fdaf8b88e..546a25168 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -57,7 +57,6 @@ static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); -static u_char *ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...); static void ngx_quic_build_int(u_char **pos, uint64_t value); static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); @@ -138,36 +137,6 @@ ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) } -static ngx_inline u_char * -ngx_quic_parse_int_multi(u_char *pos, u_char *end, ...) -{ - u_char *p; - va_list ap; - uint64_t *item; - - p = pos; - - va_start(ap, end); - - do { - item = va_arg(ap, uint64_t *); - if (item == NULL) { - break; - } - - p = ngx_quic_parse_int(p, end, item); - if (p == NULL) { - return NULL; - } - - } while (1); - - va_end(ap); - - return p; -} - - static ngx_inline u_char * ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) { @@ -621,10 +590,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.ack.largest, - &f->u.ack.delay, &f->u.ack.range_count, - &f->u.ack.first_range, NULL); - if (p == NULL) { + if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) + { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse ack frame"); return NGX_ERROR; @@ -634,7 +604,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, /* process all ranges to get bounds, values are ignored */ for (i = 0; i < f->u.ack.range_count; i++) { - p = ngx_quic_parse_int_multi(p, end, &varint, &varint, NULL); + + p = ngx_quic_parse_int(p, end, &varint); + if (p) { + p = ngx_quic_parse_int(p, end, &varint); + } + if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse ack frame range %ui", i); @@ -653,9 +628,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_ACK_ECN) { - p = ngx_quic_parse_int_multi(p, end, &f->u.ack.ect0, - &f->u.ack.ect1, &f->u.ack.ce, NULL); - if (p == NULL) { + if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) + { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse ack frame ECT counts", i); return NGX_ERROR; @@ -680,11 +656,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.ncid.seqnum, - &f->u.ncid.retire, NULL); + p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id frame"); + "failed to parse new connection id frame seqnum"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse new connection id frame retire"); return NGX_ERROR; } @@ -871,10 +853,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.reset_stream.id, - &f->u.reset_stream.error_code, - &f->u.reset_stream.final_size, NULL); - if (p == NULL) { + if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) + && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) + && (p = ngx_quic_parse_int(p, end, + &f->u.reset_stream.final_size)))) + { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "failed to parse reset stream frame"); return NGX_ERROR; @@ -893,11 +876,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.stop_sending.id, - &f->u.stop_sending.error_code, NULL); + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse stop sending frame id"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stop sending frame"); + "failed to parse stop sending frame error code"); return NGX_ERROR; } @@ -976,11 +965,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.max_stream_data.id, - &f->u.max_stream_data.limit, NULL); + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse max stream data frame"); + "failed to parse max stream data frame data id"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse max stream data frame data limit"); return NGX_ERROR; } @@ -1014,11 +1009,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto not_allowed; } - p = ngx_quic_parse_int_multi(p, end, &f->u.stream_data_blocked.id, - &f->u.stream_data_blocked.limit, NULL); + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "failed to parse tream data blocked frame id"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse tream data blocked frame"); + "failed to parse tream data blocked frame limit"); return NGX_ERROR; } -- cgit v1.2.3 From ed506f8e15d2b84b3122d692299755e2731b3c79 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Apr 2020 12:25:00 +0300 Subject: TODOs cleanup in transport. We always generate stream frames that have length. The 'len' member is used during parsing incoming frames and can be safely ignored when generating output. --- src/event/ngx_event_quic_transport.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 546a25168..150deef98 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1267,15 +1267,6 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) size_t len; u_char *start; - if (!sf->len) { -#if 0 - ngx_log_error(NGX_LOG_INFO, log, 0, - "attempt to generate a stream frame without length"); -#endif - // XXX: handle error in caller - return NGX_ERROR; - } - if (p == NULL) { len = ngx_quic_varint_len(sf->type); @@ -1345,7 +1336,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, case NGX_QUIC_TP_ORIGINAL_CONNECTION_ID: case NGX_QUIC_TP_STATELESS_RESET_TOKEN: case NGX_QUIC_TP_PREFERRED_ADDRESS: - // TODO + /* TODO: implement */ return NGX_DECLINED; } -- cgit v1.2.3 From a9ef6ed17d1ed1e1e78d47aecf178d86bb1b3b8d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 24 Apr 2020 10:11:47 +0300 Subject: Debug cleanup. + all dumps are moved under one of the following macros (undefined by default): NGX_QUIC_DEBUG_PACKETS NGX_QUIC_DEBUG_FRAMES NGX_QUIC_DEBUG_FRAMES_ALLOC NGX_QUIC_DEBUG_CRYPTO + all QUIC debug messages got "quic " prefix + all input frames are reported as "quic frame in FOO_FRAME bar:1 baz:2" + all outgoing frames re reported as "quic frame out foo bar baz" + all stream operations are prefixed with id, like: "quic stream id 0x33 recv" + all transport parameters are prefixed with "quic tp" (hex dump is moved to caller, to avoid using ngx_cycle->log) + packet flags and some other debug messages are updated to include packet type --- src/event/ngx_event_quic.c | 144 +++++++++++++++++++++------------- src/event/ngx_event_quic.h | 6 ++ src/event/ngx_event_quic_protection.c | 34 ++++++-- src/event/ngx_event_quic_transport.c | 104 ++++++++++++++---------- 4 files changed, 184 insertions(+), 104 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2e31ac9c4..3ed3501d6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -251,8 +251,10 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "level:%d read secret", rsecret, secret_len, level); +#endif keys = &c->quic->keys[level]; @@ -276,8 +278,10 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "level:%d write secret", wsecret, secret_len, level); +#endif keys = &c->quic->keys[level]; @@ -299,7 +303,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); +#endif keys = &c->quic->keys[level]; @@ -315,7 +321,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, return 1; } +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); +#endif return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, wsecret, secret_len, @@ -341,7 +349,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc = c->quic; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_add_handshake_data"); + "quic ngx_quic_add_handshake_data"); /* XXX: obtain client parameters after the handshake? */ if (!qc->client_tp_done) { @@ -350,8 +358,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, &client_params_len); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_get_peer_quic_transport_params(): params_len %ui", - client_params_len); + "quic SSL_get_peer_quic_transport_params():" + " params_len %ui", client_params_len); if (client_params_len != 0) { p = (u_char *) client_params; @@ -430,7 +438,8 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_flush_flight()"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); return 1; } @@ -445,7 +454,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_send_alert(), lvl=%d, alert=%d", + "quic ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); if (c->quic == NULL) { @@ -666,6 +675,10 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump0(c->log, "quic transport parameters", p, len); +#endif + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "SSL_set_quic_transport_params() failed"); @@ -677,13 +690,14 @@ ngx_quic_init_connection(ngx_connection_t *c) n = SSL_do_handshake(ssl_conn); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_do_handshake: %d", n); if (n == -1) { sslerr = SSL_get_error(ssl_conn, n); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_get_error: %d", sslerr); if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); @@ -692,7 +706,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); @@ -762,11 +776,11 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_pool_t *pool; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "close quic connection, rc: %i", rc); + "quic ngx_quic_close_connection, rc: %i", rc); if (!c->quic) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "close quic connection: early error"); + "quic close connection early error"); } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { return; @@ -906,7 +920,7 @@ ngx_quic_close_timer_handler(ngx_event_t *ev) { ngx_connection_t *c; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "close timer"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer"); c = ev->data; ngx_quic_close_connection(c, NGX_DONE); @@ -1416,13 +1430,13 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) /* TODO: handle */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "frame handler not implemented"); + "quic frame handler not implemented"); ack_this = 1; break; default: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "missing frame handler"); + "quic missing frame handler"); return NGX_ERROR; } } @@ -1522,7 +1536,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_handle_ack_frame level %d", pkt->level); + "quic ngx_quic_handle_ack_frame level %d", pkt->level); /* * TODO: If any computed packet number is negative, an endpoint MUST @@ -1547,7 +1561,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (ctx->largest_ack < max) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "updated largest received: %ui", max); + "quic updated largest received ack: %ui", max); } pos = ack->ranges_start; @@ -1643,7 +1657,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, if (f->offset > fs->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "out-of-order frame: expecting %ui got %ui", + "quic out-of-order frame: expecting %ui got %ui", fs->received, f->offset); return ngx_quic_buffer_frame(c, fs, frame); @@ -1704,7 +1718,8 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, fs->total -= f->length; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "skipped buffered frame, total %ui", fs->total); + "quic skipped buffered frame, total %ui", + fs->total); ngx_quic_free_frame(c, frame); continue; } @@ -1730,7 +1745,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_queue_remove(q); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "consumed buffered frame, total %ui", fs->total); + "quic consumed buffered frame, total %ui", fs->total); ngx_quic_free_frame(c, frame); @@ -1755,12 +1770,12 @@ ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, /* range preceeding already received data or duplicate, ignore */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "old or duplicate data in ordered frame, ignored"); + "quic old or duplicate data in ordered frame, ignored"); return NGX_DONE; } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "adjusted ordered frame data start to expected offset"); + "quic adjusted ordered frame data start to expected offset"); /* intersecting range: adjust data size */ @@ -1781,7 +1796,8 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *dst, *item; ngx_quic_ordered_frame_t *f, *df; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_buffer_frame"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_buffer_frame"); f = &frame->u.ord; @@ -1811,8 +1827,8 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, fs->total += f->length; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ordered frame with unexpected offset: buffered, total %ui", - fs->total); + "quic ordered frame with unexpected offset:" + " buffered, total %ui", fs->total); /* TODO: do we need some timeout for this queue ? */ @@ -1867,7 +1883,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) ssl_conn = c->ssl->connection; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); @@ -1901,7 +1917,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "handshake completed successfully"); + "quic handshake completed successfully"); frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { @@ -1928,7 +1944,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "SSL_quic_read_level: %d, SSL_quic_write_level: %d", + "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); @@ -1954,7 +1970,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); if (sn == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "stream is new"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi is new", f->stream_id); n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ? qc->tp.initial_max_stream_data_uni @@ -2042,7 +2059,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) return NGX_ERROR; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "existing stream"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic existing stream"); b = sn->b; @@ -2350,7 +2367,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; static u_char dst[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "ngx_quic_send_frames"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_frames"); q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -2368,7 +2386,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "frame: %s", f->info); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic frame out: %s", f->info); len = ngx_quic_create_frame(p, f); if (len == -1) { @@ -2398,10 +2417,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) out.len++; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "packet ready: %ui bytes at level %d need_ack: %ui", - out.len, start->level, pkt.need_ack); - qc = c->quic; keys = &c->quic->keys[start->level]; @@ -2430,11 +2445,19 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) res.data = dst; + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet ready: %ui bytes at level %d" + " need_ack: %d number: %L encoded %d:0x%xD", + out.len, start->level, pkt.need_ack, pkt.number, + pkt.num_len, pkt.trunc); + if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) { return NGX_ERROR; } +#ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); +#endif len = c->send(c, res.data, res.len); if (len == NGX_ERROR || (size_t) len != res.len) { @@ -2490,7 +2513,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) ngx_quic_connection_t *qc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "retransmit timer"); + "quic retransmit timer"); c = ev->data; qc = c->quic; @@ -2522,7 +2545,7 @@ ngx_quic_push_handler(ngx_event_t *ev) { ngx_connection_t *c; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "push timer"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); c = ev->data; @@ -2619,7 +2642,7 @@ ngx_quic_create_uni_stream(ngx_connection_t *c) | NGX_QUIC_STREAM_UNIDIRECTIONAL; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "creating server uni stream #%ui id %ui", + "quic creating server uni stream #%ui id %ui", qc->streams.id_counter, id); qc->streams.id_counter++; @@ -2779,9 +2802,9 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) qc = pc->quic; rev = c->read; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: eof:%d, avail:%z", - rev->pending_eof, b->last - b->pos); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi recv: eof:%d, avail:%z", + qs->id, rev->pending_eof, b->last - b->pos); if (b->pos == b->last) { rev->ready = 0; @@ -2791,7 +2814,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return 0; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic recv() not ready"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi recv() not ready", qs->id); return NGX_AGAIN; } @@ -2808,8 +2832,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) rev->ready = rev->pending_eof; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: %z of %uz", len, size); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi recv: %z of %uz", qs->id, len, size); if (!rev->pending_eof) { frame = ngx_quic_alloc_frame(pc, 0); @@ -2850,9 +2874,9 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(pc->quic, frame); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic recv: increased max data: %ui", - qc->streams.max_data); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi recv: increased max data: %ui", + qs->id, qc->streams.max_data); } return len; @@ -2877,7 +2901,8 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send: %uz", size); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi send: %uz", qs->id, size); /* * we need to fit at least 1 frame into a packet, thus account head/tail; @@ -2915,7 +2940,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) c->sent += fsize; p += fsize; - ngx_sprintf(frame->info, "stream %xi len=%ui level=%d", + ngx_sprintf(frame->info, "stream 0x%xi len=%ui level=%d", qs->id, fsize, frame->level); ngx_quic_queue_frame(qc, frame); @@ -2939,7 +2964,8 @@ ngx_quic_stream_cleanup_handler(void *data) pc = qs->parent; qc = pc->quic; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream cleanup"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_frames(pc, &qs->fs.frames); @@ -2955,7 +2981,8 @@ ngx_quic_stream_cleanup_handler(void *data) return; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send fin"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi send fin", qs->id); frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { @@ -2974,7 +3001,8 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stream.length = 0; frame->u.stream.data = NULL; - ngx_sprintf(frame->info, "stream %xi fin=1 level=%d", qs->id, frame->level); + ngx_sprintf(frame->info, "stream 0x%xi fin=1 level=%d", + qs->id, frame->level); ngx_quic_queue_frame(qc, frame); @@ -3051,8 +3079,10 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) ngx_queue_remove(&frame->queue); +#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "reuse quic frame n:%ui", qc->nframes); + "quic reuse frame n:%ui", qc->nframes); +#endif } else { frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); @@ -3065,8 +3095,10 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) ++qc->nframes; #endif +#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "alloc quic frame n:%ui", qc->nframes); + "quic alloc frame n:%ui", qc->nframes); +#endif } ngx_memzero(frame, sizeof(ngx_quic_frame_t)); @@ -3090,6 +3122,8 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) ngx_queue_insert_head(&qc->free_frames, &frame->queue); +#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "free quic frame n:%ui", qc->nframes); + "quic free frame n:%ui", qc->nframes); +#endif } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index ec9e289fb..7daaa1788 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -82,6 +82,12 @@ ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); /********************************* DEBUG *************************************/ +//#define NGX_QUIC_DEBUG_PACKETS /* dump packet contents */ +//#define NGX_QUIC_DEBUG_FRAMES /* dump frames contents */ +//#define NGX_QUIC_DEBUG_FRAMES_ALLOC /* log frames alloc/reuse/free */ +//#define NGX_QUIC_DEBUG_CRYPTO + + #if (NGX_DEBUG) #define ngx_quic_hexdump(log, fmt, data, len, ...) \ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 2ad5a72df..114b312fa 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -156,8 +156,10 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, .len = is_len }; +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pool->log, "salt", salt, sizeof(salt)); ngx_quic_hexdump0(pool->log, "initial secret", is, is_len); +#endif /* draft-ietf-quic-tls-23#section-5.2 */ client->secret.len = SHA256_DIGEST_LENGTH; @@ -263,8 +265,10 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, return NGX_ERROR; } +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(pool->log, "%V info", info, info_len, label); ngx_quic_hexdump(pool->log, "%V key", out->data, out->len, label); +#endif return NGX_OK; } @@ -761,21 +765,21 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); +#endif if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ngx_quic_create_long_packet: number %L, encoded %d:0x%xD", - pkt->number, (int) pkt->num_len, pkt->trunc); - ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); +#endif if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, &ad, pkt->log) @@ -791,8 +795,10 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "sample", sample, 16); ngx_quic_hexdump0(pkt->log, "mask", mask, 5); +#endif /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x0f; @@ -824,21 +830,26 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); +#endif if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ngx_quic_create_short_packet: number %L, encoded %d:0x%xD", - pkt->number, (int) pkt->num_len, pkt->trunc); + "quic ngx_quic_create_short_packet: number %L," + " encoded %d:0x%xD", pkt->number, (int) pkt->num_len, + pkt->trunc); ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); +#endif if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, &ad, pkt->log) @@ -854,8 +865,10 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "sample", sample, 16); ngx_quic_hexdump0(pkt->log, "mask", mask, 5); +#endif /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x1f; @@ -963,7 +976,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, sample = p + 4; +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "sample", sample, 16); +#endif /* header protection */ @@ -991,7 +1006,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->pn = pn; +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "mask", mask, 5); +#endif + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic clear flags: %xi", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1021,8 +1039,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); +#endif pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; @@ -1035,8 +1055,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, nonce, &in, &ad, pkt->log); +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) ngx_quic_hexdump0(pkt->log, "packet payload", pkt->payload.data, pkt->payload.len); +#endif return rc; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 150deef98..ca2981460 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -234,7 +234,9 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = pkt->data; end = pkt->data + pkt->len; +#ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len); +#endif p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { @@ -256,7 +258,8 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic flags:%xi version:%xD", pkt->flags, pkt->version); + "quic long packet flags:%xi version:%xD", + pkt->flags, pkt->version); if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -388,7 +391,9 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = pkt->data; end = pkt->data + pkt->len; +#ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len); +#endif p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { @@ -403,7 +408,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic flags:%xi", pkt->flags); + "quic short packet flags:%xi", pkt->flags); if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); @@ -460,7 +465,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %uL", varint); + "quic initial packet length: %uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); @@ -470,9 +475,11 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) pkt->raw->pos = p; pkt->len = varint; +#ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len); ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len); +#endif return NGX_OK; } @@ -496,7 +503,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet length: %uL", plen); + "quic handshake packet length: %uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); @@ -565,12 +572,14 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic CRYPTO frame length: %uL off:%uL pp:%p", + "quic frame in: CRYPTO length: %uL off:%uL pp:%p", f->u.crypto.length, f->u.crypto.offset, f->u.crypto.data); +#ifdef NGX_QUIC_DEBUG_FRAMES ngx_quic_hexdump0(pkt->log, "CRYPTO frame contents", f->u.crypto.data, f->u.crypto.length); +#endif break; case NGX_QUIC_FT_PADDING: @@ -620,8 +629,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ack.ranges_end = p; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ACK: { largest=%ui delay=%ui count=%ui first=%ui}", - f->u.ack.largest, + "quic frame in ACK largest:%ui delay:%ui" + " count:%ui first:%ui", f->u.ack.largest, f->u.ack.delay, f->u.ack.range_count, f->u.ack.first_range); @@ -638,7 +647,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ACK ECN counters: %ui %ui %ui", + "quic ACK ECN counters: %ui %ui %ui", f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); } @@ -692,7 +701,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "NCID: { seq=%ui retire=%ui len=%ui}", + "quic frame in: NCID seq:%ui retire:%ui len:%ui", f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; @@ -746,14 +755,16 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "CONN.CLOSE: { %s (0x%xi) type=0x%xi reason='%V'}", + "quic frame in CONNECTION_CLOSE" + " err:%s code:0x%xi type:0x%xi reason:'%V'", ngx_quic_error_text(f->u.close.error_code), f->u.close.error_code, f->u.close.frame_type, &f->u.close.reason); } else { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "CONN.CLOSE2: { (0x%xi) reason '%V'}", + "quic frame in: CONNECTION_CLOSE2:" + " code:0x%xi reason:'%V'", f->u.close.error_code, &f->u.close.reason); } @@ -819,14 +830,16 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "STREAM frame { 0x%xi id 0x%xi offset 0x%xi " - "len 0x%xi bits:off=%d len=%d fin=%d }", + "quic frame in: STREAM type:0x%xi id:0x%xi offset:0x%xi " + "len:0x%xi bits off:%d len:%d fin:%d", f->type, f->u.stream.stream_id, f->u.stream.offset, f->u.stream.length, f->u.stream.off, f->u.stream.len, f->u.stream.fin); +#ifdef NGX_QUIC_DEBUG_FRAMES ngx_quic_hexdump0(pkt->log, "STREAM frame contents", f->u.stream.data, f->u.stream.length); +#endif break; case NGX_QUIC_FT_MAX_DATA: @@ -843,7 +856,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "MAX_DATA frame { Maximum Data %ui }", + "quic frame in: MAX_DATA max_data:%ui", f->u.max_data.max_data); break; @@ -864,8 +877,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "RESET STREAM frame" - " { id 0x%xi error_code 0x%xi final_size 0x%xi }", + "quic frame in: RESET_STREAM" + " id:0x%xi error_code:0x%xi final_size:0x%xi", f->u.reset_stream.id, f->u.reset_stream.error_code, f->u.reset_stream.final_size); break; @@ -891,7 +904,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "STOP SENDING frame { id 0x%xi error_code 0x%xi}", + "quic frame in: STOP_SENDING id:0x%xi error_code:0x%xi", f->u.stop_sending.id, f->u.stop_sending.error_code); break; @@ -914,7 +927,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "STREAMS BLOCKED frame { limit %ui bidi: %d }", + "quic frame in: STREAMS_BLOCKED limit:%ui bidi:%d", f->u.streams_blocked.limit, f->u.streams_blocked.bidi); @@ -954,7 +967,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "MAX STREAMS frame { limit %ui bidi: %d }", + "quic frame in: MAX_STREAMS limit:%ui bidi:%d", f->u.max_streams.limit, f->u.max_streams.bidi); break; @@ -980,7 +993,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "MAX STREAM DATA frame { id: %ui limit: %ui }", + "quic frame in: MAX_STREAM_DATA id:%ui limit:%ui", f->u.max_stream_data.id, f->u.max_stream_data.limit); break; @@ -999,7 +1012,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "DATA BLOCKED frame { limit %ui }", + "quic frame in: DATA_BLOCKED limit:%ui", f->u.data_blocked.limit); break; @@ -1024,7 +1037,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "STREAM DATA BLOCKED frame { id: %ui limit: %ui }", + "quic frame in: STREAM_DATA_BLOCKED" + " id:%ui limit:%ui", f->u.stream_data_blocked.id, f->u.stream_data_blocked.limit); break; @@ -1044,7 +1058,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "RETIRE CONNECTION ID frame { sequence_number %ui }", + "quic frame in: RETIRE_CONNECTION_ID" + " sequence_number:%ui", f->u.retire_cid.sequence_number); break; @@ -1062,10 +1077,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "PATH CHALLENGE frame"); + "quic frame in: PATH_CHALLENGE"); +#ifdef NGX_QUIC_DEBUG_FRAMES ngx_quic_hexdump0(pkt->log, "path challenge data", f->u.path_challenge.data, 8); +#endif break; case NGX_QUIC_FT_PATH_RESPONSE: @@ -1082,10 +1099,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "PATH RESPONSE frame"); + "quic frame in: PATH_RESPONSE"); +#ifdef NGX_QUIC_DEBUG_FRAMES ngx_quic_hexdump0(pkt->log, "path response data", f->u.path_response.data, 8); +#endif break; default: @@ -1130,7 +1149,7 @@ ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "ACK range: gap %ui range %ui", *gap, *range); + "quic ACK range: gap %ui range %ui", *gap, *range); return p - start; } @@ -1472,47 +1491,50 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, - "client transport parameters parsed successfully"); + "quic transport parameters parsed ok"); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "disable active migration: %ui", + "quic tp disable active migration: %ui", tp->disable_active_migration); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "idle timeout: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout: %ui", tp->max_idle_timeout); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max packet size: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_packet_size: %ui", tp->max_packet_size); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max data: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data: %ui", tp->initial_max_data); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "max stream data bidi local: %ui", + "quic tp max_stream_data_bidi_local: %ui", tp->initial_max_stream_data_bidi_local); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "max stream data bidi remote: %ui", + "quic tp max_stream_data_bidi_remote: %ui", tp->initial_max_stream_data_bidi_remote); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max stream data uni: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_uni: %ui", tp->initial_max_stream_data_uni); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "initial max streams bidi: %ui", + "quic tp initial_max_streams_bidi: %ui", tp->initial_max_streams_bidi); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "initial max streams uni: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_uni: %ui", tp->initial_max_streams_uni); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "ack delay exponent: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp ack_delay_exponent: %ui", tp->ack_delay_exponent); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "max ack delay: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay: %ui", tp->max_ack_delay); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "active connection id limit: %ui", + "quic tp active_connection_id_limit: %ui", tp->active_connection_id_limit); return NGX_OK; @@ -1569,8 +1591,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) u_char *p; size_t len; -/* recent drafts with variable integer transport parameters encoding */ - #define ngx_quic_tp_len(id, value) \ ngx_quic_varint_len(id) \ + ngx_quic_varint_len(value) \ @@ -1636,8 +1656,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); - ngx_quic_hexdump0(ngx_cycle->log, "transport parameters", pos, p - pos); - return p - pos; } -- cgit v1.2.3 From 62943dfa08c50f04f7ce677a7618bd768b789e4e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 24 Apr 2020 11:33:00 +0300 Subject: Cleaned up hexdumps in debug output. - the ngx_quic_hexdump0() macro is renamed to ngx_quic_hexdump(); the original ngx_quic_hexdump() macro with variable argument is removed, extra information is logged normally, with ngx_log_debug() - all labels in hex dumps are prefixed with "quic" - the hexdump format is simplified, length is moved forward to avoid situations when the dump is truncated, and length is not shown - ngx_quic_flush_flight() function contents is debug-only, placed under NGX_DEBUG macro to avoid "unused variable" warnings from compiler - frame names in labels are capitalized, similar to other places --- src/event/ngx_event_quic.c | 23 ++++++++++------ src/event/ngx_event_quic.h | 16 ++++------- src/event/ngx_event_quic_protection.c | 52 +++++++++++++++++++++-------------- src/event/ngx_event_quic_transport.c | 26 +++++++++--------- 4 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3ed3501d6..8dc423b00 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -252,8 +252,9 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "level:%d read secret", - rsecret, secret_len, level); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); + ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); #endif keys = &c->quic->keys[level]; @@ -279,8 +280,9 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "level:%d write secret", - wsecret, secret_len, level); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); + ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif keys = &c->quic->keys[level]; @@ -304,7 +306,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "level:%d read", rsecret, secret_len, level); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); + ngx_quic_hexdump(c->log, "quic read", rsecret, secret_len); #endif keys = &c->quic->keys[level]; @@ -322,7 +326,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "level:%d write", wsecret, secret_len, level); + ngx_quic_hexdump(c->log, "quic write", wsecret, secret_len); #endif return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, @@ -434,13 +438,14 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) { +#if (NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_flush_flight()"); - +#endif return 1; } @@ -676,7 +681,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump0(c->log, "quic transport parameters", p, len); + ngx_quic_hexdump(c->log, "quic transport parameters", p, len); #endif if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { @@ -2456,7 +2461,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) } #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump0(c->log, "packet to send", res.data, res.len); + ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len); #endif len = c->send(c, res.data, res.len); diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 7daaa1788..0dc9d0838 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -87,31 +87,25 @@ ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); //#define NGX_QUIC_DEBUG_FRAMES_ALLOC /* log frames alloc/reuse/free */ //#define NGX_QUIC_DEBUG_CRYPTO - #if (NGX_DEBUG) -#define ngx_quic_hexdump(log, fmt, data, len, ...) \ +#define ngx_quic_hexdump(log, label, data, len) \ do { \ ngx_int_t m; \ u_char buf[2048]; \ \ if (log->log_level & NGX_LOG_DEBUG_EVENT) { \ m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \ - ngx_log_debug(NGX_LOG_DEBUG_EVENT, log, 0, \ - "%s: " fmt " %*s%s, len: %uz", \ - __FUNCTION__, __VA_ARGS__, m, buf, \ - len < 2048 ? "" : "...", len); \ + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, \ + label " len:%uz data:%*s%s", \ + len, m, buf, len < 2048 ? "" : "..."); \ } \ } while (0) #else -#define ngx_quic_hexdump(log, fmt, data, len, ...) +#define ngx_quic_hexdump(log, fmt, data, len) #endif -#define ngx_quic_hexdump0(log, fmt, data, len) \ - ngx_quic_hexdump(log, fmt "%s", data, len, "") \ - - #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 114b312fa..a3a306439 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -157,8 +157,10 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, }; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pool->log, "salt", salt, sizeof(salt)); - ngx_quic_hexdump0(pool->log, "initial secret", is, is_len); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic ngx_quic_set_initial_secret"); + ngx_quic_hexdump(pool->log, "quic salt", salt, sizeof(salt)); + ngx_quic_hexdump(pool->log, "quic initial secret", is, is_len); #endif /* draft-ietf-quic-tls-23#section-5.2 */ @@ -266,8 +268,10 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pool->log, "%V info", info, info_len, label); - ngx_quic_hexdump(pool->log, "%V key", out->data, out->len, label); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic ngx_quic_hkdf_expand %V keys", label); + ngx_quic_hexdump(pool->log, "quic info", info, info_len); + ngx_quic_hexdump(pool->log, "quic key", out->data, out->len); #endif return NGX_OK; @@ -678,7 +682,7 @@ ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, ngx_uint_t i; ngx_quic_ciphers_t ciphers; - ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); if (ngx_quic_ciphers(c->ssl->connection, &ciphers, ssl_encryption_application) @@ -766,7 +770,9 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ngx_quic_create_long_packet"); + ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { @@ -777,8 +783,8 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); + ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); #endif if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, @@ -796,8 +802,8 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "sample", sample, 16); - ngx_quic_hexdump0(pkt->log, "mask", mask, 5); + ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); + ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); #endif /* quic-tls: 5.4.1. Header Protection Application */ @@ -831,7 +837,9 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ngx_quic_create_short_packet"); + ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { @@ -847,8 +855,8 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); + ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12); + ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); #endif if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, @@ -866,8 +874,8 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "sample", sample, 16); - ngx_quic_hexdump0(pkt->log, "mask", mask, 5); + ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); + ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); #endif /* quic-tls: 5.4.1. Header Protection Application */ @@ -977,7 +985,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, sample = p + 4; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "sample", sample, 16); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ngx_quic_decrypt()"); + ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); #endif /* header protection */ @@ -1007,7 +1017,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->pn = pn; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "mask", mask, 5); + ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); #endif ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1040,8 +1050,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12); - ngx_quic_hexdump0(pkt->log, "ad", ad.data, ad.len); + ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); + ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; @@ -1056,8 +1066,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, nonce, &in, &ad, pkt->log); #if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) - ngx_quic_hexdump0(pkt->log, "packet payload", - pkt->payload.data, pkt->payload.len); + ngx_quic_hexdump(pkt->log, "quic packet payload", + pkt->payload.data, pkt->payload.len); #endif return rc; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ca2981460..29397d3ca 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -235,7 +235,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) end = pkt->data + pkt->len; #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len); + ngx_quic_hexdump(pkt->log, "quic long packet in", pkt->data, pkt->len); #endif p = ngx_quic_read_uint8(p, end, &pkt->flags); @@ -392,7 +392,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) end = pkt->data + pkt->len; #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len); + ngx_quic_hexdump(pkt->log, "quic short packet in", pkt->data, pkt->len); #endif p = ngx_quic_read_uint8(p, end, &pkt->flags); @@ -476,9 +476,9 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) pkt->len = varint; #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len); - ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len); - ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len); + ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump(pkt->log, "quic SCID", pkt->scid.data, pkt->scid.len); + ngx_quic_hexdump(pkt->log, "quic token", pkt->token.data, pkt->token.len); #endif return NGX_OK; @@ -577,8 +577,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.crypto.data); #ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump0(pkt->log, "CRYPTO frame contents", - f->u.crypto.data, f->u.crypto.length); + ngx_quic_hexdump(pkt->log, "quic CRYPTO frame", + f->u.crypto.data, f->u.crypto.length); #endif break; @@ -837,8 +837,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.stream.fin); #ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump0(pkt->log, "STREAM frame contents", - f->u.stream.data, f->u.stream.length); + ngx_quic_hexdump(pkt->log, "quic STREAM frame", + f->u.stream.data, f->u.stream.length); #endif break; @@ -1080,8 +1080,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, "quic frame in: PATH_CHALLENGE"); #ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump0(pkt->log, "path challenge data", - f->u.path_challenge.data, 8); + ngx_quic_hexdump(pkt->log, "quic PATH_CHALLENGE frame data", + f->u.path_challenge.data, 8); #endif break; @@ -1102,8 +1102,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, "quic frame in: PATH_RESPONSE"); #ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump0(pkt->log, "path response data", - f->u.path_response.data, 8); + ngx_quic_hexdump(pkt->log, "quic PATH_RESPONSE frame data", + f->u.path_response.data, 8); #endif break; -- cgit v1.2.3 From 2e4cd7a2e5f63cf221920ef3462f9c2202e27f7f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 24 Apr 2020 14:38:49 +0300 Subject: Error messages cleanup. + added "quic" prefix to all error messages + rephrased some messages + removed excessive error logging from frame parser + added ngx_quic_check_peer() function to check proper source/destination match and do it one place --- src/event/ngx_event_quic.c | 118 ++++++++++---------- src/event/ngx_event_quic_transport.c | 204 +++++++++++++---------------------- 2 files changed, 135 insertions(+), 187 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8dc423b00..78af3559f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -158,6 +158,8 @@ static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, @@ -385,7 +387,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "maximum packet size is invalid"); + "quic maximum packet size is invalid"); return NGX_ERROR; } @@ -519,7 +521,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram"); + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic UDP datagram is too small for initial packet"); return NGX_ERROR; } @@ -529,7 +532,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid initial packet: 0x%xi", pkt->flags); + "quic invalid initial packet: 0x%xi", pkt->flags); return NGX_ERROR; } @@ -657,7 +660,7 @@ ngx_quic_init_connection(ngx_connection_t *c) if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "SSL_set_quic_method() failed"); + "quic SSL_set_quic_method() failed"); return NGX_ERROR; } @@ -686,7 +689,7 @@ ngx_quic_init_connection(ngx_connection_t *c) if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "SSL_set_quic_transport_params() failed"); + "quic SSL_set_quic_transport_params() failed"); return NGX_ERROR; } @@ -738,7 +741,8 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "quic client timed out"); ngx_quic_close_connection(c, NGX_DONE); return; } @@ -1003,7 +1007,9 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) if (pkt.flags == 0) { /* XXX: no idea WTF is this, just ignore */ - ngx_log_error(NGX_LOG_ALERT, c->log, 0, "FIREFOX: ZEROES"); + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "quic packet with zero flags, presumably" + " firefox padding, ignored"); break; } @@ -1021,7 +1027,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) } else { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "BUG: unknown quic state"); + "quic unknown long packet type"); return NGX_ERROR; } @@ -1110,7 +1116,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (keys->client.key.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "no read keys yet, packet ignored"); + "quic no read keys yet, packet ignored"); return NGX_DECLINED; } @@ -1119,29 +1125,13 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (pkt->dcid.len != qc->dcid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); - return NGX_ERROR; - } - - if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); - return NGX_ERROR; - } - - if (pkt->scid.len != qc->scid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); - return NGX_ERROR; - } - - if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { return NGX_ERROR; } if (!ngx_quic_pkt_hs(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%xi", pkt->flags); + "quic invalid packet type: 0x%xi", pkt->flags); return NGX_ERROR; } @@ -1180,29 +1170,13 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (pkt->dcid.len != qc->dcid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl"); - return NGX_ERROR; - } - - if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid"); - return NGX_ERROR; - } - - if (pkt->scid.len != qc->scid.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl"); - return NGX_ERROR; - } - - if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid"); + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { return NGX_ERROR; } if (!ngx_quic_pkt_zrtt(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid packet type: 0x%xi", pkt->flags); + "quic invalid packet type: 0x%xi", pkt->flags); return NGX_ERROR; } @@ -1211,7 +1185,7 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (c->quic->state != NGX_QUIC_ST_EARLY_DATA) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected 0-RTT packet"); + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unexpected 0-RTT packet"); return NGX_OK; } @@ -1231,6 +1205,33 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +{ + if (pkt->dcid.len != qc->dcid.len) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid"); + return NGX_ERROR; + } + + if (pkt->scid.len != qc->scid.len) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scidl"); + return NGX_ERROR; + } + + if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -1249,7 +1250,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (keys->client.key.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "no read keys yet, packet ignored"); + "quic no read keys yet, packet ignored"); return NGX_DECLINED; } @@ -1448,7 +1449,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) if (p != end) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "trailing garbage in payload: %ui bytes", end - p); + "quic trailing garbage in payload: %ui bytes", end - p); return NGX_ERROR; } @@ -1551,7 +1552,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (ack->first_range > ack->largest) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid first range in ack frame"); + "quic invalid first range in ack frame"); return NGX_ERROR; } @@ -1582,7 +1583,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (gap >= min) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid range %ui in ack frame", i); + "quic invalid range %ui in ack frame", i); return NGX_ERROR; } @@ -1590,7 +1591,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (range > max + 1) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "invalid range %ui in ack frame", i); + "quic invalid range %ui in ack frame", i); return NGX_ERROR; } @@ -1640,7 +1641,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "ACK for the packet not in sent queue "); + "quic ACK for the packet not in sent queue "); // TODO: handle error properly: PROTOCOL VIOLATION? return NGX_ERROR; } @@ -1811,7 +1812,7 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* check limit on total size used by all buffered frames, not actual data */ if (NGX_QUIC_MAX_BUFFERED - fs->total < f->length) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "ordered input buffer limit exceeded"); + "quic ordered input buffer limit exceeded"); return NGX_ERROR; } @@ -1987,7 +1988,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (n < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no space in stream buffer"); return NGX_ERROR; } @@ -2069,7 +2071,8 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) b = sn->b; if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "no space in stream buffer"); + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no space in stream buffer"); return NGX_ERROR; } @@ -2180,7 +2183,8 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id); + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unknown stream id:%uL", f->id); return NGX_ERROR; } @@ -2595,7 +2599,7 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (start->first + qc->tp.max_idle_timeout < now) { ngx_log_error(NGX_LOG_ERR, c->log, 0, - "retransmission timeout"); + "quic retransmission timeout"); return NGX_DECLINED; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 29397d3ca..9a317411f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -241,19 +241,19 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read flags"); + "quic packet is too small to read flags"); return NGX_ERROR; } if (!ngx_quic_long_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a long packet"); return NGX_ERROR; } p = ngx_quic_read_uint32(p, end, &pkt->version); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read version"); + "quic packet is too small to read version"); return NGX_ERROR; } @@ -263,14 +263,14 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "unsupported quic version: 0x%xi", pkt->version); + "quic unsupported version: 0x%xi", pkt->version); return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &idlen); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read dcid len"); + "quic packet is too small to read dcid len"); return NGX_ERROR; } @@ -279,14 +279,14 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read dcid"); + "quic packet is too small to read dcid"); return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &idlen); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read scid len"); + "quic packet is too small to read scid len"); return NGX_ERROR; } @@ -295,7 +295,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read scid"); + "quic packet is too small to read scid"); return NGX_ERROR; } @@ -398,12 +398,12 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read flags"); + "quic packet is too small to read flags"); return NGX_ERROR; } if (!ngx_quic_short_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a short packet"); return NGX_ERROR; } @@ -420,7 +420,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = ngx_quic_read_bytes(p, end, dcid->len, &pkt->dcid.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet is too small to read dcid"); + "quic packet is too small to read dcid"); return NGX_ERROR; } @@ -445,7 +445,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse token length"); + "quic failed to parse token length"); return NGX_ERROR; } @@ -454,13 +454,13 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "packet too small to read token data"); + "quic packet too small to read token data"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "bad packet length"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); return NGX_ERROR; } @@ -468,7 +468,8 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) "quic initial packet length: %uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic truncated initial packet"); return NGX_ERROR; } @@ -498,7 +499,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) p = ngx_quic_parse_int(p, end, &plen); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "bad packet length"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); return NGX_ERROR; } @@ -506,7 +507,8 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) "quic handshake packet length: %uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet"); + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic truncated handshake packet"); return NGX_ERROR; } @@ -536,7 +538,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to obtain quic frame type"); + "quic failed to obtain quic frame type"); return NGX_ERROR; } @@ -552,23 +554,17 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse crypto frame offset"); - return NGX_ERROR; + goto error; } p = ngx_quic_parse_int(p, end, &f->u.crypto.length); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse crypto frame len"); - return NGX_ERROR; + goto error; } p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &f->u.crypto.data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse crypto frame data"); - return NGX_ERROR; + goto error; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -604,9 +600,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame"); - return NGX_ERROR; + goto error; } f->u.ack.ranges_start = p; @@ -620,9 +614,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame range %ui", i); - return NGX_ERROR; + goto error; } } @@ -641,9 +633,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame ECT counts", i); - return NGX_ERROR; + goto error; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -667,37 +657,27 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id frame seqnum"); - return NGX_ERROR; + goto error; } p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id frame retire"); - return NGX_ERROR; + goto error; } p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id length"); - return NGX_ERROR; + goto error; } p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id cid"); - return NGX_ERROR; + goto error; } p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse new connection id srt"); - return NGX_ERROR; + goto error; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -721,25 +701,19 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse close connection frame error code"); - return NGX_ERROR; + goto error; } if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse close connection frame type"); - return NGX_ERROR; + goto error; } } p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse close reason length"); - return NGX_ERROR; + goto error; } f->u.close.reason.len = varint; @@ -747,9 +721,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, &f->u.close.reason.data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse close reason"); - return NGX_ERROR; + goto error; } if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { @@ -791,17 +763,13 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stream frame id"); - return NGX_ERROR; + goto error; } if (f->type & 0x04) { p = ngx_quic_parse_int(p, end, &f->u.stream.offset); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stream frame offset"); - return NGX_ERROR; + goto error; } } else { @@ -811,9 +779,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type & 0x02) { p = ngx_quic_parse_int(p, end, &f->u.stream.length); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stream frame length"); - return NGX_ERROR; + goto error; } } else { @@ -823,10 +789,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_read_bytes(p, end, f->u.stream.length, &f->u.stream.data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stream frame data len=%ui " - "offset=%ui", f->u.stream.length, f->u.stream.offset); - return NGX_ERROR; + goto error; } ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -850,9 +813,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse max data frame"); - return NGX_ERROR; + goto error; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -871,9 +832,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size)))) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse reset stream frame"); - return NGX_ERROR; + goto error; } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -891,16 +850,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stop sending frame id"); - return NGX_ERROR; + goto error; } p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse stop sending frame error code"); - return NGX_ERROR; + goto error; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -918,9 +873,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse streams blocked frame limit"); - return NGX_ERROR; + goto error; } f->u.streams_blocked.bidi = @@ -946,7 +899,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, /* TODO: implement */ ngx_log_error(NGX_LOG_ALERT, pkt->log, 0, - "unimplemented frame type 0x%xi in packet", f->type); + "quic unimplemented frame type 0x%xi in packet", f->type); break; @@ -959,9 +912,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse max streams frame limit"); - return NGX_ERROR; + goto error; } f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; @@ -980,16 +931,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse max stream data frame data id"); - return NGX_ERROR; + goto error; } p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse max stream data frame data limit"); - return NGX_ERROR; + goto error; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1006,9 +953,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse data blocked frame limit"); - return NGX_ERROR; + goto error; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1024,16 +969,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse tream data blocked frame id"); - return NGX_ERROR; + goto error; } p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse tream data blocked frame limit"); - return NGX_ERROR; + goto error; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1051,10 +992,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse retire connection id" - " frame sequence number"); - return NGX_ERROR; + goto error; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1071,9 +1009,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to get path challenge frame data"); - return NGX_ERROR; + goto error; } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1093,9 +1029,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to get path response frame data"); - return NGX_ERROR; + goto error; } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1109,8 +1043,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, default: ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "unknown frame type 0x%xi in packet", f->type); - + "quic unknown frame type 0x%xi", f->type); return NGX_ERROR; } @@ -1119,10 +1052,18 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, not_allowed: ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "frame type 0x%xi is not allowed in packet with flags 0x%xi", + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xi", f->type, pkt->flags); return NGX_DECLINED; + +error: + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse frame type 0x%xi", f->type); + + return NGX_ERROR; } @@ -1137,14 +1078,14 @@ ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, gap); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame gap"); + "quic failed to parse ack frame gap"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, range); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "failed to parse ack frame range"); + "quic failed to parse ack frame range"); return NGX_ERROR; } @@ -1456,14 +1397,15 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, p = ngx_quic_parse_int(p, end, &id); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id"); + "quic failed to parse transport param id"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, &len); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id 0x%xi length", id); + "quic failed to parse" + " transport param id 0x%xi length", id); return NGX_ERROR; } @@ -1471,13 +1413,14 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (rc == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, log, 0, - "failed to parse transport param id 0x%xi data", id); + "quic failed to parse" + " transport param id 0x%xi data", id); return NGX_ERROR; } if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, log, 0, - "unknown transport param id 0x%xi,skipped", id); + "quic unknown transport param id 0x%xi,skipped", id); } p += len; @@ -1485,7 +1428,8 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (p != end) { ngx_log_error(NGX_LOG_INFO, log, 0, - "trailing garbage in transport parameters: %ui bytes", + "quic trailing garbage in" + " transport parameters: %ui bytes", end - p); return NGX_ERROR; } -- cgit v1.2.3 From 9c375910161cdcac5bb616f6afb8de030849f2ca Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Apr 2020 18:05:05 +0300 Subject: Assign connection number to every QUIC stream log. --- src/event/ngx_event_quic.c | 2 ++ src/http/ngx_http_request.c | 1 - src/http/v3/ngx_http_v3_streams.c | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 78af3559f..ef8e237d2 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2778,6 +2778,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) sn->c->read->log = c->log; sn->c->write->log = c->log; + log->connection = sn->c->number; + cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { ngx_close_connection(sn->c); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 082938e00..b0cc9a543 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -438,7 +438,6 @@ ngx_http_quic_stream_handler(ngx_connection_t *c) ctx->request = NULL; ctx->current_request = NULL; - c->log->connection = c->number; c->log->handler = ngx_http_log_error; c->log->data = ctx; c->log->action = "waiting for request"; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 6078725d7..165f21fdc 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -34,8 +34,6 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) { ngx_http_v3_uni_stream_t *us; - c->log->connection = c->number; - ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); -- cgit v1.2.3 From 60c8a601d08523bba989f55b68315780d435eba5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 24 Apr 2020 17:20:37 +0300 Subject: Fixed packet retransmission. Previously frames in ctx->sent queue could be lost. --- src/event/ngx_event_quic.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ef8e237d2..16b3a91fc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2586,11 +2586,18 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - pn = start->pnum; - f = start; do { + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + wait = start->last + qc->tp.max_ack_delay - now; + + if ((ngx_msec_int_t) wait > 0) { + break; + } + + pn = start->pnum; + ngx_queue_init(&range); /* send frames with same packet number to the wire */ @@ -2614,12 +2621,6 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } while (q != ngx_queue_sentinel(&ctx->sent)); - wait = start->last + qc->tp.max_ack_delay - now; - - if ((ngx_msec_int_t) wait > 0) { - break; - } - /* NGX_DONE is impossible here, such frames don't get into this queue */ if (ngx_quic_send_frames(c, &range) != NGX_OK) { return NGX_ERROR; -- cgit v1.2.3 From 75acaec13adb24b9bc4cf5d4d94855e51c0a5914 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 28 Apr 2020 16:42:43 +0300 Subject: QUIC basic congestion control. --- src/event/ngx_event_quic.c | 219 +++++++++++++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic.h | 3 +- 2 files changed, 212 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 16b3a91fc..913124b1c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -53,6 +53,14 @@ typedef struct { } ngx_quic_streams_t; +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + /* * 12.3. Packet Numbers * @@ -103,6 +111,7 @@ struct ngx_quic_connection_s { #endif ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; ngx_uint_t max_data; uint64_t cur_streams; @@ -171,6 +180,8 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max); +static void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, @@ -227,6 +238,10 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c, size_t size); static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); +static void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_msec_t sent); + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 @@ -586,6 +601,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->streams.max_data = qc->tp.initial_max_data; + qc->congestion.window = ngx_min(10 * qc->tp.max_packet_size, + ngx_max(2 * qc->tp.max_packet_size, 14720)); + qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; + qc->congestion.recovery_start = ngx_current_msec; + qc->dcid.len = pkt->dcid.len; qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); if (qc->dcid.data == NULL) { @@ -1610,9 +1630,12 @@ static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max) { - ngx_uint_t found; - ngx_queue_t *q; - ngx_quic_frame_t *f; + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = c->quic; found = 0; @@ -1623,6 +1646,10 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, f = ngx_queue_data(q, ngx_quic_frame_t, queue); if (f->pnum >= min && f->pnum <= max) { + ngx_quic_congestion_ack(c, f); + + ngx_quic_handle_stream_ack(c, f); + q = ngx_queue_next(q); ngx_queue_remove(&f->queue); ngx_quic_free_frame(c, f); @@ -1646,10 +1673,50 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return NGX_ERROR; } + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + return NGX_OK; } +static void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t sent, unacked; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + if (f->type < NGX_QUIC_FT_STREAM0 || f->type > NGX_QUIC_FT_STREAM7) { + return; + } + + qc = c->quic; + + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (sn == NULL) { + return; + } + + wev = sn->c->write; + sent = sn->c->sent; + unacked = sent - sn->acked; + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + + sn->acked += f->u.stream.length; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, + "quic stream ack %uL acked:%uL, unacked:%uL", + f->u.stream.length, sn->acked, sent - sn->acked); +} + + static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler) @@ -2263,11 +2330,14 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { size_t len, hlen, n; ngx_int_t rc; + ngx_uint_t need_ack; ngx_queue_t *q, range; ngx_quic_frame_t *f; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; qc = c->quic; + cg = &qc->congestion; if (ngx_queue_empty(&ctx->frames)) { return NGX_OK; @@ -2283,6 +2353,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) do { len = 0; + need_ack = 0; ngx_queue_init(&range); do { @@ -2295,6 +2366,14 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) break; } + if (f->need_ack) { + need_ack = 1; + } + + if (need_ack && cg->in_flight + len + n > cg->window) { + break; + } + q = ngx_queue_next(q); f->first = ngx_current_msec; @@ -2306,6 +2385,10 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) } while (q != ngx_queue_sentinel(&ctx->frames)); + if (ngx_queue_empty(&range)) { + break; + } + rc = ngx_quic_send_frames(c, &range); if (rc == NGX_OK) { @@ -2321,6 +2404,11 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_queue_add(&ctx->sent, &range); } + cg->in_flight += len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + } else if (rc == NGX_DONE) { /* no ack is expected for this frames, can free them */ @@ -2386,6 +2474,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + now = ngx_current_msec; + p = src; out.data = src; @@ -2409,6 +2499,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) p += len; f->pnum = ctx->pnum; + f->last = now; } if (start->level == ssl_encryption_initial) { @@ -2476,9 +2567,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) /* len == NGX_OK || NGX_AGAIN */ ctx->pnum++; - now = ngx_current_msec; - start->last = now; - return pkt.need_ack ? NGX_OK : NGX_DONE; } @@ -2621,6 +2709,8 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } while (q != ngx_queue_sentinel(&ctx->sent)); + ngx_quic_congestion_lost(c, start->last); + /* NGX_DONE is impossible here, such frames don't get into this queue */ if (ngx_quic_send_frames(c, &range) != NGX_OK) { return NGX_ERROR; @@ -2781,6 +2871,12 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) log->connection = sn->c->number; + if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + sn->c->write->ready = 1; + } + cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { ngx_close_connection(sn->c); @@ -2899,7 +2995,8 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) { u_char *p, *end; - size_t fsize, limit; + size_t fsize, limit, n, len; + uint64_t sent, unacked; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -2923,8 +3020,22 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) limit = qc->ctp.max_packet_size - NGX_QUIC_MAX_SHORT_HEADER - 25 - EVP_GCM_TLS_TAG_LEN; + len = size; + sent = c->sent; + unacked = sent - qs->acked; + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send hit buffer size"); + len = 0; + + } else if (unacked + len > NGX_QUIC_STREAM_BUFSIZE) { + len = NGX_QUIC_STREAM_BUFSIZE - unacked; + } + p = (u_char *) buf; - end = (u_char *) buf + size; + end = (u_char *) buf + len; + n = 0; while (p < end) { @@ -2951,6 +3062,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) c->sent += fsize; p += fsize; + n += fsize; ngx_sprintf(frame->info, "stream 0x%xi len=%ui level=%d", qs->id, fsize, frame->level); @@ -2958,7 +3070,19 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(qc, frame); } - return size; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream send %uz sent:%O, unacked:%uL", + n, c->sent, (uint64_t) c->sent - qs->acked); + + if (n != size) { + c->write->ready = 0; + } + + if (n == 0) { + return NGX_AGAIN; + } + + return n; } @@ -3121,6 +3245,83 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) } +static void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ssize_t n; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = c->quic; + cg = &qc->congestion; + + n = ngx_quic_create_frame(NULL, f); + + cg->in_flight -= n; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + return; + } + + if (cg->window < cg->ssthresh) { + cg->window += n; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz, ss:%uz, if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_packet_size * n / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz, ss:%uz, if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_msec_t sent) +{ + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = c->quic; + cg = &qc->congestion; + + timer = sent - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + return; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_packet_size * 2) { + cg->window = qc->tp.max_packet_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz, ss:%uz, if:%uz", + cg->window, cg->ssthresh, cg->in_flight); +} + + static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 0dc9d0838..051550de6 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -31,7 +31,7 @@ #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 -#define NGX_QUIC_STREAM_BUFSIZE 16384 +#define NGX_QUIC_STREAM_BUFSIZE 65536 typedef struct { @@ -70,6 +70,7 @@ struct ngx_quic_stream_s { ngx_connection_t *parent; ngx_connection_t *c; uint64_t id; + uint64_t acked; ngx_buf_t *b; ngx_quic_frames_stream_t fs; }; -- cgit v1.2.3 From 7509f967f779bc86cc55ecbd1956c9c5386b174d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 28 Apr 2020 16:37:32 +0300 Subject: Respect MAX_DATA and MAX_STREAM_DATA from QUIC client. --- src/event/ngx_event_quic.c | 173 ++++++++++++++++++++++++++++++++++++++++----- src/event/ngx_event_quic.h | 1 + 2 files changed, 156 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 913124b1c..0fcb02fdf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -48,8 +48,10 @@ typedef struct { ngx_uint_t id_counter; - uint64_t total_received; - uint64_t max_data; + uint64_t received; + uint64_t sent; + uint64_t recv_max_data; + uint64_t send_max_data; } ngx_quic_streams_t; @@ -112,7 +114,6 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; - ngx_uint_t max_data; uint64_t cur_streams; uint64_t max_streams; @@ -201,10 +202,14 @@ static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); +static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -599,7 +604,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; - qc->streams.max_data = qc->tp.initial_max_data; + qc->streams.recv_max_data = qc->tp.initial_max_data; qc->congestion.window = ngx_min(10 * qc->tp.max_packet_size, ngx_max(2 * qc->tp.max_packet_size, 14720)); @@ -1416,7 +1421,12 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_MAX_DATA: - c->quic->max_data = frame.u.max_data.max_data; + + if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) + { + return NGX_ERROR; + } + ack_this = 1; break; @@ -1445,6 +1455,18 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ack_this = 1; break; + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (ngx_quic_handle_max_stream_data_frame(c, pkt, + &frame.u.max_stream_data) + != NGX_OK) + { + return NGX_ERROR; + } + + ack_this = 1; + break; + case NGX_QUIC_FT_NEW_CONNECTION_ID: case NGX_QUIC_FT_RETIRE_CONNECTION_ID: case NGX_QUIC_FT_NEW_TOKEN: @@ -1452,7 +1474,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_STOP_SENDING: case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: - case NGX_QUIC_FT_MAX_STREAM_DATA: /* TODO: handle */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -2207,6 +2228,45 @@ ngx_quic_handle_max_streams(ngx_connection_t *c) } +static ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_event_t *wev; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = c->quic; + tree = &qc->streams.tree; + + if (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (qc->streams.sent >= qc->streams.send_max_data) { + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + wev = qs->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + } + + qc->streams.send_max_data = f->max_data; + + return NGX_OK; +} + + static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) @@ -2279,6 +2339,44 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + uint64_t sent; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = c->quic; + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id); + return NGX_ERROR; + } + + if (f->limit <= sn->send_max_data) { + return NGX_OK; + } + + sent = sn->c->sent; + + if (sent >= sn->send_max_data) { + wev = sn->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + + sn->send_max_data = f->limit; + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { @@ -2810,10 +2908,13 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) static ngx_quic_stream_t * ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) { - ngx_log_t *log; - ngx_pool_t *pool; - ngx_quic_stream_t *sn; - ngx_pool_cleanup_t *cln; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_quic_stream_t *sn; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; + + qc = c->quic; pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); if (pool == NULL) { @@ -2877,6 +2978,19 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) sn->c->write->ready = 1; } + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_uni; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + } else { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + } + } + cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { ngx_close_connection(sn->c); @@ -2932,7 +3046,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_memcpy(buf, b->pos, len); b->pos += len; - qc->streams.total_received += len; + qc->streams.received += len; if (b->pos == b->last) { b->pos = b->start; @@ -2963,7 +3077,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(pc->quic, frame); } - if ((qc->streams.max_data / 2) < qc->streams.total_received) { + if ((qc->streams.recv_max_data / 2) < qc->streams.received) { frame = ngx_quic_alloc_frame(pc, 0); @@ -2971,11 +3085,11 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - qc->streams.max_data *= 2; + qc->streams.recv_max_data *= 2; frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_MAX_DATA; - frame->u.max_data.max_data = qc->streams.max_data; + frame->u.max_data.max_data = qc->streams.recv_max_data; ngx_sprintf(frame->info, "MAX_DATA max_data:%d level=%d on recv", (int) frame->u.max_data.max_data, frame->level); @@ -2984,7 +3098,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id 0x%xi recv: increased max data: %ui", - qs->id, qc->streams.max_data); + qs->id, qc->streams.recv_max_data); } return len; @@ -3024,6 +3138,10 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) sent = c->sent; unacked = sent - qs->acked; + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send hit buffer size"); @@ -3033,6 +3151,24 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) len = NGX_QUIC_STREAM_BUFSIZE - unacked; } + if (qc->streams.sent >= qc->streams.send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send hit MAX_DATA"); + len = 0; + + } else if (qc->streams.sent + len > qc->streams.send_max_data) { + len = qc->streams.send_max_data - qc->streams.sent; + } + + if (sent >= qs->send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send hit MAX_STREAM_DATA"); + len = 0; + + } else if (sent + len > qs->send_max_data) { + len = qs->send_max_data - sent; + } + p = (u_char *) buf; end = (u_char *) buf + len; n = 0; @@ -3061,6 +3197,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) frame->u.stream.data = frame->data; c->sent += fsize; + qc->streams.sent += fsize; p += fsize; n += fsize; @@ -3070,9 +3207,9 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(qc, frame); } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream send %uz sent:%O, unacked:%uL", - n, c->sent, (uint64_t) c->sent - qs->acked); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send %uz of %uz, sent:%O, unacked:%uL", + n, size, c->sent, (uint64_t) c->sent - qs->acked); if (n != size) { c->write->ready = 0; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 051550de6..4482c8b7d 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -71,6 +71,7 @@ struct ngx_quic_stream_s { ngx_connection_t *c; uint64_t id; uint64_t acked; + uint64_t send_max_data; ngx_buf_t *b; ngx_quic_frames_stream_t fs; }; -- cgit v1.2.3 From 6582758e185a361af1f82ff61f55a598bd8bbba8 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 28 Apr 2020 18:16:13 +0300 Subject: Added README. --- README | 222 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 README diff --git a/README b/README new file mode 100644 index 000000000..d29b5ca5a --- /dev/null +++ b/README @@ -0,0 +1,222 @@ +Experimental QUIC support for nginx +----------------------------------- + +1. Introduction +2. Installing +3. Configuration +4. Clients +5. Troubleshooting +6. Links + +1. Introduction + + This is an experimental QUIC [1] / HTTP/3 [2] support for nginx. + + The code is developed in a separate "quic" branch available + at https://hg.nginx.org/nginx-quic. Currently it is based + on nginx mainline 1.17.10. We are planning to merge new nginx + releases into this branch regularly. + + The project code base is under the same BSD license as nginx. + + The code is at an early alpha level of quality and should not + be used in production. + + We are working on improving HTTP/3 support with the goal of + integrating it to the main NGINX codebase. Expect frequent + updates of this code and don't rely on it for whatever purpose. + + We'll be grateful for any feedback and code submissions however + we don't bear any responsibilities for any issues with this code. + + You can always contact us via nginx-devel mailing list [3]. + + What works now: + + Currently we support IETF-QUIC draft 27 + Earlier drafts are NOT supported as they have incompatible wire format; + + nginx should be able to respond to simple HTTP/3 requests over QUIC and + it should be possible to upload and download big files without errors. + + + The handshake completes successfully + + One endpoint can update keys and its peer responds correctly + + 00-RTT data is being received and acted on + + Connection is established using TLS Resume Ticket + + Stream data is being exchanged and ACK'ed + + An H3 transaction succeeded + + One or both endpoints insert entries into dynamic table and + subsequently reference them from header blocks + + Not (yet) supported features: + + - Version negotiation + - Stateless Retry + - ECN, Congestion control and friends as specified in quic-recovery [5] + - A connection with the spin bit succeeds and the bit is spinning + - Structured Logging + - QUIC recovery (proper congestion and flow control) + - NAT Rebinding + - Address Mobility + - Server push + - HTTP/3 trailers + + Since the code is experimental and still under development, + a lot of things may not work as expected, for example: + + - Protocol error messages are not implemented, in case of error connection + closes silently for peer + + - ACK handling is basic: every received ack-eliciting packet + is acknowledged, no ack ranges are used + + - Flow control mechanism is basic and intended to avoid CPU hog and make + simple interactions possible + + - Not all draft requirements are strictly followed; some of checks are + omitted for the sake of simplicity of initial implementation + +2. Installing + + You will need a BoringSSL [4] library that provides QUIC support + + $ hg clone https://hg.nginx.org/nginx-quic + $ cd nginx-quic + $ ./auto/configure --with-debug --with-cc-opt="-I../boringssl/include" \ + --with-ld-opt="-L../boringssl/build/ssl \ + -L../boringssl/build/crypto" + $ make + +3. Configuration + + The "listen" directive got a new option: "http3" + which enables HTTP/3 over QUIC on the specified port. + + Along with "http3", you also have to specify "reuseport" option [6] + to make it work properly with multiple workers. + + A number of directives were added that specify transport parameter values: + + quic_max_idle_timeout + quic_max_ack_delay + quic_max_packet_size + quic_initial_max_data + quic_initial_max_stream_data_bidi_local + quic_initial_max_stream_data_bidi_remote + quic_initial_max_stream_data_uni + quic_initial_max_streams_bidi + quic_initial_max_streams_uni + quic_ack_delay_exponent + quic_active_migration + quic_active_connection_id_limit + + Two additional variables are available: $quic and $http3. + The value of $quic is "quic" if QUIC connection is used, + and empty string otherwise. The value of $http3 is a string + "h3-xx" where "xx" is the supported draft number. + +Example configuration: + + http { + log_format quic '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$quic" "$http3"'; + + access_log logs/access.log quic; + + server { + # for better compatibility it's recommended + # to use the same port for quic and https + listen 8443 http3 reuseport; + listen 8443 ssl; + + ssl_certificate certs/example.com.crt; + ssl_certificate_key certs/example.com.key; + ssl_protocols TLSv1.3; + + location / { + # required for browsers to direct them into quic port + add_header Alt-Svc $http3=":8443"; + } + } + } + +4. Clients + + * Browsers + + Known to work: Firefox 75+ and Chrome 83+ + + Beware of strange issues: sometimes browser may decide to ignore QUIC + Cache clearing/restart might help. Always check access.log and + error.log to make sure you are using HTTP/3 and not TCP https. + + + to enable QUIC in Firefox, set the following in 'about:config': + network.http.http3.enabled = true + + + to enable QUIC in Chrome, enable it on command line and force it + on your site: + + $ ./chrome --enable-quic --quic-version=h3-27 \ + --origin-to-force-quic-on=example.com:8443 + + * Console clients + + Known to work: ngtcp2, firefox's neqo and chromium's console clients: + + $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html + + $ ./neqo-client https://127.0.0.1:8443/ + + $ chromium-build/out/my_build/quic_client http://example.com:8443 \ + --quic_version=h3-27 \ + --allow_unknown_root_cert \ + --disable_certificate_verification + + + If you've got it right, in the access log you should see something like: + + 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" + "nghttp3/ngtcp2 client" "quic" "h3-27" + + +5. Troubleshooting + + Here are some tips that may help you to identify problems: + + + Ensure you are building with proper SSL library that + implements draft 27 + + + Ensure you are using the proper SSL library in runtime + (`nginx -V` will show you what you are using) + + + Ensure your client is actually sending QUIC requests + (see "Clients" section about browsers and cache) + + We recommend to start with simple console client like ngtcp2 + to ensure you've got server configured properly before trying + with real browsers that may be very peaky with certificates, + for example. + + + Build nginx with debug support [7] and check your debug log. + It should contain all details about connection and why it + failed. All related messages contain "quic " prefix and can + be easily filtered out. + + + If you want to investigate deeper, you may want to enable + additional debugging in src/event/ngx_event_quic.h: + + #define NGX_QUIC_DEBUG_PACKETS + #define NGX_QUIC_DEBUG_FRAMES + #define NGX_QUIC_DEBUG_FRAMES_ALLOC + #define NGX_QUIC_DEBUG_CRYPTO + +6. Links + + [1] https://tools.ietf.org/html/draft-ietf-quic-transport-27 + [2] https://tools.ietf.org/html/draft-ietf-quic-http-27 + [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel + [4] https://boringssl.googlesource.com/boringssl/ + [5] https://tools.ietf.org/html/draft-ietf-quic-recovery-27 + [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen + [7] https://nginx.org/en/docs/debugging_log.html -- cgit v1.2.3 From 467c8a13c9b56ffa37f627f2ec3a3447d49cd1c0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 28 Apr 2020 18:23:56 +0300 Subject: Factored out sending ACK from payload handler. Now there's no need to annotate every frame in ACK-eliciting packet. Sending ACK was moved to the first place, so that queueing ACK frame no longer postponed up to the next packet after pushing STREAM frames. --- src/event/ngx_event_quic.c | 85 +++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0fcb02fdf..1c599e4d0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -174,6 +174,7 @@ static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, ngx_uint_t err); @@ -1328,8 +1329,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *end, *p; ssize_t len; - ngx_uint_t ack_this, do_close; - ngx_quic_frame_t frame, *ack_frame; + ngx_uint_t ack_sent, do_close; + ngx_quic_frame_t frame; ngx_quic_connection_t *qc; qc = c->quic; @@ -1349,7 +1350,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) p = pkt->payload.data; end = p + pkt->payload.len; - ack_this = 0; + ack_sent = 0; do_close = 0; while (p < end) { @@ -1380,7 +1381,29 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - break; + continue; + + case NGX_QUIC_FT_PADDING: + /* no action required */ + continue; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE2: + do_close = 1; + continue; + } + + /* got there with ack-eliciting packet */ + + if (!ack_sent) { + if (ngx_quic_send_ack(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + ack_sent = 1; + } + + switch (frame.type) { case NGX_QUIC_FT_CRYPTO: @@ -1388,20 +1411,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; - break; - - case NGX_QUIC_FT_PADDING: - /* no action required */ break; case NGX_QUIC_FT_PING: - ack_this = 1; - break; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE2: - do_close = 1; break; case NGX_QUIC_FT_STREAM0: @@ -1417,7 +1429,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; break; case NGX_QUIC_FT_MAX_DATA: @@ -1427,7 +1438,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; break; case NGX_QUIC_FT_STREAMS_BLOCKED: @@ -1440,7 +1450,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; break; case NGX_QUIC_FT_STREAM_DATA_BLOCKED: @@ -1452,7 +1461,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; break; case NGX_QUIC_FT_MAX_STREAM_DATA: @@ -1464,7 +1472,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - ack_this = 1; break; case NGX_QUIC_FT_NEW_CONNECTION_ID: @@ -1478,7 +1485,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) /* TODO: handle */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic frame handler not implemented"); - ack_this = 1; break; default: @@ -1497,35 +1503,36 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) if (do_close) { qc->draining = 1; ngx_quic_close_connection(c, NGX_OK); - return NGX_OK; } - if (ack_this == 0) { - /* do not ack packets with ACKs and PADDING */ - return NGX_OK; - } + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_frame_t *frame; c->log->action = "generating acknowledgment"; - // packet processed, ACK it now if required - // TODO: if (ack_required) ... - currently just ack each packet + /* every ACK-eliciting packet is acknowledged, TODO ACK Ranges */ - ack_frame = ngx_quic_alloc_frame(c, 0); - if (ack_frame == NULL) { + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { return NGX_ERROR; } - ack_frame->level = (pkt->level == ssl_encryption_early_data) - ? ssl_encryption_application - : pkt->level; + frame->level = (pkt->level == ssl_encryption_early_data) + ? ssl_encryption_application + : pkt->level; - ack_frame->type = NGX_QUIC_FT_ACK; - ack_frame->u.ack.largest = pkt->pn; - /* only ack immediate packet ]*/ - ack_frame->u.ack.first_range = 0; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = pkt->pn; - ngx_sprintf(ack_frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, ack_frame->level); - ngx_quic_queue_frame(qc, ack_frame); + ngx_sprintf(frame->info, "ACK for PN=%d from frame handler level=%d", + pkt->pn, frame->level); + ngx_quic_queue_frame(c->quic, frame); return NGX_OK; } -- cgit v1.2.3 From 9aeb2c0ece8555680e3797a5e93f001b345df445 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 28 Apr 2020 18:24:01 +0300 Subject: Server CID change. --- src/event/ngx_event_quic.c | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1c599e4d0..22995c206 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -152,6 +152,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler); +static ngx_int_t ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *sid); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -612,12 +613,13 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; qc->congestion.recovery_start = ngx_current_msec; - qc->dcid.len = pkt->dcid.len; - qc->dcid.data = ngx_pnalloc(c->pool, pkt->dcid.len); - if (qc->dcid.data == NULL) { + if (ngx_quic_new_cid(c->pool, &qc->dcid) != NGX_OK) { return NGX_ERROR; } - ngx_memcpy(qc->dcid.data, pkt->dcid.data, qc->dcid.len); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); +#endif qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); @@ -636,7 +638,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, keys = &c->quic->keys[ssl_encryption_initial]; if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, - &qc->dcid) + &pkt->dcid) != NGX_OK) { return NGX_ERROR; @@ -667,6 +669,31 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, } +static ngx_int_t +ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *cid) +{ + uint8_t len; + + if (RAND_bytes(&len, sizeof(len)) != 1) { + return NGX_ERROR; + } + + len = len % 10 + 10; + + cid->len = len; + cid->data = ngx_pnalloc(pool, len); + if (cid->data == NULL) { + return NGX_ERROR; + } + + if (RAND_bytes(cid->data, len) != 1) { + return NGX_ERROR; + } + + return NGX_OK; +} + + static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { -- cgit v1.2.3 From 4c2ccf217a2e663b425dab03f177d87e0cf1fb6d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 29 Apr 2020 14:59:21 +0300 Subject: Renamed retransmit event object in preparation for retry support. --- src/event/ngx_event_quic.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 22995c206..c0ff5f187 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -103,7 +103,7 @@ struct ngx_quic_connection_s { ngx_ssl_t *ssl; ngx_event_t push; - ngx_event_t retry; + ngx_event_t retransmit; ngx_event_t close; ngx_queue_t free_frames; ngx_msec_t last_cc; @@ -586,10 +586,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_queue_init(&qc->free_frames); - qc->retry.log = c->log; - qc->retry.data = c; - qc->retry.handler = ngx_quic_retransmit_handler; - qc->retry.cancelable = 1; + qc->retransmit.log = c->log; + qc->retransmit.data = c; + qc->retransmit.handler = ngx_quic_retransmit_handler; + qc->retransmit.cancelable = 1; qc->push.log = c->log; qc->push.data = c; @@ -963,8 +963,8 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_del_timer(&qc->push); } - if (qc->retry.timer_set) { - ngx_del_timer(&qc->retry); + if (qc->retransmit.timer_set) { + ngx_del_timer(&qc->retransmit); } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -2449,8 +2449,8 @@ ngx_quic_output(ngx_connection_t *c) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - if (!qc->retry.timer_set && !qc->closing) { - ngx_add_timer(&qc->retry, qc->tp.max_ack_delay); + if (!qc->retransmit.timer_set && !qc->closing) { + ngx_add_timer(&qc->retransmit, qc->tp.max_ack_delay); } return NGX_OK; @@ -2764,7 +2764,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) } if (wait > 0) { - ngx_add_timer(&qc->retry, wait); + ngx_add_timer(&qc->retransmit, wait); } } -- cgit v1.2.3 From ed461dbff70d5962e58cd894b77e7bd478daacb9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 30 Apr 2020 12:38:38 +0300 Subject: Reworked macros for parsing/assembling packet types. Previously, macros checking a packet type with the long header also checked whether this is a long header. Now it requires a separate preceding check. --- src/event/ngx_event_quic.c | 11 ++- src/event/ngx_event_quic_protection.c | 2 +- src/event/ngx_event_quic_transport.c | 167 +++++++++++++++------------------- src/event/ngx_event_quic_transport.h | 35 ++++--- 4 files changed, 105 insertions(+), 110 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c0ff5f187..e32d14227 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2655,16 +2655,19 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) pkt.secret = &keys->server; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; + if (start->level == ssl_encryption_initial) { - pkt.flags = NGX_QUIC_PKT_INITIAL; + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; pkt.token = initial_token; } else if (start->level == ssl_encryption_handshake) { - pkt.flags = NGX_QUIC_PKT_HANDSHAKE; + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; } else { - // TODO: macro, set FIXED bit - pkt.flags = 0x40 | (c->quic->key_phase ? NGX_QUIC_PKT_KPHASE : 0); + if (c->quic->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; + } } ngx_quic_set_packet_number(&pkt, ctx); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index a3a306439..d5afaefc1 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -948,7 +948,7 @@ ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) { - if (pkt->level == ssl_encryption_application) { + if (ngx_quic_short_pkt(pkt->flags)) { return ngx_quic_create_short_packet(pkt, ssl_conn, res); } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 9a317411f..5098b505e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -66,6 +66,8 @@ static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst); +static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, + ngx_uint_t frame_type); static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); @@ -528,11 +530,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *f) { u_char *p; - uint8_t flags; uint64_t varint; ngx_uint_t i; - flags = pkt->flags; p = start; p = ngx_quic_parse_int(p, end, &varint); @@ -544,14 +544,14 @@ 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) { + return NGX_DECLINED; + } + switch (f->type) { case NGX_QUIC_FT_CRYPTO: - if (ngx_quic_pkt_zrtt(flags)) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); if (p == NULL) { goto error; @@ -580,8 +580,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_PADDING: - /* allowed in any packet type */ - while (p < end && *p == NGX_QUIC_FT_PADDING) { p++; } @@ -591,10 +589,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: - if (ngx_quic_pkt_zrtt(flags)) { - goto not_allowed; - } - if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) @@ -644,17 +638,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; case NGX_QUIC_FT_PING: - - /* allowed in any packet type */ - break; case NGX_QUIC_FT_NEW_CONNECTION_ID: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); if (p == NULL) { goto error; @@ -686,19 +673,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; case NGX_QUIC_FT_CONNECTION_CLOSE2: - - if (!ngx_quic_short_pkt(flags)) { - goto not_allowed; - } - /* fall through */ case NGX_QUIC_FT_CONNECTION_CLOSE: - if (ngx_quic_pkt_zrtt(flags)) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { goto error; @@ -751,10 +729,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - f->u.stream.type = f->type; f->u.stream.off = ngx_quic_stream_bit_off(f->type); @@ -807,10 +781,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_MAX_DATA: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); if (p == NULL) { goto error; @@ -823,10 +793,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_RESET_STREAM: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) && (p = ngx_quic_parse_int(p, end, @@ -844,10 +810,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STOP_SENDING: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); if (p == NULL) { goto error; @@ -867,10 +829,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAMS_BLOCKED: case NGX_QUIC_FT_STREAMS_BLOCKED2: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); if (p == NULL) { goto error; @@ -886,16 +844,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; - case NGX_QUIC_FT_HANDSHAKE_DONE: - /* only sent by server, not by client */ - goto not_allowed; - case NGX_QUIC_FT_NEW_TOKEN: - - if (!ngx_quic_short_pkt(flags)) { - goto not_allowed; - } - /* TODO: implement */ ngx_log_error(NGX_LOG_ALERT, pkt->log, 0, @@ -906,10 +855,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_MAX_STREAMS: case NGX_QUIC_FT_MAX_STREAMS2: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); if (p == NULL) { goto error; @@ -925,10 +870,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_MAX_STREAM_DATA: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); if (p == NULL) { goto error; @@ -947,10 +888,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_DATA_BLOCKED: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); if (p == NULL) { goto error; @@ -963,10 +900,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAM_DATA_BLOCKED: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); if (p == NULL) { goto error; @@ -986,10 +919,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); if (p == NULL) { goto error; @@ -1003,10 +932,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_PATH_CHALLENGE: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); if (p == NULL) { goto error; @@ -1023,10 +948,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_PATH_RESPONSE: - if (!(ngx_quic_short_pkt(flags) || ngx_quic_pkt_zrtt(flags))) { - goto not_allowed; - } - p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); if (p == NULL) { goto error; @@ -1049,21 +970,81 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return p - start; -not_allowed: +error: ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic frame type 0x%xi is not " - "allowed in packet with flags 0x%xi", - f->type, pkt->flags); + "quic failed to parse frame type 0x%xi", f->type); - return NGX_DECLINED; + return NGX_ERROR; +} -error: + +static ngx_int_t +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +{ + uint8_t ptype; + + /* frame permissions per packet: 4 bits: IH01: 12.4, Table 3 */ + 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 */ 0x1, + /* STREAM0 */ 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 */ 0x3, + /* CONNECTION_CLOSE */ 0xD, + /* CONNECTION_CLOSE2 */ 0x1, + /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + }; + + if (ngx_quic_long_pkt(pkt->flags)) { + + if (ngx_quic_pkt_in(pkt->flags)) { + ptype = 8; /* initial */ + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + ptype = 4; /* handshake */ + + } else { + ptype = 2; /* zero-rtt */ + } + + } else { + ptype = 1; /* application data */ + } + + if (ptype & ngx_quic_frame_masks[frame_type]) { + return NGX_OK; + } ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse frame type 0x%xi", f->type); + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xi", + frame_type, pkt->flags); - return NGX_ERROR; + return NGX_DECLINED; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 761db57c1..78fd4d301 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -12,20 +12,31 @@ #include -#define ngx_quic_long_pkt(flags) ((flags) & 0x80) /* 17.2 */ -#define ngx_quic_short_pkt(flags) (((flags) & 0x80) == 0) /* 17.3 */ +/* QUIC flags in first byte, see quic-transport 17.2 and 17.3 */ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_PKT_FIXED_BIT 0x40 +#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) +#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) /* Long packet types */ -#define NGX_QUIC_PKT_INITIAL 0xC0 /* 17.2.2 */ -#define NGX_QUIC_PKT_ZRTT 0xD0 /* 17.2.3 */ -#define NGX_QUIC_PKT_HANDSHAKE 0xE0 /* 17.2.4 */ -#define NGX_QUIC_PKT_RETRY 0xF0 /* 17.2.5 */ -#define NGX_QUIC_PKT_KPHASE 0x04 /* 17.3 */ - -#define ngx_quic_pkt_in(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_INITIAL) -#define ngx_quic_pkt_zrtt(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_ZRTT) -#define ngx_quic_pkt_hs(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_HANDSHAKE) -#define ngx_quic_pkt_retry(flags) (((flags) & 0xF0) == NGX_QUIC_PKT_RETRY) +#define NGX_QUIC_PKT_INITIAL 0x00 +#define NGX_QUIC_PKT_ZRTT 0x10 +#define NGX_QUIC_PKT_HANDSHAKE 0x20 +#define NGX_QUIC_PKT_RETRY 0x30 + +#define ngx_quic_pkt_in(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) + /* 12.4. Frames and Frame Types */ #define NGX_QUIC_FT_PADDING 0x00 -- cgit v1.2.3 From be478232480f29309e876d2b06c6737fb03dacba Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 29 Apr 2020 14:45:55 +0300 Subject: Removed outdated/incorrect comments and fixed style. - we need transport parameters early to get packet size limits at least. --- src/event/ngx_event_quic.c | 6 ++---- src/event/ngx_event_quic.h | 8 ++++---- src/event/ngx_event_quic_transport.h | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e32d14227..ed6c6cd4e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -379,7 +379,6 @@ 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"); - /* XXX: obtain client parameters after the handshake? */ if (!qc->client_tp_done) { SSL_get_peer_quic_transport_params(ssl_conn, &client_params, @@ -1066,7 +1065,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) break; } - // TODO: check current state + /* TODO: check current state */ if (ngx_quic_long_pkt(pkt.flags)) { if (ngx_quic_pkt_in(pkt.flags)) { @@ -1724,7 +1723,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic ACK for the packet not in sent queue "); - // TODO: handle error properly: PROTOCOL VIOLATION? + /* TODO: handle error properly: PROTOCOL VIOLATION */ return NGX_ERROR; } @@ -2578,7 +2577,6 @@ ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) } -/* pack a group of frames [start; end) into memory p and send as single packet */ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 4482c8b7d..eb184bd7d 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -84,10 +84,10 @@ ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); /********************************* DEBUG *************************************/ -//#define NGX_QUIC_DEBUG_PACKETS /* dump packet contents */ -//#define NGX_QUIC_DEBUG_FRAMES /* dump frames contents */ -//#define NGX_QUIC_DEBUG_FRAMES_ALLOC /* log frames alloc/reuse/free */ -//#define NGX_QUIC_DEBUG_CRYPTO +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_FRAMES_ALLOC */ /* log frames alloc/reuse/free */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ #if (NGX_DEBUG) diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 78fd4d301..e2b383e5e 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -256,7 +256,7 @@ struct ngx_quic_frame_s { ngx_quic_path_challenge_frame_t path_challenge; ngx_quic_path_challenge_frame_t path_response; } u; - u_char info[128]; // for debug + u_char info[128]; /* for debug */ }; -- cgit v1.2.3 From b7b3aca7040a0c734a555efd8775249a29b5ac5d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 30 Apr 2020 15:47:43 +0300 Subject: Configure: unbreak with old OpenSSL, --with-http_v3_module added. --- README | 7 ++++--- auto/lib/openssl/conf | 34 ++++++++++++++++++++++++++-------- auto/modules | 27 ++++++++++++++++++--------- auto/options | 5 ++++- src/core/ngx_core.h | 2 ++ 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/README b/README index d29b5ca5a..e3e3a198c 100644 --- a/README +++ b/README @@ -82,9 +82,10 @@ Experimental QUIC support for nginx $ hg clone https://hg.nginx.org/nginx-quic $ cd nginx-quic - $ ./auto/configure --with-debug --with-cc-opt="-I../boringssl/include" \ - --with-ld-opt="-L../boringssl/build/ssl \ - -L../boringssl/build/crypto" + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../boringssl/include" \ + --with-ld-opt="-L../boringssl/build/ssl \ + -L../boringssl/build/crypto" $ make 3. Configuration diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 4f4390e11..faebd8fa4 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -141,11 +141,29 @@ END fi -ngx_feature="OpenSSL QUIC support" -ngx_feature_name="NGX_OPENSSL_QUIC" -ngx_feature_run=no -ngx_feature_incs="#include " -ngx_feature_path= -ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL" -ngx_feature_test="SSL_CTX_set_quic_method(NULL, NULL)" -. auto/feature + +if [ $USE_OPENSSL_QUIC = YES ]; then + + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_OPENSSL_QUIC" + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL" + ngx_feature_test="SSL_CTX_set_quic_method(NULL, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then + +cat << END + +$0: error: certain modules require OpenSSL QUIC support. +You can either do not enable the modules, or install the OpenSSL library +into the system, or build the OpenSSL library statically from the source +with nginx by using --with-openssl= option. + +END + exit 1 + fi + +fi diff --git a/auto/modules b/auto/modules index abd3aa4b9..67339e7fa 100644 --- a/auto/modules +++ b/auto/modules @@ -404,9 +404,13 @@ if [ $HTTP = YES ]; then ngx_module_type=HTTP if [ $HTTP_V3 = YES ]; then + USE_OPENSSL=YES + USE_OPENSSL_QUIC=YES have=NGX_HTTP_V3 . auto/have have=NGX_HTTP_HEADERS . auto/have + HTTP_SSL=YES + # XXX for Huffman HTTP_V2=YES @@ -1265,19 +1269,24 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_openssl_module ngx_module_incs= - ngx_module_deps="src/event/ngx_event_openssl.h \ - src/event/ngx_event_quic.h \ - src/event/ngx_event_quic_transport.h \ - src/event/ngx_event_quic_protection.h" - ngx_module_srcs="src/event/ngx_event_openssl.c \ - src/event/ngx_event_openssl_stapling.c \ - src/event/ngx_event_quic.c \ - src/event/ngx_event_quic_transport.c \ - src/event/ngx_event_quic_protection.c" + ngx_module_deps=src/event/ngx_event_openssl.h + ngx_module_srcs="src/event/ngx_event_openssl.c + src/event/ngx_event_openssl_stapling.c" ngx_module_libs= ngx_module_link=YES ngx_module_order= + if [ $USE_OPENSSL_QUIC = YES ]; then + ngx_module_deps="$ngx_module_deps \ + src/event/ngx_event_quic.h \ + src/event/ngx_event_quic_transport.h \ + src/event/ngx_event_quic_protection.h" + ngx_module_srcs="$ngx_module_srcs \ + src/event/ngx_event_quic.c \ + src/event/ngx_event_quic_transport.c \ + src/event/ngx_event_quic_protection.c" + fi + . auto/module fi diff --git a/auto/options b/auto/options index de1634462..ddb861783 100644 --- a/auto/options +++ b/auto/options @@ -59,7 +59,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO -HTTP_V3=YES +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -146,6 +146,7 @@ PCRE_CONF_OPT= PCRE_JIT=NO USE_OPENSSL=NO +USE_OPENSSL_QUIC=NO OPENSSL=NONE USE_ZLIB=NO @@ -225,6 +226,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -440,6 +442,7 @@ cat << END --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index eb3acd663..a8959ddcc 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -84,10 +84,12 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #if (NGX_OPENSSL) #include +#if (NGX_OPENSSL_QUIC) #include #include #include #endif +#endif #include #include #include -- cgit v1.2.3 From cf5168c9e028d19f8408b46f35299a91990f0848 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 30 Apr 2020 15:59:14 +0300 Subject: Mention quic branch in README. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index e3e3a198c..56229e0bf 100644 --- a/README +++ b/README @@ -80,7 +80,7 @@ Experimental QUIC support for nginx You will need a BoringSSL [4] library that provides QUIC support - $ hg clone https://hg.nginx.org/nginx-quic + $ hg clone -b quic https://hg.nginx.org/nginx-quic $ cd nginx-quic $ ./auto/configure --with-debug --with-http_v3_module \ --with-cc-opt="-I../boringssl/include" \ -- cgit v1.2.3 From 1a6fc01fb853052536b49cbb4fe612c8ac4467ab Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 1 May 2020 13:02:30 +0300 Subject: Configure: fixed static compilation with OpenSSL 1.1.1 / BoringSSL. See 7246:04ebf29eaf5b for details. --- auto/lib/openssl/conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index faebd8fa4..046317f8a 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -149,7 +149,7 @@ if [ $USE_OPENSSL_QUIC = YES ]; then ngx_feature_run=no ngx_feature_incs="#include " ngx_feature_path= - ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL" + ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" ngx_feature_test="SSL_CTX_set_quic_method(NULL, NULL)" . auto/feature -- cgit v1.2.3 From 5e5c703656a93b6b6413bdbe72043c863fb5f224 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 30 Apr 2020 12:22:35 +0300 Subject: Store clearflags in pkt->flags after decryption. It doesn't make sense to store protected flags. --- src/event/ngx_event_quic_protection.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index d5afaefc1..354d4f9fa 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1015,6 +1015,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pn = ngx_quic_parse_pn(&p, pnl, &mask[1], largest_pn); pkt->pn = pn; + pkt->flags = clearflags; #ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); -- cgit v1.2.3 From 9ed0d4c9aed944d6eac72e3f5c98d73ce48e522d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 6 May 2020 14:34:44 +0300 Subject: Restored ngx_quic_encrypt return type. It was inadvertently changed while working on removing memory allocations. --- src/event/ngx_event_quic_protection.c | 10 +++++----- src/event/ngx_event_quic_protection.h | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 354d4f9fa..6af047d40 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -53,9 +53,9 @@ static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); -static ssize_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, +static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); -static ssize_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, +static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); @@ -752,7 +752,7 @@ ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, } -static ssize_t +static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) { @@ -819,7 +819,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } -static ssize_t +static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) { @@ -944,7 +944,7 @@ ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) } -ssize_t +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res) { diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index befb7ef66..37d7c37b1 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -40,9 +40,8 @@ int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, ngx_int_t ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, ngx_quic_secrets_t *next); -ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); - ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, uint64_t *largest_pn); -- cgit v1.2.3 From 1816f3af588f4809c2bf3bd7045e12156eaa8275 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 7 May 2020 12:34:04 +0300 Subject: Cleaned up firefox workaround. The idea is to skip any zeroes that follow valid QUIC packet. Currently such behavior can be only observed with Firefox which sends zero-padded initial packets. --- src/event/ngx_event_quic.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ed6c6cd4e..f270ab7fe 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -163,6 +163,7 @@ static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc); static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); +static ngx_inline u_char *ngx_quic_skip_zero_padding(ngx_buf_t *b); static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, @@ -664,6 +665,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, /* pos is at header end, adjust by actual packet length */ pkt->raw->pos += pkt->len; + (void) ngx_quic_skip_zero_padding(pkt->raw); + return ngx_quic_input(c, pkt->raw); } @@ -1057,14 +1060,6 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.log = c->log; pkt.flags = p[0]; - if (pkt.flags == 0) { - /* XXX: no idea WTF is this, just ignore */ - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "quic packet with zero flags, presumably" - " firefox padding, ignored"); - break; - } - /* TODO: check current state */ if (ngx_quic_long_pkt(pkt.flags)) { @@ -1108,14 +1103,26 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) */ /* b->pos is at header end, adjust by actual packet length */ - p = b->pos + pkt.len; - b->pos = p; /* reset b->pos to the next packet start */ + b->pos += pkt.len; + p = ngx_quic_skip_zero_padding(b); } return NGX_OK; } +/* firefox workaround: skip zero padding at the end of quic packet */ +static ngx_inline u_char * +ngx_quic_skip_zero_padding(ngx_buf_t *b) +{ + while (b->pos < b->last && *(b->pos) == 0) { + b->pos++; + } + + return b->pos; +} + + static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { -- cgit v1.2.3 From 7017a641839ef8922b59d2737e35c2bc4d3d023f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 8 May 2020 13:08:04 +0300 Subject: Cleaned up reordering code. The ordered frame handler is always called for the existing stream, as it is allocated from this stream. Instead of searching stream by id, pointer to the stream node is passed. --- src/event/ngx_event_quic.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f270ab7fe..66162b4a6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -126,7 +126,7 @@ struct ngx_quic_connection_s { typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, - ngx_quic_frame_t *frame); + ngx_quic_frame_t *frame, void *data); #if BORINGSSL_API_VERSION >= 10 @@ -189,7 +189,7 @@ static void ngx_quic_handle_stream_ack(ngx_connection_t *c, static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, - ngx_quic_frame_handler_pt handler); + ngx_quic_frame_handler_pt handler, void *data); static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *f, uint64_t offset_in); static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, @@ -198,11 +198,11 @@ static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, - ngx_quic_frame_t *frame); + ngx_quic_frame_t *frame, void *data); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, - ngx_quic_frame_t *frame); + ngx_quic_frame_t *frame, void *data); static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, @@ -1780,7 +1780,7 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler) + ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) { size_t full_len; ngx_int_t rc; @@ -1811,7 +1811,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* f->offset == fs->received */ - rc = handler(c, frame); + rc = handler(c, frame, data); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1863,7 +1863,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* f->offset == fs->received */ - rc = handler(c, frame); + rc = handler(c, frame, data); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -2000,12 +2000,13 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qc = c->quic; fs = &qc->crypto[pkt->level]; - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input); + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, + NULL); } static ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame) +ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { int sslerr; ssize_t n; @@ -2091,8 +2092,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { size_t n; - ngx_buf_t *b; - ngx_event_t *rev; + ngx_buf_t *b; + ngx_event_t *rev; ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -2170,12 +2171,13 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, fs = &sn->fs; - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input); + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn); } static ngx_int_t -ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) +ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { ngx_buf_t *b; ngx_event_t *rev; @@ -2184,16 +2186,10 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame) ngx_quic_stream_frame_t *f; qc = c->quic; + sn = data; f = &frame->u.stream; - sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - if (sn == NULL) { - // TODO: possible? - // stream was deleted while in reordering queue ? - return NGX_ERROR; - } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic existing stream"); b = sn->b; -- cgit v1.2.3 From 13fb3fbd6762ea458fbfced66dbb415ad330ca6c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 9 May 2020 17:39:47 +0300 Subject: Removed redundant SSL_do_handshake call before any handshake data. --- src/event/ngx_event_quic.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 66162b4a6..74db8bc97 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -699,7 +699,6 @@ ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *cid) static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { - int n, sslerr; u_char *p; ssize_t len; ngx_ssl_conn_t *ssl_conn; @@ -751,28 +750,6 @@ ngx_quic_init_connection(ngx_connection_t *c) qc->max_streams = qc->tp.initial_max_streams_bidi; qc->state = NGX_QUIC_ST_HANDSHAKE; - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_do_handshake: %d", n); - - if (n == -1) { - sslerr = SSL_get_error(ssl_conn, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_get_error: %d", sslerr); - - if (sslerr != SSL_ERROR_WANT_READ) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - return NGX_ERROR; - } - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - return NGX_OK; } -- cgit v1.2.3 From 7ab07b34092341bef7dc98f44189f43ab1f86c3f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 9 May 2020 17:41:07 +0300 Subject: Removed redundant long packet type checks. --- src/event/ngx_event_quic.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 74db8bc97..93b9a40b5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1165,12 +1165,6 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (!ngx_quic_pkt_hs(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid packet type: 0x%xi", pkt->flags); - return NGX_ERROR; - } - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1210,12 +1204,6 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (!ngx_quic_pkt_zrtt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid packet type: 0x%xi", pkt->flags); - return NGX_ERROR; - } - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From a0cc9398eda37dd28e148ab5f598c278eb89b668 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 12 May 2020 18:18:58 +0300 Subject: Preserve original DCID and unbreak parsing 0-RTT packets. As per QUIC transport, the first flight of 0-RTT packets obviously uses same Destination and Source Connection ID values as the client's first Initial. The fix is to match 0-RTT against original DCID after it has been switched. --- src/event/ngx_event_quic.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 93b9a40b5..9a53afa9a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -87,6 +87,7 @@ typedef struct { struct ngx_quic_connection_s { ngx_str_t scid; ngx_str_t dcid; + ngx_str_t odcid; ngx_str_t token; ngx_uint_t client_tp_done; @@ -621,6 +622,13 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); #endif + qc->odcid.len = pkt->dcid.len; + qc->odcid.data = ngx_pnalloc(c->pool, qc->odcid.len); + if (qc->odcid.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(qc->odcid.data, pkt->dcid.data, qc->odcid.len); + qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { @@ -638,7 +646,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, keys = &c->quic->keys[ssl_encryption_initial]; if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, - &pkt->dcid) + &qc->odcid) != NGX_OK) { return NGX_ERROR; @@ -1232,12 +1240,16 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { - if (pkt->dcid.len != qc->dcid.len) { + ngx_str_t *dcid; + + dcid = ngx_quic_pkt_zrtt(pkt->flags) ? &qc->odcid : &qc->dcid; + + if (pkt->dcid.len != dcid->len) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcidl"); return NGX_ERROR; } - if (ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) != 0) { + if (ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) != 0) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid"); return NGX_ERROR; } -- cgit v1.2.3 From d35eebede2dc224cff5773badb6d75ad05c9bd65 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 13 May 2020 18:34:34 +0300 Subject: Server CID change refactored. --- src/event/ngx_event_quic.c | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9a53afa9a..d13274159 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -153,7 +153,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler); -static ngx_int_t ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *sid); +static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -614,21 +614,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; qc->congestion.recovery_start = ngx_current_msec; - if (ngx_quic_new_cid(c->pool, &qc->dcid) != NGX_OK) { + if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { return NGX_ERROR; } -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); -#endif - - qc->odcid.len = pkt->dcid.len; - qc->odcid.data = ngx_pnalloc(c->pool, qc->odcid.len); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->odcid.data, pkt->dcid.data, qc->odcid.len); - qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { @@ -680,9 +669,12 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, static ngx_int_t -ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *cid) +ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) { - uint8_t len; + uint8_t len; + ngx_quic_connection_t *qc; + + qc = c->quic; if (RAND_bytes(&len, sizeof(len)) != 1) { return NGX_ERROR; @@ -690,13 +682,23 @@ ngx_quic_new_cid(ngx_pool_t *pool, ngx_str_t *cid) len = len % 10 + 10; - cid->len = len; - cid->data = ngx_pnalloc(pool, len); - if (cid->data == NULL) { + qc->dcid.len = len; + qc->dcid.data = ngx_pnalloc(c->pool, len); + if (qc->dcid.data == NULL) { return NGX_ERROR; } - if (RAND_bytes(cid->data, len) != 1) { + if (RAND_bytes(qc->dcid.data, len) != 1) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); +#endif + + qc->odcid.len = odcid->len; + qc->odcid.data = ngx_pstrdup(c->pool, odcid); + if (qc->odcid.data == NULL) { return NGX_ERROR; } -- cgit v1.2.3 From ad2289e70ed3b3c0d0fb75b554f493a60db99307 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 14 May 2020 15:47:18 +0300 Subject: Address validation using Retry packets. The behaviour is toggled with the new directive "quic_retry on|off". QUIC token construction is made suitable for issuing with NEW_TOKEN. --- src/event/ngx_event_quic.c | 377 +++++++++++++++++++++++++++++++++- src/event/ngx_event_quic.h | 12 +- src/event/ngx_event_quic_protection.c | 53 +++++ src/event/ngx_event_quic_transport.c | 42 ++++ src/event/ngx_event_quic_transport.h | 4 + src/http/v3/ngx_http_v3_module.c | 18 ++ 6 files changed, 497 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d13274159..2fd629c3b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -123,6 +123,7 @@ struct ngx_quic_connection_s { unsigned closing:1; unsigned draining:1; unsigned key_phase:1; + unsigned in_retry:1; }; @@ -154,6 +155,10 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); +static ngx_int_t ngx_quic_retry(ngx_connection_t *c); +static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); +static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -165,6 +170,8 @@ static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); static ngx_inline u_char *ngx_quic_skip_zero_padding(ngx_buf_t *b); +static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, @@ -524,7 +531,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return; } - ngx_add_timer(c->read, c->quic->tp.max_idle_timeout); + ngx_add_timer(c->read, c->quic->in_retry ? NGX_QUIC_RETRY_TIMEOUT + : c->quic->tp.max_idle_timeout); c->read->handler = ngx_quic_input_handler; @@ -625,13 +633,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, } ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - qc->token.len = pkt->token.len; - qc->token.data = ngx_pnalloc(c->pool, qc->token.len); - if (qc->token.data == NULL) { - return NGX_ERROR; - } - ngx_memcpy(qc->token.data, pkt->token.data, qc->token.len); - keys = &c->quic->keys[ssl_encryption_initial]; if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, @@ -641,6 +642,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + if (tp->retry) { + return ngx_quic_retry(c); + } + pkt->secret = &keys->client; pkt->level = ssl_encryption_initial; pkt->plaintext = buf; @@ -706,6 +711,270 @@ ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) } +static ngx_int_t +ngx_quic_retry(ngx_connection_t *c) +{ + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + + if (ngx_quic_new_token(c, &token) != NGX_OK) { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.log = c->log; + pkt.odcid = c->quic->odcid; + pkt.dcid = c->quic->scid; + pkt.scid = c->quic->dcid; + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, NULL, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len); +#endif + + len = c->send(c, res.data, res.len); + if (len == NGX_ERROR || (size_t) len != res.len) { + return NGX_ERROR; + } + + c->quic->token = token; + c->quic->tp.original_connection_id = c->quic->odcid; + c->quic->in_retry = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) +{ + int len, iv_len; + u_char *data, *p, *key, *iv; + ngx_msec_t now; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + + len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now)); + data = c->addr_text.data; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + + p = ngx_cpymem(in, data, len); + + now = ngx_current_msec; + len += sizeof(now); + ngx_memcpy(p, &now, sizeof(now)); + + cipher = EVP_aes_256_cbc(); + iv_len = EVP_CIPHER_iv_length(cipher); + + token->len = iv_len + len + EVP_CIPHER_block_size(cipher); + token->data = ngx_pnalloc(c->pool, token->len); + if (token->data == NULL) { + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + key = c->quic->tp.token_key; + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(c->log, "quic new token", token->data, token->len); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *key, *iv, *p, *data; + ngx_msec_t msec; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + ngx_quic_connection_t *qc; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + + if (pkt->token.len == 0) { + return NGX_ERROR; + } + + qc = c->quic; + + /* Retry token */ + + if (qc->token.len) { + if (pkt->token.len != qc->token.len) { + return NGX_ERROR; + } + + if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { + return NGX_ERROR; + } + + return NGX_OK; + } + + /* NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_cbc(); + key = c->quic->tp.token_key; + iv = pkt->token.data; + iv_len = EVP_CIPHER_iv_length(cipher); + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + return NGX_ERROR; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len; + + if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + EVP_CIPHER_CTX_free(ctx); + + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + + len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec)); + data = c->addr_text.data; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + + if (ngx_memcmp(tdec, data, len) != 0) { + return NGX_ERROR; + } + + ngx_memcpy(&msec, tdec + len, sizeof(msec)); + + if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { + return NGX_DECLINED; + } + + return NGX_OK; +} + + static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { @@ -776,6 +1045,7 @@ ngx_quic_input_handler(ngx_event_t *rev) b.start = buf; b.end = buf + sizeof(buf); b.pos = b.last = b.start; + b.memory = 1; c = rev->data; qc = c->quic; @@ -1047,6 +1317,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.log = c->log; pkt.flags = p[0]; + if (c->quic->in_retry) { + return ngx_quic_retry_input(c, &pkt); + } + /* TODO: check current state */ if (ngx_quic_long_pkt(pkt.flags)) { @@ -1110,6 +1384,93 @@ ngx_quic_skip_zero_padding(ngx_buf_t *b) } +static ngx_int_t +ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + + c->log->action = "retrying quic connection"; + + qc = c->quic; + + if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_OK; + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_pkt_zrtt(pkt->flags)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic discard inflight 0-RTT packet"); + return NGX_OK; + } + + if (!ngx_quic_pkt_in(pkt->flags)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid initial packet: 0x%xi", pkt->flags); + return NGX_ERROR; + } + + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { + return NGX_ERROR; + } + + qc = c->quic; + + keys = &c->quic->keys[ssl_encryption_initial]; + + if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, + &qc->odcid) + != NGX_OK) + { + return NGX_ERROR; + } + + c->quic->in_retry = 0; + + if (ngx_quic_validate_token(c, pkt) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + return NGX_ERROR; + } + + pkt->secret = &keys->client; + pkt->level = ssl_encryption_initial; + pkt->plaintext = buf; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_payload_handler(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + /* pos is at header end, adjust by actual packet length */ + pkt->raw->pos += pkt->len; + + (void) ngx_quic_skip_zero_padding(pkt->raw); + + return ngx_quic_input(c, pkt->raw); +} + + static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index eb184bd7d..862b339eb 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -23,6 +23,13 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_RETRY_TIMEOUT 3000 +#define NGX_QUIC_RETRY_LIFETIME 30000 +#define NGX_QUIC_RETRY_BUFFER_SIZE 128 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */ +#define NGX_QUIC_MAX_TOKEN_SIZE 32 + /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ + #define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */ #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ @@ -49,9 +56,12 @@ typedef struct { ngx_uint_t ack_delay_exponent; ngx_uint_t disable_active_migration; ngx_uint_t active_connection_id_limit; + ngx_str_t original_connection_id; + + ngx_flag_t retry; + u_char token_key[32]; /* AES 256 */ /* TODO */ - ngx_uint_t original_connection_id; u_char stateless_reset_token[16]; void *preferred_address; } ngx_quic_tp_t; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 6af047d40..97fc58ecc 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -57,6 +57,8 @@ static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); +static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); static ngx_int_t @@ -891,6 +893,53 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + ngx_quic_secret_t secret; + ngx_quic_ciphers_t ciphers; + + /* 5.8. Retry Packet Integrity */ + static u_char key[16] = + "\x4d\x32\xec\xdb\x2a\x21\x33\xc8" + "\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; + static u_char nonce[12] = + "\x4d\x16\x11\xd0\x55\x13" + "\xa5\x52\xc5\x87\xd5\x75"; + static ngx_str_t in = ngx_string(""); + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len); +#endif + + if (ngx_quic_ciphers(NULL, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + secret.key.len = sizeof(key); + secret.key.data = key; + secret.iv.len = sizeof(nonce); + + if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = itag.data + itag.len - start; + res->data = start; + + return NGX_OK; +} + + static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, uint64_t *largest_pn) @@ -952,6 +1001,10 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return ngx_quic_create_short_packet(pkt, ssl_conn, res); } + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_create_retry_packet(pkt, res); + } + return ngx_quic_create_long_packet(pkt, ssl_conn, res); } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 5098b505e..7f064eb54 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -385,6 +385,35 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, } +size_t +ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start) +{ + u_char *p; + + p = out; + + *p++ = pkt->odcid.len; + p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); + + *start = p; + + *p++ = 0xff; + + p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + + return p - out; +} + + ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) { @@ -1553,6 +1582,12 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + if (tp->retry) { + len += ngx_quic_varint_len(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID); + len += ngx_quic_varint_len(tp->original_connection_id.len); + len += tp->original_connection_id.len; + } + if (pos == NULL) { return len; } @@ -1581,6 +1616,13 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + if (tp->retry) { + ngx_quic_build_int(&p, NGX_QUIC_TP_ORIGINAL_CONNECTION_ID); + ngx_quic_build_int(&p, tp->original_connection_id.len); + p = ngx_cpymem(p, tp->original_connection_id.data, + tp->original_connection_id.len); + } + return p - pos; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index e2b383e5e..62ec842d6 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -280,6 +280,7 @@ typedef struct { size_t len; /* cleartext fields */ + ngx_str_t odcid; /* retry packet tag */ ngx_str_t dcid; ngx_str_t scid; uint64_t pn; @@ -303,6 +304,9 @@ ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); +size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start); + ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 9daaedb3e..efad51c71 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -111,6 +111,13 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), &ngx_http_v3_active_connection_id_limit_bounds }, + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.retry), + NULL }, + ngx_null_command }; @@ -257,6 +264,8 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT; v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + v3cf->quic.retry = NGX_CONF_UNSET; + return v3cf; } @@ -310,6 +319,15 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, prev->quic.active_connection_id_limit, 2); + ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); + + if (conf->quic.retry) { + if (RAND_bytes(conf->quic.token_key, sizeof(conf->quic.token_key)) <= 0) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; } -- cgit v1.2.3 From 9b1800d09a0f8017762d003e109393bd0686b92d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 14 May 2020 15:47:24 +0300 Subject: Address validation using NEW_TOKEN frame. --- src/event/ngx_event_quic.c | 52 +++++++++++++++++++++++++++++++++++- src/event/ngx_event_quic_transport.c | 29 ++++++++++++++++++++ src/event/ngx_event_quic_transport.h | 6 +++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2fd629c3b..6bb348892 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -187,6 +187,7 @@ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, ngx_uint_t err); +static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); @@ -544,6 +545,7 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler) { + ngx_int_t rc; ngx_uint_t i; ngx_quic_tp_t *ctp; ngx_quic_secrets_t *keys; @@ -642,7 +644,22 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - if (tp->retry) { + if (pkt->token.len) { + rc = ngx_quic_validate_token(c, pkt); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return ngx_quic_retry(c); + } + + /* NGX_OK */ + + } else if (tp->retry) { return ngx_quic_retry(c); } @@ -1950,6 +1967,35 @@ ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, } +static ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c) +{ + ngx_str_t token; + ngx_quic_frame_t *frame; + + if (!c->quic->tp.retry) { + return NGX_OK; + } + + if (ngx_quic_new_token(c, &token) != NGX_OK) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->u.token.length = token.len; + frame->u.token.data = token.data; + ngx_sprintf(frame->info, "NEW_TOKEN"); + ngx_quic_queue_frame(c->quic, frame); + + return NGX_OK; +} + static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) @@ -2405,6 +2451,10 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed"); ngx_quic_queue_frame(c->quic, frame); + if (ngx_quic_send_new_token(c) != NGX_OK) { + return NGX_ERROR; + } + /* * Generating next keys before a key update is received. * See quic-tls 9.4 Header Protection Timing Side-Channels. diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7f064eb54..ddf99a3bc 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -72,6 +72,8 @@ static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); static size_t ngx_quic_create_hs_done(u_char *p); +static size_t ngx_quic_create_new_token(u_char *p, + ngx_quic_new_token_frame_t *token); static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); @@ -1128,6 +1130,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_HANDSHAKE_DONE: return ngx_quic_create_hs_done(p); + case NGX_QUIC_FT_NEW_TOKEN: + return ngx_quic_create_new_token(p, &f->u.token); + case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: @@ -1231,6 +1236,30 @@ ngx_quic_create_hs_done(u_char *p) } +static size_t +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); + len += ngx_quic_varint_len(token->length); + len += token->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); + ngx_quic_build_int(&p, token->length); + p = ngx_cpymem(p, token->data, token->length); + + return p - start; +} + + static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 62ec842d6..1ff70f97b 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -132,6 +132,11 @@ typedef struct { } ngx_quic_new_conn_id_frame_t; +typedef struct { + uint64_t length; + u_char *data; +} ngx_quic_new_token_frame_t; + /* * common layout for CRYPTO and STREAM frames; * conceptually, CRYPTO frame is also a stream @@ -242,6 +247,7 @@ struct ngx_quic_frame_s { ngx_quic_crypto_frame_t crypto; ngx_quic_ordered_frame_t ord; ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_new_token_frame_t token; ngx_quic_stream_frame_t stream; ngx_quic_max_data_frame_t max_data; ngx_quic_close_frame_t close; -- cgit v1.2.3 From cb316c5f589593c368404e59e7c1094864724481 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 14 May 2020 15:54:45 +0300 Subject: Added generation of CC frames with error on connection termination. When an error occurs, then c->quic->error field may be populated with an appropriate error code, and the CONNECTION CLOSE frame will be sent to the peer before the connection is closed. Otherwise, the error treated as internal and INTERNAL_ERROR code is sent. The pkt->error field is populated by functions processing packets to indicate an error when it does not fit into pass/fail return status. --- src/event/ngx_event_quic.c | 84 ++++++++++++++++++++++-------------- src/event/ngx_event_quic_transport.c | 6 ++- src/event/ngx_event_quic_transport.h | 1 + 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6bb348892..bbe1e79c3 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -34,6 +34,7 @@ typedef enum { + NGX_QUIC_ST_UNAVAIL, /* connection not ready */ NGX_QUIC_ST_INITIAL, /* connection just created */ NGX_QUIC_ST_HANDSHAKE, /* handshake started */ NGX_QUIC_ST_EARLY_DATA, /* handshake in progress */ @@ -119,6 +120,8 @@ struct ngx_quic_connection_s { uint64_t cur_streams; uint64_t max_streams; + ngx_uint_t error; + unsigned send_timer_set:1; unsigned closing:1; unsigned draining:1; @@ -405,6 +408,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) != NGX_OK) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; return NGX_ERROR; } @@ -417,6 +421,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, if (qc->ctp.max_packet_size < NGX_QUIC_MIN_INITIAL_SIZE || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic maximum packet size is invalid"); return NGX_ERROR; @@ -580,8 +585,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - qc->state = NGX_QUIC_ST_INITIAL; - ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -608,6 +611,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->push.cancelable = 1; c->quic = qc; + qc->state = NGX_QUIC_ST_UNAVAIL; qc->ssl = ssl; qc->tp = *tp; qc->streams.handler = handler; @@ -644,6 +648,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + qc->state = NGX_QUIC_ST_INITIAL; + if (pkt->token.len) { rc = ngx_quic_validate_token(c, pkt); @@ -896,10 +902,12 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) if (qc->token.len) { if (pkt->token.len != qc->token.len) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -916,6 +924,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) /* sanity checks */ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -938,11 +947,13 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { EVP_CIPHER_CTX_free(ctx); + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { EVP_CIPHER_CTX_free(ctx); + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -979,6 +990,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (ngx_memcmp(tdec, data, len) != 0) { + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; return NGX_ERROR; } @@ -1116,7 +1128,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_close_connection, rc: %i", rc); - if (!c->quic) { + if (!c->quic || c->quic->state == NGX_QUIC_ST_UNAVAIL) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close connection early error"); @@ -1149,7 +1161,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { - ngx_uint_t i; + ngx_uint_t i, err; ngx_quic_connection_t *qc; enum ssl_encryption_level_t level; @@ -1157,6 +1169,20 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) if (!qc->closing) { + switch (qc->state) { + case NGX_QUIC_ST_INITIAL: + level = ssl_encryption_initial; + break; + + case NGX_QUIC_ST_HANDSHAKE: + level = ssl_encryption_handshake; + break; + + default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ + level = ssl_encryption_application; + break; + } + if (rc == NGX_OK) { /* @@ -1168,20 +1194,6 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic immediate close, drain = %d", qc->draining); - switch (qc->state) { - case NGX_QUIC_ST_INITIAL: - level = ssl_encryption_initial; - break; - - case NGX_QUIC_ST_HANDSHAKE: - level = ssl_encryption_handshake; - break; - - default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ - level = ssl_encryption_application; - break; - } - if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR) == NGX_OK) { qc->close.log = c->log; @@ -1206,8 +1218,12 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->draining ? "drained" : "idle"); } else { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to fatal error"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to fatal error: %ui", + qc->error); + + err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; + (void) ngx_quic_send_cc(c, level, err); } qc->closing = 1; @@ -1749,14 +1765,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) len = ngx_quic_parse_frame(pkt, p, end, &frame); - if (len == NGX_DECLINED) { - /* TODO: handle protocol violation: - * such frame not allowed in this packet - */ - return NGX_ERROR; - } - if (len < 0) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1887,6 +1897,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) if (p != end) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic trailing garbage in payload: %ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; return NGX_ERROR; } @@ -2012,12 +2024,13 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, "quic ngx_quic_handle_ack_frame level %d", pkt->level); /* - * TODO: If any computed packet number is negative, an endpoint MUST - * generate a connection error of type FRAME_ENCODING_ERROR. - * (19.3.1) + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + * (19.3.1) */ if (ack->first_range > ack->largest) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid first range in ack frame"); return NGX_ERROR; @@ -2049,6 +2062,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, pos += n; if (gap >= min) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; @@ -2057,6 +2071,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, max = min - 1 - gap; if (range > max + 1) { + c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; @@ -2116,7 +2131,9 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic ACK for the packet not in sent queue "); - /* TODO: handle error properly: PROTOCOL VIOLATION */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; } @@ -2978,6 +2995,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_msec_t now; ngx_str_t out, res; ngx_queue_t *q; + ngx_ssl_conn_t *ssl_conn; ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; @@ -2990,6 +3008,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_send_frames"); + ssl_conn = c->ssl ? c->ssl->connection : NULL; + q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -3077,7 +3097,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) out.len, start->level, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); - if (ngx_quic_encrypt(&pkt, c->ssl->connection, &res) != NGX_OK) { + if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ddf99a3bc..4dcb78abd 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -568,6 +568,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic failed to obtain quic frame type"); return NGX_ERROR; @@ -576,7 +577,8 @@ 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) { - return NGX_DECLINED; + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; } switch (f->type) { @@ -1003,6 +1005,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, error: + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic failed to parse frame type 0x%xi", f->type); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 1ff70f97b..364f827ac 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -278,6 +278,7 @@ typedef struct { uint32_t version; ngx_str_t token; enum ssl_encryption_level_t level; + ngx_uint_t error; /* filled in by parser */ ngx_buf_t *raw; /* udp datagram */ -- cgit v1.2.3 From d4f9bba1110b95cabf964075031583e1816be56b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 14 May 2020 01:06:45 +0300 Subject: Discard packets without fixed bit or reserved bits set. Section 17.2 and 17.3 of QUIC transport: Fixed bit: Packets containing a zero value for this bit are not valid packets in this version and MUST be discarded. Reserved bit: An endpoint MUST treat receipt of a packet that has a non-zero value for these bits, after removing both packet and header protection, as a connection error of type PROTOCOL_VIOLATION. --- src/event/ngx_event_quic.c | 40 ++++++++++++++++++++++++----------- src/event/ngx_event_quic_protection.c | 24 ++++++++++++++++++++- src/event/ngx_event_quic_transport.c | 10 +++++++++ src/event/ngx_event_quic_transport.h | 3 +++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index bbe1e79c3..26e307bbe 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -564,8 +564,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_long_header(pkt); + if (rc != NGX_OK) { + return rc; } if (!ngx_quic_pkt_in(pkt->flags)) { @@ -676,6 +677,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ctx = ngx_quic_get_send_ctx(qc, pkt->level); if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1420,6 +1422,7 @@ ngx_quic_skip_zero_padding(ngx_buf_t *b) static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1435,8 +1438,9 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_OK; } - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_long_header(pkt); + if (rc != NGX_OK) { + return rc; } if (ngx_quic_pkt_zrtt(pkt->flags)) { @@ -1484,6 +1488,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1507,6 +1512,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_ssl_conn_t *ssl_conn; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; @@ -1516,8 +1522,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ssl_conn = c->ssl->connection; - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_long_header(pkt); + if (rc != NGX_OK) { + return rc; } if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { @@ -1533,6 +1540,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); if (ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn) != NGX_OK) { + c->quic->error = pkt->error; return NGX_ERROR; } @@ -1543,6 +1551,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1561,8 +1570,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } /* extract cleartext data into pkt */ - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_long_header(pkt); + if (rc != NGX_OK) { + return rc; } if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { @@ -1580,6 +1590,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1590,6 +1601,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1600,8 +1612,9 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; /* extract cleartext data into pkt */ - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_long_header(pkt); + if (rc != NGX_OK) { + return rc; } if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { @@ -1626,6 +1639,7 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + qc->error = pkt->error; return NGX_ERROR; } @@ -1686,8 +1700,9 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } - if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { - return NGX_ERROR; + rc = ngx_quic_parse_short_header(pkt, &qc->dcid); + if (rc != NGX_OK) { + return rc; } pkt->secret = &keys->client; @@ -1699,6 +1714,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + qc->error = pkt->error; return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 97fc58ecc..e44a13dee 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1014,6 +1014,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, uint64_t *largest_pn) { u_char clearflags, *p, *sample; + uint8_t badflags; uint64_t pn; ngx_int_t pnl, rc, key_phase; ngx_str_t in, ad; @@ -1048,6 +1049,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { + pkt->error = NGX_QUIC_ERR_CRYPTO_ERROR; return NGX_ERROR; } @@ -1085,9 +1087,11 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (ngx_quic_long_pkt(pkt->flags)) { in.len = pkt->len - pnl; + badflags = clearflags & NGX_QUIC_PKT_LONG_RESERVED_BIT; } else { in.len = pkt->data + pkt->len - p; + badflags = clearflags & NGX_QUIC_PKT_SHORT_RESERVED_BIT; } ad.len = p - pkt->data; @@ -1124,6 +1128,24 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->payload.data, pkt->payload.len); #endif - return rc; + if (rc != NGX_OK) { + pkt->error = NGX_QUIC_ERR_CRYPTO_ERROR; + return rc; + } + + if (badflags) { + /* + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic reserved bit set in packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + return NGX_OK; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 4dcb78abd..1732f8b0f 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -265,6 +265,11 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) "quic long packet flags:%xi version:%xD", pkt->flags, pkt->version); + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_DECLINED; + } + if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unsupported version: 0x%xi", pkt->version); @@ -443,6 +448,11 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic short packet flags:%xi", pkt->flags); + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_DECLINED; + } + if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); return NGX_ERROR; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 364f827ac..c3b2bbf01 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -19,6 +19,9 @@ #define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ #define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ +#define NGX_QUIC_PKT_LONG_RESERVED_BIT 0x0C +#define NGX_QUIC_PKT_SHORT_RESERVED_BIT 0x18 + #define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) #define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) -- cgit v1.2.3 From 5ccda6882e9f72b9df01fa4b9403376f0899addc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 14 May 2020 14:49:28 +0300 Subject: Added tests for connection id lengths in initial packet. --- src/event/ngx_event_quic.c | 8 ++++++++ src/event/ngx_event_quic_transport.c | 12 ++++++++++++ src/event/ngx_event_quic_transport.h | 5 ++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 26e307bbe..2ebb72f24 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -579,6 +579,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial packet: length %i", + pkt->dcid.len); + return NGX_ERROR; + } + c->log->action = "creating new quic connection"; qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1732f8b0f..5d0182032 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -283,6 +283,12 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) return NGX_ERROR; } + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet dcid is too long"); + return NGX_ERROR; + } + pkt->dcid.len = idlen; p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); @@ -299,6 +305,12 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) return NGX_ERROR; } + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet scid is too long"); + return NGX_ERROR; + } + pkt->scid.len = idlen; p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index c3b2bbf01..35db6ccf3 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -112,6 +112,9 @@ #define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D #define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_CID_LEN_MIN 8 +#define NGX_QUIC_CID_LEN_MAX 20 + typedef struct { uint64_t largest; @@ -130,7 +133,7 @@ typedef struct { uint64_t seqnum; uint64_t retire; uint8_t len; - u_char cid[20]; + u_char cid[NGX_QUIC_CID_LEN_MAX]; u_char srt[16]; } ngx_quic_new_conn_id_frame_t; -- cgit v1.2.3 From 6f7477f6c66bc54d630cbba16ea2e6cb71ef58e6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 12 May 2020 18:45:44 +0300 Subject: Fixed time comparison. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2ebb72f24..156dfa51f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3262,7 +3262,7 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, do { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (start->first + qc->tp.max_idle_timeout < now) { + if (now - start->first > qc->tp.max_idle_timeout) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "quic retransmission timeout"); return NGX_DECLINED; -- cgit v1.2.3 From 50123aec3a3d64ab041d5e302e4e6796efc68849 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 14 May 2020 16:33:46 +0300 Subject: README: Retry support, protocol error messages implemented. --- README | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README b/README index 56229e0bf..5b6928f1a 100644 --- a/README +++ b/README @@ -43,6 +43,7 @@ Experimental QUIC support for nginx + One endpoint can update keys and its peer responds correctly + 00-RTT data is being received and acted on + Connection is established using TLS Resume Ticket + + A handshake that includes a Retry packet completes successfully + Stream data is being exchanged and ACK'ed + An H3 transaction succeeded + One or both endpoints insert entries into dynamic table and @@ -51,7 +52,6 @@ Experimental QUIC support for nginx Not (yet) supported features: - Version negotiation - - Stateless Retry - ECN, Congestion control and friends as specified in quic-recovery [5] - A connection with the spin bit succeeds and the bit is spinning - Structured Logging @@ -64,9 +64,6 @@ Experimental QUIC support for nginx Since the code is experimental and still under development, a lot of things may not work as expected, for example: - - Protocol error messages are not implemented, in case of error connection - closes silently for peer - - ACK handling is basic: every received ack-eliciting packet is acknowledged, no ack ranges are used -- cgit v1.2.3 From d6887ac1f0e9f0bdac73c91bbaeb17ef3838d6ab Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 14 May 2020 17:22:29 +0300 Subject: Fixed a typo. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 5b6928f1a..dc077f152 100644 --- a/README +++ b/README @@ -41,7 +41,7 @@ Experimental QUIC support for nginx + The handshake completes successfully + One endpoint can update keys and its peer responds correctly - + 00-RTT data is being received and acted on + + 0-RTT data is being received and acted on + Connection is established using TLS Resume Ticket + A handshake that includes a Retry packet completes successfully + Stream data is being exchanged and ACK'ed -- cgit v1.2.3 From 92df22200e3901f8ae27f9125026cb9cb3f3ba56 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 14 May 2020 18:10:53 +0300 Subject: Removed outdated debug. --- src/event/ngx_event_quic_transport.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 5d0182032..71fe830d3 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -117,7 +117,6 @@ ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) ngx_uint_t len; if (pos >= end) { - printf("OOPS >=\n"); return NULL; } @@ -127,7 +126,6 @@ ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) value = *p++ & 0x3f; if ((size_t)(end - p) < (len - 1)) { - printf("LEN TOO BIG: need %ld have %ld\n", len, end - p); return NULL; } -- cgit v1.2.3 From 0041c3f6b72d7e31beca74b0f17d8445e8aaf9e9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 18 May 2020 13:54:35 +0300 Subject: Fixed frame retransmissions. It was possible that retransmit timer was not set after the first retransmission attempt, due to ngx_quic_retransmit() did not set wait time properly, and the condition in retransmit handler was incorrect. --- src/event/ngx_event_quic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 156dfa51f..31a10f98f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3196,7 +3196,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) if (i == 0) { wait = nswait; - } else if (nswait > 0 && nswait < wait) { + } else if (nswait > 0 && (wait == 0 || wait > nswait)) { wait = nswait; } } @@ -3289,6 +3289,8 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, /* move frames group to the end of queue */ ngx_queue_add(&ctx->sent, &range); + wait = qc->tp.max_ack_delay; + } while (q != ngx_queue_sentinel(&ctx->sent)); *waitp = wait; -- cgit v1.2.3 From 04efd355aa02e40e5ec408a34bb31bec86649849 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 18 May 2020 13:54:53 +0300 Subject: Avoid retransmitting of packets with discarded keys. Sections 4.10.1 and 4.10.2 of quic transport describe discarding of initial and handshake keys. Since the keys are discarded, we no longer need to retransmit packets and corresponding queues should be emptied. This patch removes previously added workaround that did not require acknowledgement for initial packets, resulting in avoiding retransmission, which is wrong because a packet could be lost and we have to retransmit it. --- src/event/ngx_event_quic.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 31a10f98f..714660dbb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1602,6 +1602,13 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + /* + * 4.10.1. The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial); + ngx_quic_free_frames(c, &ctx->sent); + return ngx_quic_payload_handler(c, pkt); } @@ -2438,6 +2445,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) int sslerr; ssize_t n; ngx_ssl_conn_t *ssl_conn; + ngx_quic_send_ctx_t *ctx; ngx_quic_crypto_frame_t *f; f = &frame->u.crypto; @@ -2507,6 +2515,13 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { return NGX_ERROR; } + + /* + * 4.10.2 An endpoint MUST discard its handshake keys + * when the TLS handshake is confirmed + */ + ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_handshake); + ngx_quic_free_frames(c, &ctx->sent); } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3069,14 +3084,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) f->last = now; } - if (start->level == ssl_encryption_initial) { - /* ack will not be sent in initial packets due to initial keys being - * discarded when handshake start. - * Thus consider initial packets as non-ack-eliciting - */ - pkt.need_ack = 0; - } - out.len = p - out.data; while (out.len < 4) { -- cgit v1.2.3 From 51e4e31a8dbfd856dbc1875208fa37eeb56ddacc Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 20 May 2020 15:36:24 +0300 Subject: Assorted fixes. Found by Clang Static Analyzer. --- src/event/ngx_event_quic.c | 5 ++--- src/event/ngx_event_quic_protection.c | 3 ++- src/http/v3/ngx_http_v3_request.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 714660dbb..54322b938 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1081,6 +1081,7 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + ngx_memzero(&b, sizeof(ngx_buf_t)); b.start = buf; b.end = buf + sizeof(buf); b.pos = b.last = b.start; @@ -1438,8 +1439,6 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) c->log->action = "retrying quic connection"; - qc = c->quic; - if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic UDP datagram is too small for initial packet"); @@ -3243,7 +3242,6 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, qc = c->quic; now = ngx_current_msec; - wait = 0; if (ngx_queue_empty(&ctx->sent)) { *waitp = 0; @@ -3949,6 +3947,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) if (frame->data) { ngx_free(frame->data); + frame->data = NULL; } ngx_queue_insert_head(&qc->free_frames, &frame->queue); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index e44a13dee..c4620c741 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -914,6 +914,7 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); itag.data = ad.data + ad.len; + itag.len = EVP_GCM_TLS_TAG_LEN; #ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len); @@ -960,7 +961,7 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, *pos = p; expected_pn = *largest_pn + 1; - pn_win = 1 << pn_nbits; + pn_win = 1ULL << pn_nbits; pn_hwin = pn_win / 2; pn_mask = pn_win - 1; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e0ee0c882..c4b313d31 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -618,7 +618,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b = ngx_create_temp_buf(c->pool, len); if (b == NULL) { - NULL; + return NULL; } *b->last++ = NGX_HTTP_V3_FRAME_DATA; -- cgit v1.2.3 From c19b7b6b1af7b667e26d11a02bad80cab040e68b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 22 May 2020 18:16:34 +0300 Subject: Fixed a typo. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index dc077f152..f162d6ba0 100644 --- a/README +++ b/README @@ -193,7 +193,7 @@ Example configuration: We recommend to start with simple console client like ngtcp2 to ensure you've got server configured properly before trying - with real browsers that may be very peaky with certificates, + with real browsers that may be very picky with certificates, for example. + Build nginx with debug support [7] and check your debug log. -- cgit v1.2.3 From 089bc0baf4956734291f9504b9ca43048dd1c591 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 22 May 2020 18:22:00 +0300 Subject: README: pointed out Alt-Svc "ma" parameter useful with curl. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index f162d6ba0..69ec851d1 100644 --- a/README +++ b/README @@ -134,7 +134,7 @@ Example configuration: location / { # required for browsers to direct them into quic port - add_header Alt-Svc $http3=":8443"; + add_header Alt-Svc '$http3=":8443"; ma=86400'; } } } -- cgit v1.2.3 From 7e0314de24fb9f48931aff8023a9cbe597991800 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 21 May 2020 15:38:52 +0300 Subject: Avoided excessive definitions for connection state. There is no need in a separate type for the QUIC connection state. The only state not found in the SSL library is NGX_QUIC_ST_UNAVAILABLE, which is actually a flag used by the ngx_quic_close_quic() function to prevent cleanup of uninitialized connection. --- src/event/ngx_event_quic.c | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 54322b938..f7191f51d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -33,15 +33,6 @@ #define NGX_QUIC_MAX_BUFFERED 65535 -typedef enum { - NGX_QUIC_ST_UNAVAIL, /* connection not ready */ - NGX_QUIC_ST_INITIAL, /* connection just created */ - NGX_QUIC_ST_HANDSHAKE, /* handshake started */ - NGX_QUIC_ST_EARLY_DATA, /* handshake in progress */ - NGX_QUIC_ST_APPLICATION /* handshake complete */ -} ngx_quic_state_t; - - typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; @@ -95,7 +86,7 @@ struct ngx_quic_connection_s { ngx_quic_tp_t tp; ngx_quic_tp_t ctp; - ngx_quic_state_t state; + enum ssl_encryption_level_t state; ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; @@ -127,6 +118,7 @@ struct ngx_quic_connection_s { unsigned draining:1; unsigned key_phase:1; unsigned in_retry:1; + unsigned initialized:1; }; @@ -297,7 +289,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, keys = &c->quic->keys[level]; if (level == ssl_encryption_early_data) { - c->quic->state = NGX_QUIC_ST_EARLY_DATA; + c->quic->state = ssl_encryption_early_data; } return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, @@ -358,7 +350,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } if (level == ssl_encryption_early_data) { - c->quic->state = NGX_QUIC_ST_EARLY_DATA; + c->quic->state = ssl_encryption_early_data; return 1; } @@ -620,7 +612,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->push.cancelable = 1; c->quic = qc; - qc->state = NGX_QUIC_ST_UNAVAIL; + qc->state = ssl_encryption_initial; qc->ssl = ssl; qc->tp = *tp; qc->streams.handler = handler; @@ -657,7 +649,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } - qc->state = NGX_QUIC_ST_INITIAL; + qc->initialized = 1; if (pkt->token.len) { rc = ngx_quic_validate_token(c, pkt); @@ -1066,7 +1058,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } qc->max_streams = qc->tp.initial_max_streams_bidi; - qc->state = NGX_QUIC_ST_HANDSHAKE; + qc->state = ssl_encryption_handshake; return NGX_OK; } @@ -1139,7 +1131,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_close_connection, rc: %i", rc); - if (!c->quic || c->quic->state == NGX_QUIC_ST_UNAVAIL) { + if (!c->quic || !c->quic->initialized) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close connection early error"); @@ -1180,19 +1172,9 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) if (!qc->closing) { - switch (qc->state) { - case NGX_QUIC_ST_INITIAL: - level = ssl_encryption_initial; - break; - - case NGX_QUIC_ST_HANDSHAKE: - level = ssl_encryption_handshake; - break; - - default: /* NGX_QUIC_ST_APPLICATION/EARLY_DATA */ - level = ssl_encryption_application; - break; - } + level = (qc->state == ssl_encryption_early_data) + ? ssl_encryption_application + : qc->state; if (rc == NGX_OK) { @@ -1639,7 +1621,7 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (c->quic->state != NGX_QUIC_ST_EARLY_DATA) { + if (c->quic->state != ssl_encryption_early_data) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unexpected 0-RTT packet"); return NGX_OK; } @@ -2480,7 +2462,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } } else if (n == 1 && !SSL_in_init(ssl_conn)) { - c->quic->state = NGX_QUIC_ST_APPLICATION; + c->quic->state = ssl_encryption_application; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); -- cgit v1.2.3 From 444432047396953152485773398d7c636b76f5eb Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 21 May 2020 15:41:01 +0300 Subject: Fixed retransmission of frames after closing connection. Frames in sent queues are discarded, as no acknowledgment is expected if the connection is closing. --- src/event/ngx_event_quic.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f7191f51d..71e21310c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1165,6 +1165,7 @@ static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { ngx_uint_t i, err; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; enum ssl_encryption_level_t level; @@ -1172,6 +1173,12 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) if (!qc->closing) { + /* drop packets from retransmit queues, no ack is expected */ + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = ngx_quic_get_send_ctx(qc, i); + ngx_quic_free_frames(c, &ctx->sent); + } + level = (qc->state == ssl_encryption_early_data) ? ssl_encryption_application : qc->state; -- cgit v1.2.3 From 76605fa07a0b9579e00952528eb7dc455fa98d01 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 22 May 2020 18:08:02 +0300 Subject: Added more context to CONNECTION CLOSE frames. Now it is possible to specify frame type that caused an error and a human-readable reason phrase. --- src/event/ngx_event_quic.c | 66 +++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 71e21310c..32c9faff3 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -112,6 +112,8 @@ struct ngx_quic_connection_s { uint64_t max_streams; ngx_uint_t error; + ngx_uint_t error_ftype; + const char *error_reason; unsigned send_timer_set:1; unsigned closing:1; @@ -181,7 +183,8 @@ static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, - enum ssl_encryption_level_t level, ngx_uint_t err); + enum ssl_encryption_level_t level, ngx_uint_t err, ngx_uint_t frame_type, + const char *reason); static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, @@ -401,6 +404,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, != NGX_OK) { qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + return NGX_ERROR; } @@ -414,6 +419,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) { qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic maximum packet size is invalid"); return NGX_ERROR; @@ -496,7 +503,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, return 1; } - if (ngx_quic_send_cc(c, level, 0x100 + alert) != NGX_OK) { + if (ngx_quic_send_cc(c, level, 0x100 + alert, 0, "TLS alert") != NGX_OK) { return 0; } @@ -678,6 +685,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return NGX_ERROR; } @@ -904,13 +913,11 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) if (qc->token.len) { if (pkt->token.len != qc->token.len) { - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } return NGX_OK; @@ -926,12 +933,11 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) /* sanity checks */ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { - return NGX_ERROR; + goto bad_token; } ctx = EVP_CIPHER_CTX_new(); @@ -949,14 +955,12 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { EVP_CIPHER_CTX_free(ctx); - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { EVP_CIPHER_CTX_free(ctx); - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } EVP_CIPHER_CTX_free(ctx); @@ -992,8 +996,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) } if (ngx_memcmp(tdec, data, len) != 0) { - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - return NGX_ERROR; + goto bad_token; } ngx_memcpy(&msec, tdec + len, sizeof(msec)); @@ -1003,6 +1006,13 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) } return NGX_OK; + +bad_token: + + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; + qc->error_reason = "invalid_token"; + + return NGX_ERROR; } @@ -1194,7 +1204,9 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic immediate close, drain = %d", qc->draining); - if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR) == NGX_OK) { + if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR, 0, NULL) + == NGX_OK) + { qc->close.log = c->log; qc->close.data = c; @@ -1223,7 +1235,8 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->error); err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; - (void) ngx_quic_send_cc(c, level, err); + (void) ngx_quic_send_cc(c, level, err, qc->error_ftype, + qc->error_reason); } qc->closing = 1; @@ -1769,7 +1782,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) * a packet containing a CONNECTION_CLOSE frame and to identify * packets as belonging to the connection. */ - return ngx_quic_send_cc(c, pkt->level, NGX_QUIC_ERR_NO_ERROR); + return ngx_quic_send_cc(c, pkt->level, NGX_QUIC_ERR_NO_ERROR, 0, + "connection is closing, packet discarded"); } p = pkt->payload.data; @@ -1961,7 +1975,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, - ngx_uint_t err) + ngx_uint_t err, ngx_uint_t frame_type, const char *reason) { ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -1987,8 +2001,16 @@ ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, frame->level = level; frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; frame->u.close.error_code = err; - ngx_sprintf(frame->info, "cc from send_cc err=%ui level=%d", err, - frame->level); + frame->u.close.frame_type = frame_type; + + if (reason) { + frame->u.close.reason.len = ngx_strlen(reason); + frame->u.close.reason.data = (u_char *) reason; + } + + ngx_snprintf(frame->info, sizeof(frame->info) - 1, + "cc from send_cc err=%ui level=%d ft=%ui reason \"%s\"", + err, level, frame_type, reason ? reason : "-"); ngx_quic_queue_frame(c->quic, frame); @@ -2152,6 +2174,8 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, "quic ACK for the packet not in sent queue "); qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; return NGX_ERROR; } -- cgit v1.2.3 From 57696b56e91de2d60675df35587fe7aa4a756529 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 22 May 2020 18:14:35 +0300 Subject: Added sending of extra CONNECTION_CLOSE frames. According to quic-transport draft 28 section 10.3.1: When sending CONNECTION_CLOSE, the goal is to ensure that the peer will process the frame. Generally, this means sending the frame in a packet with the highest level of packet protection to avoid the packet being discarded. After the handshake is confirmed (see Section 4.1.2 of [QUIC-TLS]), an endpoint MUST send any CONNECTION_CLOSE frames in a 1-RTT packet. However, prior to confirming the handshake, it is possible that more advanced packet protection keys are not available to the peer, so another CONNECTION_CLOSE frame MAY be sent in a packet that uses a lower packet protection level. --- src/event/ngx_event_quic.c | 63 ++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 32c9faff3..e2ccf5e43 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1189,24 +1189,32 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_quic_free_frames(c, &ctx->sent); } - level = (qc->state == ssl_encryption_early_data) - ? ssl_encryption_application - : qc->state; + if (rc == NGX_DONE) { - if (rc == NGX_OK) { + /* + * 10.2. Idle Timeout + * + * If the idle timeout is enabled by either peer, a connection is + * silently closed and its state is discarded when it remains idle + */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic closing %s connection", + qc->draining ? "drained" : "idle"); + + } else { /* * 10.3. Immediate Close * - * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) to - * terminate the connection immediately. + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) + * to terminate the connection immediately. */ - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close, drain = %d", qc->draining); - if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_NO_ERROR, 0, NULL) - == NGX_OK) - { + if (rc == NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close, drain = %d", + qc->draining); qc->close.log = c->log; qc->close.data = c; @@ -1214,29 +1222,30 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->close.cancelable = 1; ngx_add_timer(&qc->close, 3 * NGX_QUIC_HARDCODED_PTO); - } - } else if (rc == NGX_DONE) { + err = NGX_QUIC_ERR_NO_ERROR; - /* - * 10.2. Idle Timeout - * - * If the idle timeout is enabled by either peer, a connection is - * silently closed and its state is discarded when it remains idle - */ + } else { + err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic closing %s connection", - qc->draining ? "drained" : "idle"); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to error: %ui %s", + qc->error, + qc->error_reason ? qc->error_reason : ""); + } - } else { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to fatal error: %ui", - qc->error); + level = (qc->state == ssl_encryption_early_data) + ? ssl_encryption_handshake + : qc->state; - err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; (void) ngx_quic_send_cc(c, level, err, qc->error_ftype, qc->error_reason); + + if (level == ssl_encryption_handshake) { + /* for clients that might not have handshake keys */ + (void) ngx_quic_send_cc(c, ssl_encryption_initial, err, + qc->error_ftype, qc->error_reason); + } } qc->closing = 1; -- cgit v1.2.3 From 74564bdd0db974209a4cb2c8b5ff521e174d52e1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 21 May 2020 15:48:39 +0300 Subject: Style. Rephrased error message and removed trailing space. Long comments were shortened/rephrased. --- src/event/ngx_event_quic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e2ccf5e43..4b59258f2 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -67,9 +67,9 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; - uint64_t pnum; /* packet number to send */ - uint64_t largest_ack; /* number received from peer */ - uint64_t largest_pn; /* number received from peer */ + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ ngx_queue_t frames; ngx_queue_t sent; @@ -2180,7 +2180,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic ACK for the packet not in sent queue "); + "quic ACK for the packet not sent"); qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; qc->error_ftype = NGX_QUIC_FT_ACK; -- cgit v1.2.3 From e0eb261b833dd544adc17d6d810a68c5bd8e6f0c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 23 May 2020 14:41:08 +0300 Subject: README: documented Retry, 0-RTT, TLSv1.3 configuration. --- README | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README b/README index 69ec851d1..1c64d364d 100644 --- a/README +++ b/README @@ -108,6 +108,18 @@ Experimental QUIC support for nginx quic_active_migration quic_active_connection_id_limit + To enable address validation: + + quic_retry on; + + To enable 0-RTT: + + ssl_early_data on; + + Make sure that TLS 1.3 is configured which is required for QUIC: + + ssl_protocols TLSv1.3; + Two additional variables are available: $quic and $http3. The value of $quic is "quic" if QUIC connection is used, and empty string otherwise. The value of $http3 is a string -- cgit v1.2.3 From 1c54340e0aa65781f6cd4cc643f704826c47459a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:41:41 +0300 Subject: HTTP/3: prevent array access by negative index for unknown streams. Currently there are no such streams, but the function ngx_http_v3_get_uni_stream() supports them. --- src/http/v3/ngx_http_v3_streams.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 165f21fdc..bd4583998 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -320,7 +320,9 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) sc->read->handler = ngx_http_v3_uni_read_handler; sc->write->handler = ngx_http_v3_dummy_write_handler; - h3c->known_streams[index] = sc; + if (index >= 0) { + h3c->known_streams[index] = sc; + } n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; -- cgit v1.2.3 From d69f678e9c788e2c2424eb6dc2fbacf8366213f6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 14 May 2020 14:49:53 +0300 Subject: HTTP/3: move body parser call out of ngx_http_parse_chunked(). The function ngx_http_parse_chunked() is also called from the proxy module to parse the upstream response. It should always parse HTTP/1 body in this case. --- src/http/ngx_http_parse.c | 6 ------ src/http/ngx_http_request_body.c | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 92bcf12ad..f66593443 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -2185,12 +2185,6 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, sw_trailer_header_almost_done } state; -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - return ngx_http_v3_parse_request_body(r, b, ctx); - } -#endif - state = ctx->state; if (state == sw_chunk_data && ctx->size == 0) { diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index b07d8562f..568f11f02 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -735,7 +735,16 @@ ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) for ( ;; ) { - rc = ngx_http_parse_chunked(r, b, rb->chunked); + switch (r->http_version) { +#if (NGX_HTTP_V3) + case NGX_HTTP_VERSION_30: + rc = ngx_http_v3_parse_request_body(r, b, rb->chunked); + break; +#endif + + default: /* HTTP/1.x */ + rc = ngx_http_parse_chunked(r, b, rb->chunked); + } if (rc == NGX_OK) { @@ -978,7 +987,16 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); - rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); + switch (r->http_version) { +#if (NGX_HTTP_V3) + case NGX_HTTP_VERSION_30: + rc = ngx_http_v3_parse_request_body(r, cl->buf, rb->chunked); + break; +#endif + + default: /* HTTP/1.x */ + rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); + } if (rc == NGX_OK) { -- cgit v1.2.3 From 6abb50658fcdf6ab92a4bc9042cd7dd9bb413850 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:29:10 +0300 Subject: HTTP/3: split header parser in two functions. The first one parses pseudo-headers and is analagous to the request line parser in HTTP/1. The second one parses regular headers and is analogous to the header parser in HTTP/1. Additionally, error handling of client passing malformed uri is now fixed. --- src/http/ngx_http_request.c | 2 +- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_request.c | 142 ++++++++++++++++++++------------------ 3 files changed, 77 insertions(+), 68 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index b0cc9a543..e77c4bc35 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1164,7 +1164,7 @@ ngx_http_process_request_line(ngx_event_t *rev) switch (r->http_version) { #if (NGX_HTTP_V3) case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_header(r, r->header_in); + rc = ngx_http_v3_parse_request(r, r->header_in); break; #endif diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 3f35e985e..f80b74f3a 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -67,6 +67,7 @@ typedef struct { } ngx_http_v3_header_t; +ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_http_chunked_t *ctx); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c4b313d31..2bb627489 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -38,21 +38,14 @@ struct { ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) { - size_t n; + size_t len; u_char *p; - ngx_int_t rc; + ngx_int_t rc, n; ngx_str_t *name, *value; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; - enum { - sw_start = 0, - sw_prev, - sw_headers, - sw_last, - sw_done - }; c = r->connection; st = r->h3_parse; @@ -68,23 +61,6 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) r->h3_parse = st; } - switch (r->state) { - - case sw_prev: - r->state = sw_headers; - return NGX_OK; - - case sw_done: - goto done; - - case sw_last: - r->state = sw_done; - return NGX_OK; - - default: - break; - } - while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos++); @@ -99,68 +75,100 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) name = &st->header_rep.header.name; value = &st->header_rep.header.value; - if (r->state == sw_start) { + n = ngx_http_v3_process_pseudo_header(r, name, value); - if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) { - if (rc == NGX_OK) { - continue; - } + if (n == NGX_ERROR) { + goto failed; + } - r->state = sw_done; + if (n == NGX_OK && rc == NGX_OK) { + continue; + } - } else if (rc == NGX_OK) { - r->state = sw_prev; + len = (r->method_end - r->method_start) + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3") - 1; - } else { - r->state = sw_last; - } + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + goto failed; + } - n = (r->method_end - r->method_start) + 1 - + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3") - 1; + r->request_start = p; + + p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + + r->request_end = p; + + return NGX_OK; + } + + return NGX_AGAIN; + +failed: + + return NGX_HTTP_PARSE_INVALID_REQUEST; +} - p = ngx_pnalloc(c->pool, n); - if (p == NULL) { - goto failed; - } - r->request_start = p; +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_str_t *name, *value; + ngx_connection_t *c; + ngx_http_v3_parse_headers_t *st; - p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); - *p++ = ' '; - p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); - *p++ = ' '; - p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + c = r->connection; + st = r->h3_parse; - r->request_end = p; + if (st->state == 0) { + if (r->header_name_start == NULL) { + name = &st->header_rep.header.name; - } else if (rc == NGX_DONE) { - r->state = sw_done; + if (name->len && name->data[0] != ':') { + goto done; + } } - r->header_name_start = name->data; - r->header_name_end = name->data + name->len; - r->header_start = value->data; - r->header_end = value->data + value->len; - r->header_hash = ngx_hash_key(name->data, name->len); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header done"); - /* XXX r->lowcase_index = i; */ + return NGX_HTTP_PARSE_HEADER_DONE; + } - return NGX_OK; + while (b->pos < b->last) { + rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + + if (rc == NGX_ERROR) { + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (rc != NGX_AGAIN) { + goto done; + } } return NGX_AGAIN; -failed: +done: - return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST - : NGX_HTTP_PARSE_INVALID_HEADER; + name = &st->header_rep.header.name; + value = &st->header_rep.header.value; -done: + r->header_name_start = name->data; + r->header_name_end = name->data + name->len; + r->header_start = value->data; + r->header_end = value->data + value->len; + r->header_hash = ngx_hash_key(name->data, name->len); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + /* XXX r->lowcase_index = i; */ - return NGX_HTTP_PARSE_HEADER_DONE; + return NGX_OK; } -- cgit v1.2.3 From d25937c2b596013874fcb049f10b28270ee49e29 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:34:00 +0300 Subject: HTTP/3: restricted symbols in header names. As per HTTP/3 draft 27, a request or response containing uppercase header field names MUST be treated as malformed. Also, existing rules applied when parsing HTTP/1 header names are also applied to HTTP/3 header names: - null character is not allowed - underscore character may or may not be treated as invalid depending on the value of "underscores_in_headers" - all non-alphanumeric characters with the exception of '-' are treated as invalid Also, the r->locase_header field is now filled while parsing an HTTP/3 header. Error logging for invalid headers is fixed as well. --- src/http/ngx_http_request.c | 10 ++++---- src/http/v3/ngx_http_v3.h | 3 ++- src/http/v3/ngx_http_v3_request.c | 49 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index e77c4bc35..e3d217f79 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1511,7 +1511,8 @@ ngx_http_process_request_headers(ngx_event_t *rev) switch (r->http_version) { #if (NGX_HTTP_V3) case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_header(r, r->header_in); + rc = ngx_http_v3_parse_header(r, r->header_in, + cscf->underscores_in_headers); break; #endif @@ -1530,9 +1531,10 @@ ngx_http_process_request_headers(ngx_event_t *rev) /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid header line: \"%*s\"", - r->header_end - r->header_name_start, - r->header_name_start); + "client sent invalid header line: \"%*s: %*s\"", + r->header_name_end - r->header_name_start, + r->header_name_start, + r->header_end - r->header_start, r->header_start); continue; } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index f80b74f3a..29cc06ee9 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -68,7 +68,8 @@ typedef struct { ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b); -ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, + ngx_uint_t allow_underscores); ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_http_chunked_t *ctx); ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2bb627489..59b8ce5b8 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -116,16 +116,23 @@ failed: ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, + ngx_uint_t allow_underscores) { + u_char ch; ngx_int_t rc; ngx_str_t *name, *value; + ngx_uint_t hash, i, n; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; c = r->connection; st = r->h3_parse; + if (st->header_rep.state == 0) { + r->invalid_header = 0; + } + if (st->state == 0) { if (r->header_name_start == NULL) { name = &st->header_rep.header.name; @@ -164,9 +171,45 @@ done: r->header_name_end = name->data + name->len; r->header_start = value->data; r->header_end = value->data + value->len; - r->header_hash = ngx_hash_key(name->data, name->len); - /* XXX r->lowcase_index = i; */ + hash = 0; + i = 0; + + for (n = 0; n < name->len; n++) { + ch = name->data[n]; + + if (ch >= 'A' && ch <= 'Z') { + /* + * A request or response containing uppercase + * header field names MUST be treated as malformed + */ + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch == '\0') { + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch == '_' && !allow_underscores) { + r->invalid_header = 1; + continue; + } + + if ((ch < 'a' || ch > 'z') + && (ch < '0' || ch > '9') + && ch != '-' && ch != '_') + { + r->invalid_header = 1; + continue; + } + + hash = ngx_hash(hash, ch); + r->lowcase_header[i++] = ch; + i &= (NGX_HTTP_LC_HEADER_LEN - 1); + } + + r->header_hash = hash; + r->lowcase_index = i; return NGX_OK; } -- cgit v1.2.3 From d6b6b6dfc57c916dc7990504fffe71248421456d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:47:37 +0300 Subject: Fixed $request_length for HTTP/3. New field r->parse_start is introduced to substitute r->request_start and r->header_name_start for request length accounting. These fields only work for this purpose in HTTP/1 because HTTP/1 request line and header line start with these values. Also, error logging is now fixed to output the right part of the request. --- src/http/ngx_http_parse.c | 2 ++ src/http/ngx_http_request.c | 27 ++++++++++++++------------- src/http/ngx_http_request.h | 1 + src/http/v3/ngx_http_v3_request.c | 2 ++ 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index f66593443..b59a6ce4c 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -143,6 +143,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) /* HTTP methods: GET, HEAD, POST */ case sw_start: + r->parse_start = p; r->request_start = p; r->method_start = p; @@ -883,6 +884,7 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, /* first char */ case sw_start: + r->parse_start = p; r->header_name_start = p; r->invalid_header = 0; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index e3d217f79..baef5f444 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1178,7 +1178,7 @@ ngx_http_process_request_line(ngx_event_t *rev) r->request_line.len = r->request_end - r->request_start; r->request_line.data = r->request_start; - r->request_length = r->header_in->pos - r->request_start; /* XXX */ + r->request_length = r->header_in->pos - r->parse_start; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); @@ -1293,8 +1293,8 @@ ngx_http_process_request_line(ngx_event_t *rev) } if (rv == NGX_DECLINED) { - r->request_line.len = r->header_in->end - r->request_start; - r->request_line.data = r->request_start; + r->request_line.len = r->header_in->end - r->parse_start; + r->request_line.data = r->parse_start; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); @@ -1470,7 +1470,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) } if (rv == NGX_DECLINED) { - p = r->header_name_start; + p = r->parse_start; r->lingering_close = 1; @@ -1490,7 +1490,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long header line: \"%*s...\"", - len, r->header_name_start); + len, r->parse_start); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); @@ -1523,8 +1523,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) if (rc == NGX_OK) { - /* XXX */ - r->request_length += r->header_in->pos - r->header_name_start; + r->request_length += r->header_in->pos - r->parse_start; if (r->invalid_header && cscf->ignore_invalid_headers) { @@ -1596,7 +1595,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header done"); - r->request_length += r->header_in->pos - r->header_name_start; /* XXX */ + r->request_length += r->header_in->pos - r->parse_start; r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; @@ -1711,7 +1710,7 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, return NGX_OK; } - old = request_line ? r->request_start : r->header_name_start; /* XXX */ + old = r->parse_start; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); @@ -1783,6 +1782,8 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, b->pos = new + (r->header_in->pos - old); b->last = new + (r->header_in->pos - old); + r->parse_start = new; + if (request_line) { r->request_start = new; @@ -3892,15 +3893,15 @@ ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr, len -= p - buf; buf = p; - if (r->request_line.data == NULL && r->request_start) { - for (p = r->request_start; p < r->header_in->last; p++) { + if (r->request_line.data == NULL && r->parse_start) { + for (p = r->parse_start; p < r->header_in->last; p++) { if (*p == CR || *p == LF) { break; } } - r->request_line.len = p - r->request_start; - r->request_line.data = r->request_start; + r->request_line.len = p - r->parse_start; + r->request_line.data = r->parse_start; } if (r->request_line.len) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 772d53b2d..1994000a4 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -579,6 +579,7 @@ struct ngx_http_request_s { * via ngx_http_ephemeral_t */ + u_char *parse_start; u_char *uri_start; u_char *uri_end; u_char *uri_ext; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 59b8ce5b8..432bc8711 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -59,6 +59,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) } r->h3_parse = st; + r->parse_start = b->pos; } while (b->pos < b->last) { @@ -130,6 +131,7 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, st = r->h3_parse; if (st->header_rep.state == 0) { + r->parse_start = b->pos; r->invalid_header = 0; } -- cgit v1.2.3 From 94764fda6efa48fc18b2fc0a2cd82d451e947094 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 16:20:33 +0300 Subject: Fixed client buffer reallocation for HTTP/3. Preserving pointers within the client buffer is not needed for HTTP/3 because all data is either allocated from pool or static. Unlike with HTTP/1, data typically cannot be referenced directly within the client buffer. Trying to preserve NULLs or external pointers lead to broken pointers. Also, reverted changes in ngx_http_alloc_large_header_buffer() not relevant for HTTP/3 to minimize diff to mainstream. --- src/http/ngx_http_request.c | 48 ++++++++++++++++----------------------- src/http/v3/ngx_http_v3_request.c | 1 + 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index baef5f444..1b3573598 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1784,6 +1784,12 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->parse_start = new; + r->header_in = b; + + if (r->http_version > NGX_HTTP_VERSION_11) { + return NGX_OK; + } + if (request_line) { r->request_start = new; @@ -1791,63 +1797,47 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->request_end = new + (r->request_end - old); } - if (r->method_start >= old && r->method_start < r->header_in->pos) { - r->method_start = new + (r->method_start - old); - r->method_end = new + (r->method_end - old); - } + r->method_end = new + (r->method_end - old); - if (r->uri_start >= old && r->uri_start < r->header_in->pos) { - r->uri_start = new + (r->uri_start - old); - r->uri_end = new + (r->uri_end - old); - } + r->uri_start = new + (r->uri_start - old); + r->uri_end = new + (r->uri_end - old); - if (r->schema_start >= old && r->schema_start < r->header_in->pos) { + if (r->schema_start) { r->schema_start = new + (r->schema_start - old); r->schema_end = new + (r->schema_end - old); } - if (r->host_start >= old && r->host_start < r->header_in->pos) { + if (r->host_start) { r->host_start = new + (r->host_start - old); if (r->host_end) { r->host_end = new + (r->host_end - old); } } - if (r->port_start >= old && r->port_start < r->header_in->pos) { + if (r->port_start) { r->port_start = new + (r->port_start - old); r->port_end = new + (r->port_end - old); } - if (r->uri_ext >= old && r->uri_ext < r->header_in->pos) { + if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } - if (r->args_start >= old && r->args_start < r->header_in->pos) { + if (r->args_start) { r->args_start = new + (r->args_start - old); } - if (r->http_protocol.data >= old - && r->http_protocol.data < r->header_in->pos) - { + if (r->http_protocol.data) { r->http_protocol.data = new + (r->http_protocol.data - old); } } else { - if (r->header_name_start >= old - && r->header_name_start < r->header_in->pos) - { - r->header_name_start = new; - r->header_name_end = new + (r->header_name_end - old); - } - - if (r->header_start >= old && r->header_start < r->header_in->pos) { - r->header_start = new + (r->header_start - old); - r->header_end = new + (r->header_end - old); - } + r->header_name_start = new; + r->header_name_end = new + (r->header_name_end - old); + r->header_start = new + (r->header_start - old); + r->header_end = new + (r->header_end - old); } - r->header_in = b; - return NGX_OK; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 432bc8711..7fb297728 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -60,6 +60,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) r->h3_parse = st; r->parse_start = b->pos; + r->state = 1; } while (b->pos < b->last) { -- cgit v1.2.3 From 097d8a87b3f8c0d4f12cc468d35a590ae18e81ed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 14 May 2020 16:02:32 +0300 Subject: HTTP/3: reallocate strings inserted into the dynamic table. They should always be allocated from the main QUIC connection pool. --- src/http/v3/ngx_http_v3_tables.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index f58f190f1..ecebe943c 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -149,12 +149,15 @@ ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) { ngx_array_t *dt; + ngx_connection_t *pc; ngx_http_v3_header_t *ref, *h; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 ref insert %s[$ui] \"%V\"", dynamic ? "dynamic" : "static", index, value); + pc = c->qs->parent; + ref = ngx_http_v3_lookup_table(c, dynamic, index); if (ref == NULL) { return NGX_ERROR; @@ -171,7 +174,14 @@ ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, } h->name = ref->name; - h->value = *value; + + h->value.data = ngx_pstrdup(pc->pool, value); + if (h->value.data == NULL) { + h->value.len = 0; + return NGX_ERROR; + } + + h->value.len = value->len; if (ngx_http_v3_new_header(c) != NGX_OK) { return NGX_ERROR; @@ -186,11 +196,14 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) { ngx_array_t *dt; + ngx_connection_t *pc; ngx_http_v3_header_t *h; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 insert \"%V\":\"%V\"", name, value); + pc = c->qs->parent; + dt = ngx_http_v3_get_dynamic_table(c); if (dt == NULL) { return NGX_ERROR; @@ -201,8 +214,22 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, return NGX_ERROR; } - h->name = *name; - h->value = *value; + h->name.data = ngx_pstrdup(pc->pool, name); + if (h->name.data == NULL) { + h->name.len = 0; + h->value.len = 0; + return NGX_ERROR; + } + + h->name.len = name->len; + + h->value.data = ngx_pstrdup(pc->pool, value); + if (h->value.data == NULL) { + h->value.len = 0; + return NGX_ERROR; + } + + h->value.len = value->len; if (ngx_http_v3_new_header(c) != NGX_OK) { return NGX_ERROR; -- cgit v1.2.3 From 2360f58a9b4515cfb5aa2d454892da70274ae183 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 25 May 2020 18:37:43 +0300 Subject: Updated README with "Contributing" section and draft details. --- README | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README b/README index 1c64d364d..90bf787dc 100644 --- a/README +++ b/README @@ -6,7 +6,8 @@ Experimental QUIC support for nginx 3. Configuration 4. Clients 5. Troubleshooting -6. Links +6. Contributing +7. Links 1. Introduction @@ -36,6 +37,10 @@ Experimental QUIC support for nginx Currently we support IETF-QUIC draft 27 Earlier drafts are NOT supported as they have incompatible wire format; + Newer drafts development (draft-28 at the time of writing) is in progress. + You may look at src/event/ngx_event_quic.h for alternative values of the + NGX_QUIC_DRAFT_VERSION macro used to select IETF draft version number. + nginx should be able to respond to simple HTTP/3 requests over QUIC and it should be possible to upload and download big files without errors. @@ -221,7 +226,12 @@ Example configuration: #define NGX_QUIC_DEBUG_FRAMES_ALLOC #define NGX_QUIC_DEBUG_CRYPTO -6. Links +6. Contributing + + If you are willing to contribute, please refer to + http://nginx.org/en/docs/contributing_changes.html + +7. Links [1] https://tools.ietf.org/html/draft-ietf-quic-transport-27 [2] https://tools.ietf.org/html/draft-ietf-quic-http-27 -- cgit v1.2.3 From 5352a71779b3df14a40729376b4b165ae25b33a7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 26 May 2020 20:41:43 +0300 Subject: README: update after merging 1.19.0. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 90bf787dc..1dc6601b4 100644 --- a/README +++ b/README @@ -15,7 +15,7 @@ Experimental QUIC support for nginx The code is developed in a separate "quic" branch available at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.17.10. We are planning to merge new nginx + on nginx mainline 1.19.x. We are planning to merge new nginx releases into this branch regularly. The project code base is under the same BSD license as nginx. -- cgit v1.2.3 From a4a641fa6ce6197f23ed632cd447bb7f975c0b1d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 12:50:20 +0300 Subject: Fixed return codes in ngx_quic_add_handshake_data() callback. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4b59258f2..281d6a8cf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -406,7 +406,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; qc->error_reason = "failed to process transport parameters"; - return NGX_ERROR; + return 0; } if (qc->ctp.max_idle_timeout > 0 @@ -423,7 +423,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic maximum packet size is invalid"); - return NGX_ERROR; + return 0; } qc->client_tp_done = 1; -- cgit v1.2.3 From 49d7ae444f73d8d47029d6c70b01d01d5637fb34 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 12:55:39 +0300 Subject: Rejected forbidden transport parameters with TRANSPORT_PARAMETER_ERROR. --- src/event/ngx_event_quic_transport.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 71fe830d3..ed140e4ac 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1355,14 +1355,6 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, { uint64_t varint; - switch (id) { - case NGX_QUIC_TP_ORIGINAL_CONNECTION_ID: - case NGX_QUIC_TP_STATELESS_RESET_TOKEN: - case NGX_QUIC_TP_PREFERRED_ADDRESS: - /* TODO: implement */ - return NGX_DECLINED; - } - switch (id) { case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: @@ -1464,6 +1456,16 @@ 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_CONNECTION_ID: + case NGX_QUIC_TP_PREFERRED_ADDRESS: + case NGX_QUIC_TP_STATELESS_RESET_TOKEN: + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id 0x%xi", id); + return NGX_ERROR; + } + p = ngx_quic_parse_int(p, end, &len); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, -- cgit v1.2.3 From 76bbe70406fe9541c6a6b07e68c833cfb15a46aa Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 12:56:08 +0300 Subject: Renamed max_packet_size to max_udp_payload_size, from draft-28. No functional changes. --- src/event/ngx_event_quic.c | 41 ++++++++++++++++++----------------- src/event/ngx_event_quic.h | 4 ++-- src/event/ngx_event_quic_protection.c | 2 +- src/event/ngx_event_quic_transport.c | 11 +++++----- src/event/ngx_event_quic_transport.h | 2 +- src/http/v3/ngx_http_v3_module.c | 29 +++++++++++++------------ 6 files changed, 46 insertions(+), 43 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 281d6a8cf..c0c4c02fd 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -415,8 +415,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; } - if (qc->ctp.max_packet_size < NGX_QUIC_MIN_INITIAL_SIZE - || qc->ctp.max_packet_size > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) + if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; qc->error_reason = "invalid maximum packet size"; @@ -434,7 +434,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, * we need to fit at least 1 frame into a packet, thus account head/tail; * 17 = 1 + 8x2 is max header for CRYPTO frame, with 1 byte for frame type */ - limit = qc->ctp.max_packet_size - NGX_QUIC_MAX_LONG_HEADER - 17 + limit = qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_LONG_HEADER - 17 - EVP_GCM_TLS_TAG_LEN; fs = &qc->crypto[level]; @@ -555,7 +555,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -625,14 +625,15 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->streams.handler = handler; ctp = &qc->ctp; - ctp->max_packet_size = NGX_QUIC_DEFAULT_MAX_PACKET_SIZE; + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; qc->streams.recv_max_data = qc->tp.initial_max_data; - qc->congestion.window = ngx_min(10 * qc->tp.max_packet_size, - ngx_max(2 * qc->tp.max_packet_size, 14720)); + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; qc->congestion.recovery_start = ngx_current_msec; @@ -1081,7 +1082,7 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_buf_t b; ngx_connection_t *c; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ngx_memzero(&b, sizeof(ngx_buf_t)); b.start = buf; @@ -1446,7 +1447,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "retrying quic connection"; @@ -1534,7 +1535,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_ssl_conn_t *ssl_conn; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "processing initial quic packet"; @@ -1573,7 +1574,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "processing handshake quic packet"; @@ -1630,7 +1631,7 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "processing early data quic packet"; @@ -1710,7 +1711,7 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_secrets_t *keys, *next, tmp; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "processing application data quic packet"; @@ -2963,7 +2964,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) n = ngx_quic_create_frame(NULL, f); - if (len && hlen + len + n > qc->ctp.max_packet_size) { + if (len && hlen + len + n > qc->ctp.max_udp_payload_size) { break; } @@ -3062,8 +3063,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static ngx_str_t initial_token = ngx_null_string; - static u_char src[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; - static u_char dst[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE]; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_send_frames"); @@ -3632,7 +3633,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) * we need to fit at least 1 frame into a packet, thus account head/tail; * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type */ - limit = qc->ctp.max_packet_size - NGX_QUIC_MAX_SHORT_HEADER - 25 + limit = qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_SHORT_HEADER - 25 - EVP_GCM_TLS_TAG_LEN; len = size; @@ -3912,7 +3913,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) cg->window, cg->ssthresh, cg->in_flight); } else { - cg->window += qc->tp.max_packet_size * n / cg->window; + cg->window += qc->tp.max_udp_payload_size * n / cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion avoidance win:%uz, ss:%uz, if:%uz", @@ -3948,8 +3949,8 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_msec_t sent) cg->recovery_start = ngx_current_msec; cg->window /= 2; - if (cg->window < qc->tp.max_packet_size * 2) { - cg->window = qc->tp.max_packet_size * 2; + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; } cg->ssthresh = cg->window; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 862b339eb..be125705d 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -19,7 +19,7 @@ #define NGX_QUIC_MAX_LONG_HEADER 56 /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ -#define NGX_QUIC_DEFAULT_MAX_PACKET_SIZE 65527 +#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 @@ -46,7 +46,7 @@ typedef struct { ngx_msec_t max_idle_timeout; ngx_msec_t max_ack_delay; - size_t max_packet_size; + size_t max_udp_payload_size; size_t initial_max_data; size_t initial_max_stream_data_bidi_local; size_t initial_max_stream_data_bidi_remote; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index c4620c741..7162c7703 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1115,7 +1115,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; - if (NGX_QUIC_DEFAULT_MAX_PACKET_SIZE - ad.len < pkt->payload.len) { + if (NGX_QUIC_MAX_UDP_PAYLOAD_SIZE - ad.len < pkt->payload.len) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ed140e4ac..ef9068432 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1366,7 +1366,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, return NGX_OK; case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: - case NGX_QUIC_TP_MAX_PACKET_SIZE: + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: case NGX_QUIC_TP_INITIAL_MAX_DATA: case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: @@ -1393,8 +1393,8 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, dst->max_idle_timeout = varint; break; - case NGX_QUIC_TP_MAX_PACKET_SIZE: - dst->max_packet_size = varint; + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + dst->max_udp_payload_size = varint; break; case NGX_QUIC_TP_INITIAL_MAX_DATA: @@ -1509,8 +1509,9 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout: %ui", tp->max_idle_timeout); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_packet_size: %ui", - tp->max_packet_size); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_udp_payload_size: %ui", + tp->max_udp_payload_size); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data: %ui", tp->initial_max_data); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 35db6ccf3..f29038ec4 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -99,7 +99,7 @@ #define NGX_QUIC_TP_ORIGINAL_CONNECTION_ID 0x00 #define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 #define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02 -#define NGX_QUIC_TP_MAX_PACKET_SIZE 0x03 +#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 #define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 #define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 #define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index efad51c71..434b7e73d 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -11,14 +11,14 @@ static char *ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data); -static char *ngx_http_v3_max_packet_size(ngx_conf_t *cf, void *post, +static char *ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data); static ngx_conf_post_t ngx_http_v3_max_ack_delay_post = { ngx_http_v3_max_ack_delay }; -static ngx_conf_post_t ngx_http_v3_max_packet_size_post = - { ngx_http_v3_max_packet_size }; +static ngx_conf_post_t ngx_http_v3_max_udp_payload_size_post = + { ngx_http_v3_max_udp_payload_size }; static ngx_conf_num_bounds_t ngx_http_v3_ack_delay_exponent_bounds = { ngx_conf_check_num_bounds, 0, 20 }; static ngx_conf_num_bounds_t ngx_http_v3_active_connection_id_limit_bounds = @@ -41,12 +41,12 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay), &ngx_http_v3_max_ack_delay_post }, - { ngx_string("quic_max_packet_size"), + { ngx_string("quic_max_udp_payload_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.max_packet_size), - &ngx_http_v3_max_packet_size_post }, + offsetof(ngx_http_v3_srv_conf_t, quic.max_udp_payload_size), + &ngx_http_v3_max_udp_payload_size_post }, { ngx_string("quic_initial_max_data"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, @@ -253,7 +253,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC; v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC; - v3cf->quic.max_packet_size = NGX_CONF_UNSET_SIZE; + v3cf->quic.max_udp_payload_size = NGX_CONF_UNSET_SIZE; v3cf->quic.initial_max_data = NGX_CONF_UNSET_SIZE; v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; @@ -283,9 +283,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->quic.max_ack_delay, NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - ngx_conf_merge_size_value(conf->quic.max_packet_size, - prev->quic.max_packet_size, - NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); + ngx_conf_merge_size_value(conf->quic.max_udp_payload_size, + prev->quic.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); ngx_conf_merge_size_value(conf->quic.initial_max_data, prev->quic.initial_max_data, @@ -349,17 +349,18 @@ ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data) static char * -ngx_http_v3_max_packet_size(ngx_conf_t *cf, void *post, void *data) +ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_DEFAULT_MAX_PACKET_SIZE) + || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_packet_size\" must be between %d and %d", + "\"quic_max_udp_payload_size\" must be between " + "%d and %d", NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_DEFAULT_MAX_PACKET_SIZE); + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); return NGX_CONF_ERROR; } -- cgit v1.2.3 From 25f5ab5e2d609a355bb2cd5e475cd1186e921d0c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 13:05:57 +0300 Subject: Introduced macros for building length-value transport parameters. --- src/event/ngx_event_quic_transport.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ef9068432..2beb5d0fe 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1613,6 +1613,18 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_build_int(&p, value); \ } while (0) +#define ngx_quic_tp_strlen(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value.len) \ + + value.len + +#define ngx_quic_tp_str(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, value.len); \ + p = ngx_cpymem(p, value.data, value.len); \ + } while (0) + p = pos; len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, @@ -1639,9 +1651,8 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) tp->max_idle_timeout); if (tp->retry) { - len += ngx_quic_varint_len(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID); - len += ngx_quic_varint_len(tp->original_connection_id.len); - len += tp->original_connection_id.len; + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID, + tp->original_connection_id); } if (pos == NULL) { @@ -1673,10 +1684,8 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) tp->max_idle_timeout); if (tp->retry) { - ngx_quic_build_int(&p, NGX_QUIC_TP_ORIGINAL_CONNECTION_ID); - ngx_quic_build_int(&p, tp->original_connection_id.len); - p = ngx_cpymem(p, tp->original_connection_id.data, - tp->original_connection_id.len); + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID, + tp->original_connection_id); } return p - pos; -- cgit v1.2.3 From 7d41fd85b8d6679335798740ce9bbae7586ae649 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 15:06:33 +0300 Subject: QUIC draft-28 transport parameters support. Draft-27 and draft-28 support can now be enabled interchangeably, it's based on the compile-time macro NGX_QUIC_DRAFT_VERSION. --- src/event/ngx_event_quic.c | 17 ++++++++++++- src/event/ngx_event_quic.h | 5 +++- src/event/ngx_event_quic_transport.c | 46 ++++++++++++++++++++++++++++++++---- src/event/ngx_event_quic_transport.h | 4 +++- src/http/v3/ngx_http_v3_module.c | 4 +++- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c0c4c02fd..37b3e4c6f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -426,6 +426,17 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } +#if (NGX_QUIC_DRAFT_VERSION >= 28) + if (qc->scid.len != qc->ctp.initial_scid.len + || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, + qc->scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id mismatch"); + return 0; + } +#endif + qc->client_tp_done = 1; } } @@ -641,6 +652,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } + qc->tp.original_dcid = c->quic->odcid; + qc->tp.initial_scid = c->quic->dcid; + qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { @@ -782,7 +796,7 @@ ngx_quic_retry(ngx_connection_t *c) } c->quic->token = token; - c->quic->tp.original_connection_id = c->quic->odcid; + c->quic->tp.retry_scid = c->quic->dcid; c->quic->in_retry = 1; return NGX_OK; @@ -1483,6 +1497,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } qc = c->quic; + qc->tp.initial_scid = c->quic->dcid; keys = &c->quic->keys[ssl_encryption_initial]; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index be125705d..d9d287857 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -12,6 +12,7 @@ #include +/* Supported drafts: 27, 28 */ #define NGX_QUIC_DRAFT_VERSION 27 #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) @@ -56,7 +57,9 @@ typedef struct { ngx_uint_t ack_delay_exponent; ngx_uint_t disable_active_migration; ngx_uint_t active_connection_id_limit; - ngx_str_t original_connection_id; + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; ngx_flag_t retry; u_char token_key[32]; /* AES 256 */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 2beb5d0fe..00df78a59 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1354,6 +1354,10 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ngx_quic_tp_t *dst) { uint64_t varint; + ngx_str_t str; + + varint = 0; + ngx_str_null(&str); switch (id) { @@ -1383,6 +1387,12 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, } break; + case NGX_QUIC_TP_INITIAL_SCID: + + str.len = end - p; + p = ngx_quic_read_bytes(p, end, str.len, &str.data); + break; + default: return NGX_DECLINED; } @@ -1433,6 +1443,10 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, dst->active_connection_id_limit = varint; break; + case NGX_QUIC_TP_INITIAL_SCID: + dst->initial_scid = str; + break; + default: return NGX_ERROR; } @@ -1457,8 +1471,9 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, } switch (id) { - case NGX_QUIC_TP_ORIGINAL_CONNECTION_ID: + case NGX_QUIC_TP_ORIGINAL_DCID: case NGX_QUIC_TP_PREFERRED_ADDRESS: + case NGX_QUIC_TP_RETRY_SCID: case NGX_QUIC_TP_STATELESS_RESET_TOKEN: ngx_log_error(NGX_LOG_INFO, log, 0, "quic client sent forbidden transport param" @@ -1547,6 +1562,11 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, "quic tp active_connection_id_limit: %ui", tp->active_connection_id_limit); +#if (NGX_QUIC_DRAFT_VERSION >= 28) + ngx_quic_hexdump(log, "quic tp initial_source_connection_id:", + tp->initial_scid.data, tp->initial_scid.len); +#endif + return NGX_OK; } @@ -1650,9 +1670,17 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); +#if (NGX_QUIC_DRAFT_VERSION >= 28) + 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); +#endif + if (tp->retry) { - len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID, - tp->original_connection_id); +#if (NGX_QUIC_DRAFT_VERSION >= 28) + len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); +#else + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); +#endif } if (pos == NULL) { @@ -1683,9 +1711,17 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); +#if (NGX_QUIC_DRAFT_VERSION >= 28) + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); +#endif + if (tp->retry) { - ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_CONNECTION_ID, - tp->original_connection_id); +#if (NGX_QUIC_DRAFT_VERSION >= 28) + ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); +#else + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); +#endif } return p - pos; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index f29038ec4..322fc78cf 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -96,7 +96,7 @@ #define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR /* Transport parameters */ -#define NGX_QUIC_TP_ORIGINAL_CONNECTION_ID 0x00 +#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 #define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 #define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02 #define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 @@ -111,6 +111,8 @@ #define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C #define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D #define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_TP_INITIAL_SCID 0x0F +#define NGX_QUIC_TP_RETRY_SCID 0x10 #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 434b7e73d..550b706da 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -245,7 +245,9 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): - * v3cf->quic.original_connection_id = 0; + * v3cf->quic.original_dcid = { 0, NULL }; + * v3cf->quic.initial_scid = { 0, NULL }; + * v3cf->quic.retry_scid = { 0, NULL }; * v3cf->quic.stateless_reset_token = { 0 } * conf->quic.preferred_address = NULL */ -- cgit v1.2.3 From f45ec755a565589ce26398f839b6f2548c07f6ab Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 29 May 2020 15:07:46 +0300 Subject: Made NGX_QUIC_DRAFT_VERSION tunable from configure parameters. Now it can be switched using --with-cc-opt='-DNGX_QUIC_DRAFT_VERSION=28'. --- src/event/ngx_event_quic.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index d9d287857..17ea64700 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -13,7 +13,9 @@ /* Supported drafts: 27, 28 */ +#ifndef NGX_QUIC_DRAFT_VERSION #define NGX_QUIC_DRAFT_VERSION 27 +#endif #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -- cgit v1.2.3 From 101113a98f92b023c7d6586e45767ba3b886abd7 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 29 May 2020 13:29:24 +0300 Subject: Added propagation of the "wildcard" flag to c->listening. The flags was originally added by 8f038068f4bc, and is propagated correctly in the stream module. With QUIC introduction, http module now uses datagram sockets as well, thus the fix. --- src/http/ngx_http.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 10f88fabb..1b5e387db 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1795,6 +1795,8 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->reuseport = addr->opt.reuseport; #endif + ls->wildcard = addr->opt.wildcard; + return ls; } -- cgit v1.2.3 From 22297afd7924d00440105bc440aa4f67fde380fe Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 29 May 2020 12:42:23 +0300 Subject: Require ":authority" or "Host" in HTTP/3 and HTTP/2 requests. Also, if both are present, require that they have the same value. These requirements are specified in HTTP/3 draft 28. Current implementation of HTTP/2 treats ":authority" and "Host" interchangeably. New checks only make sure at least one of these values is present in the request. A similar check existed earlier and was limited only to HTTP/1.1 in 38c0898b6df7. --- src/http/ngx_http_request.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 3e6fce676..23b28c243 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2065,6 +2065,31 @@ ngx_http_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } + if (r->http_version >= NGX_HTTP_VERSION_20) { + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent HTTP request without " + "\":authority\" or \"Host\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent HTTP request with different " + "values of \":authority\" and \"Host\" headers"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + } + } + if (r->headers_in.content_length) { r->headers_in.content_length_n = ngx_atoof(r->headers_in.content_length->value.data, -- cgit v1.2.3 From 59782257222b7a08cace201456cf2886b2a98067 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 1 Jun 2020 19:16:44 +0300 Subject: Fixed transport parameters on a new connection with a valid token. Previously, the retry transport parameter was sent regardless. --- src/event/ngx_event_quic.c | 5 +++++ src/event/ngx_event_quic_transport.c | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 37b3e4c6f..cd5b530c9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -652,7 +652,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, return NGX_ERROR; } +#if (NGX_QUIC_DRAFT_VERSION >= 28) qc->tp.original_dcid = c->quic->odcid; +#endif qc->tp.initial_scid = c->quic->dcid; qc->scid.len = pkt->scid.len; @@ -796,6 +798,9 @@ ngx_quic_retry(ngx_connection_t *c) } c->quic->token = token; +#if (NGX_QUIC_DRAFT_VERSION < 28) + c->quic->tp.original_dcid = c->quic->odcid; +#endif c->quic->tp.retry_scid = c->quic->dcid; c->quic->in_retry = 1; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 00df78a59..3b64aef6d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1673,15 +1673,15 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) #if (NGX_QUIC_DRAFT_VERSION >= 28) 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); -#endif - if (tp->retry) { -#if (NGX_QUIC_DRAFT_VERSION >= 28) + if (tp->retry_scid.len) { len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } #else + if (tp->original_dcid.len) { len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); -#endif } +#endif if (pos == NULL) { return len; @@ -1714,15 +1714,15 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) #if (NGX_QUIC_DRAFT_VERSION >= 28) ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); -#endif - if (tp->retry) { -#if (NGX_QUIC_DRAFT_VERSION >= 28) + if (tp->retry_scid.len) { ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } #else + if (tp->original_dcid.len) { ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); -#endif } +#endif return p - pos; } -- cgit v1.2.3 From 0a11fdbb28d2efaaf2a541c321d4c5566bf1fbe5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 1 Jun 2020 19:53:13 +0300 Subject: Compatibility with BoringSSL master branch. Recently BoringSSL introduced SSL_set_quic_early_data_context() that serves as an additional constrain to enable 0-RTT in QUIC. Relevant changes: * https://boringssl.googlesource.com/boringssl/+/7c52299%5E!/ * https://boringssl.googlesource.com/boringssl/+/8519432%5E!/ --- auto/lib/openssl/conf | 12 ++++++++++++ src/event/ngx_event_quic.c | 13 +++++++++++-- src/event/ngx_event_quic_transport.c | 21 +++++++++++++-------- src/event/ngx_event_quic_transport.h | 2 +- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 046317f8a..f4dcab725 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -167,3 +167,15 @@ END fi fi + + +if [ $USE_OPENSSL_QUIC = YES ]; then + ngx_feature="OpenSSL QUIC 0-RTT context" + ngx_feature_name="NGX_OPENSSL_QUIC_ZRTT_CTX" + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" + ngx_feature_test="SSL_set_quic_early_data_context(NULL, NULL, 0)" + . auto/feature +fi diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index cd5b530c9..2b226a3eb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1040,6 +1040,7 @@ static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { u_char *p; + size_t clen; ssize_t len; ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; @@ -1064,7 +1065,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif - len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp); + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); /* always succeeds */ p = ngx_pnalloc(c->pool, len); @@ -1072,7 +1073,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } - len = ngx_quic_create_transport_params(p, p + len, &qc->tp); + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); if (len < 0) { return NGX_ERROR; } @@ -1087,6 +1088,14 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } +#if NGX_OPENSSL_QUIC_ZRTT_CTX + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + qc->max_streams = qc->tp.initial_max_streams_bidi; qc->state = ssl_encryption_handshake; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 3b64aef6d..e056e23de 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1616,7 +1616,8 @@ ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) ssize_t -ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, + size_t *clen) { u_char *p; size_t len; @@ -1647,10 +1648,7 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) p = pos; - len = ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, - tp->active_connection_id_limit); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA,tp->initial_max_data); + len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, tp->initial_max_streams_uni); @@ -1670,6 +1668,13 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + if (clen) { + *clen = len; + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + #if (NGX_QUIC_DRAFT_VERSION >= 28) 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); @@ -1687,9 +1692,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) return len; } - ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, - tp->active_connection_id_limit); - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); @@ -1711,6 +1713,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp) ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + #if (NGX_QUIC_DRAFT_VERSION >= 28) ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 322fc78cf..e70317177 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -335,6 +335,6 @@ ssize_t ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log); ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, - ngx_quic_tp_t *tp); + ngx_quic_tp_t *tp, size_t *clen); #endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ -- cgit v1.2.3 From c0003539ac767ec9d16e54d26b5296a6669d0089 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 2 Jun 2020 15:59:14 +0300 Subject: Decoupled validation of Host and :authority for HTTP/2 and HTTP/3. Previously an error was triggered for HTTP/2 when host with port was passed by client. --- src/http/ngx_http_request.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 23b28c243..ac5937347 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2065,10 +2065,18 @@ ngx_http_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } - if (r->http_version >= NGX_HTTP_VERSION_20) { + if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_20) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent HTTP/2 request without " + "\":authority\" or \"Host\" header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->http_version == NGX_HTTP_VERSION_30) { if (r->headers_in.server.len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP request without " + "client sent HTTP/3 request without " "\":authority\" or \"Host\" header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; @@ -2082,7 +2090,7 @@ ngx_http_process_request_header(ngx_http_request_t *r) != 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP request with different " + "client sent HTTP/3 request with different " "values of \":authority\" and \"Host\" headers"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; -- cgit v1.2.3 From d047ecee95da4d80508f7b47f21bc46f2060f88c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 13:20:02 +0300 Subject: Treat receipt of NEW_TOKEN as connection error PROTOCOL_VIOLATION. --- src/event/ngx_event_quic.c | 1 - src/event/ngx_event_quic_transport.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2b226a3eb..376d1756b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1948,7 +1948,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_NEW_CONNECTION_ID: case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - case NGX_QUIC_FT_NEW_TOKEN: case NGX_QUIC_FT_RESET_STREAM: case NGX_QUIC_FT_STOP_SENDING: case NGX_QUIC_FT_PATH_CHALLENGE: diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index e056e23de..32fcb5a66 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1048,7 +1048,7 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) /* RESET_STREAM */ 0x3, /* STOP_SENDING */ 0x3, /* CRYPTO */ 0xD, - /* NEW_TOKEN */ 0x1, + /* NEW_TOKEN */ 0x0, /* only sent by server */ /* STREAM0 */ 0x3, /* STREAM1 */ 0x3, /* STREAM2 */ 0x3, -- cgit v1.2.3 From ac73dffb57ce7fa398c8b5501e177c69c34bad75 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 13:20:02 +0300 Subject: Receipt of CONNECTION_CLOSE in 0-RTT is permitted in draft-28. --- src/event/ngx_event_quic_transport.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 32fcb5a66..712fbf5bc 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1069,8 +1069,13 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) /* RETIRE_CONNECTION_ID */ 0x3, /* PATH_CHALLENGE */ 0x3, /* PATH_RESPONSE */ 0x3, +#if (NGX_QUIC_DRAFT_VERSION >= 28) + /* CONNECTION_CLOSE */ 0xF, + /* CONNECTION_CLOSE2 */ 0x3, +#else /* CONNECTION_CLOSE */ 0xD, /* CONNECTION_CLOSE2 */ 0x1, +#endif /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ }; -- cgit v1.2.3 From facf59bd863feedee9e188c578263070f513b77f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 13:20:03 +0300 Subject: Introduced connection error APPLICATION_ERROR from draft-28. --- src/event/ngx_event_quic_transport.c | 2 +- src/event/ngx_event_quic_transport.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 712fbf5bc..348580ae8 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -101,7 +101,7 @@ static char *ngx_quic_errors[] = { "CONNECTION_ID_LIMIT_ERROR", "PROTOCOL_VIOLATION", "INVALID_TOKEN", - "unknown error 0xC", + "APPLICATION_ERROR", "CRYPTO_BUFFER_EXCEEDED", "unknown error 0xE", "unknown error 0xF", diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index e70317177..cfa35ab78 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -87,7 +87,7 @@ #define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 #define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A #define NGX_QUIC_ERR_INVALID_TOKEN 0x0B -/* 0xC is not defined */ +#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D /* 0xE is not defined */ /* 0xF is not defined */ -- cgit v1.2.3 From d42354136031a3dfce5f7e5a857baae950c4fb14 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 20:59:26 +0300 Subject: Reject invalid STREAM ID with STREAM_STATE_ERROR connection error. --- src/event/ngx_event_quic.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 376d1756b..cb0df3d5d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2598,12 +2598,24 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qc = c->quic; f = &frame->u.stream; + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); if (sn == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id 0x%xi is new", f->stream_id); + if (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) ? qc->tp.initial_max_stream_data_uni : qc->tp.initial_max_stream_data_bidi_remote; -- cgit v1.2.3 From d42fadcf294e268c0623f16112985a1970b6dd56 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 20:59:27 +0300 Subject: Stream ID handling in RESET_STREAM and STOP_SENDING frames. --- src/event/ngx_event_quic.c | 81 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index cb0df3d5d..64dc3a19c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -220,6 +220,10 @@ static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -1946,10 +1950,30 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; - case NGX_QUIC_FT_NEW_CONNECTION_ID: - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: case NGX_QUIC_FT_RESET_STREAM: + + if (ngx_quic_handle_reset_stream_frame(c, pkt, + &frame.u.reset_stream) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_STOP_SENDING: + + if (ngx_quic_handle_stop_sending_frame(c, pkt, + &frame.u.stop_sending) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: @@ -2921,6 +2945,59 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic frame handler not implemented"); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + c->quic->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic frame handler not implemented"); + + qc = c->quic; + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xi is new", f->id); + + if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + } + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { -- cgit v1.2.3 From 7d8a64a8622ae479cfa4400b40cff9aca41532e7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 5 Jun 2020 20:59:27 +0300 Subject: Stream ID handling in MAX_STREAM_DATA and STREAM_DATA_BLOCKED. --- src/event/ngx_event_quic.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 64dc3a19c..0665e474b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2875,6 +2875,14 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_connection_t *qc; qc = c->quic; + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { @@ -2917,10 +2925,23 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ngx_quic_connection_t *qc; qc = c->quic; + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id); + + if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + } + return NGX_ERROR; } -- cgit v1.2.3 From d684a69c68705460121455e2fab0fe7726b62af3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Jun 2020 21:23:10 +0300 Subject: Fixed usage of own/client transport parameters. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0665e474b..11895d4e6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3058,7 +3058,7 @@ ngx_quic_output(ngx_connection_t *c) } if (!qc->retransmit.timer_set && !qc->closing) { - ngx_add_timer(&qc->retransmit, qc->tp.max_ack_delay); + ngx_add_timer(&qc->retransmit, qc->ctp.max_ack_delay); } return NGX_OK; @@ -3414,7 +3414,7 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, do { start = ngx_queue_data(q, ngx_quic_frame_t, queue); - wait = start->last + qc->tp.max_ack_delay - now; + wait = start->last + qc->ctp.max_ack_delay - now; if ((ngx_msec_int_t) wait > 0) { break; -- cgit v1.2.3 From 5cef58452d27801c82a3efd790f43e3695085981 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Jun 2020 21:33:20 +0300 Subject: Increased default initial retransmit timeout. This is a temporary workaround, proper retransmission mechanism based on quic-recovery rfc draft is yet to be implemented. Currently hardcoded value is too small for real networks. The patch sets static PTO, considering rtt of ~333ms, what gives about 1s. --- src/event/ngx_event_quic.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 11895d4e6..56cf9d06f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3058,7 +3058,8 @@ ngx_quic_output(ngx_connection_t *c) } if (!qc->retransmit.timer_set && !qc->closing) { - ngx_add_timer(&qc->retransmit, qc->ctp.max_ack_delay); + ngx_add_timer(&qc->retransmit, + qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO); } return NGX_OK; @@ -3414,7 +3415,8 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, do { start = ngx_queue_data(q, ngx_quic_frame_t, queue); - wait = start->last + qc->ctp.max_ack_delay - now; + wait = start->last + qc->ctp.max_ack_delay + + NGX_QUIC_HARDCODED_PTO - now; if ((ngx_msec_int_t) wait > 0) { break; @@ -3455,7 +3457,7 @@ ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, /* move frames group to the end of queue */ ngx_queue_add(&ctx->sent, &range); - wait = qc->tp.max_ack_delay; + wait = qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO; } while (q != ngx_queue_sentinel(&ctx->sent)); -- cgit v1.2.3 From 64c00708facb6e2203f88654837ede2a6a92f8cb Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Jun 2020 21:37:08 +0300 Subject: Limited max udp payload size for outgoing packets. This allows to avoid problems with packet fragmentation in real networks. This is a temporary workaround. --- src/event/ngx_event_quic.c | 8 +++++++- src/event/ngx_event_quic.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 56cf9d06f..d685d4514 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -430,6 +430,12 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } + if (qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_OUT) { + qc->ctp.max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client maximum packet size truncated"); + } + #if (NGX_QUIC_DRAFT_VERSION >= 28) if (qc->scid.len != qc->ctp.initial_scid.len || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, @@ -640,7 +646,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->streams.handler = handler; ctp = &qc->ctp; - ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 17ea64700..495e07996 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -23,6 +23,8 @@ /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ #define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1300 /* TODO */ + #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 -- cgit v1.2.3 From 966a8679716b55625602be5cda024cace3a63041 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Jun 2020 21:37:48 +0300 Subject: Style. --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d685d4514..d84ea88a7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -442,7 +442,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->scid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic client initial_source_connection_id mismatch"); + "quic client initial_source_connection_id " + "mismatch"); return 0; } #endif -- cgit v1.2.3 From 6c2712f7818cda54f3954b04f677b057fe49be2e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 15 Jun 2020 16:59:53 +0300 Subject: QUIC: Fixed connection cleanup. A posted event need to be deleted during the connection close. --- src/event/ngx_event_quic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d84ea88a7..9253549ca 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1321,6 +1321,10 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_del_timer(&qc->retransmit); } + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); + } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic part of connection is terminated"); -- cgit v1.2.3 From d6d7838c79b179ffafa661826cebbce34c425462 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 15 Jun 2020 17:06:40 +0300 Subject: QUIC: raise error on missing transport parameters. quic-tls, 8.2: The quic_transport_parameters extension is carried in the ClientHello and the EncryptedExtensions messages during the handshake. Endpoints MUST send the quic_transport_parameters extension; endpoints that receive ClientHello or EncryptedExtensions messages without the quic_transport_parameters extension MUST close the connection with an error of type 0x16d (equivalent to a fatal TLS missing_extension alert, see Section 4.10). --- src/event/ngx_event_quic.c | 88 +++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9253549ca..0fd25bb64 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -400,56 +400,64 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "quic SSL_get_peer_quic_transport_params():" " params_len %ui", client_params_len); - if (client_params_len != 0) { - p = (u_char *) client_params; - end = p + client_params_len; + if (client_params_len == 0) { + /* quic-tls 8.2 */ + qc->error = 0x100 + SSL_AD_MISSING_EXTENSION; + qc->error_reason = "missing transport parameters"; - if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) - != NGX_OK) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "failed to process transport parameters"; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "missing transport parameters"); + return 0; + } - return 0; - } + p = (u_char *) client_params; + end = p + client_params_len; - if (qc->ctp.max_idle_timeout > 0 - && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) - { - qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; - } + if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; - if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE - || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "invalid maximum packet size"; + return 0; + } - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic maximum packet size is invalid"); - return 0; - } + if (qc->ctp.max_idle_timeout > 0 + && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; + } - if (qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_OUT) { - qc->ctp.max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic client maximum packet size truncated"); - } + if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return 0; + } + + if (qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_OUT) { + qc->ctp.max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client maximum packet size truncated"); + } #if (NGX_QUIC_DRAFT_VERSION >= 28) - if (qc->scid.len != qc->ctp.initial_scid.len - || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, - qc->scid.len) != 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic client initial_source_connection_id " - "mismatch"); - return 0; - } + if (qc->scid.len != qc->ctp.initial_scid.len + || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, + qc->scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id " + "mismatch"); + return 0; + } #endif - qc->client_tp_done = 1; - } + qc->client_tp_done = 1; } /* -- cgit v1.2.3 From 3b3264dc25a6d5da80c1641671737fc6505c45f1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 16 Jun 2020 11:54:05 +0300 Subject: QUIC: further limiting maximum QUIC packet size. quic-transport draft 29, section 14: QUIC depends upon a minimum IP packet size of at least 1280 bytes. This is the IPv6 minimum size [RFC8200] and is also supported by most modern IPv4 networks. Assuming the minimum IP header size, this results in a QUIC maximum packet size of 1232 bytes for IPv6 and 1252 bytes for IPv4. Since the packet size can change during connection lifetime, the ngx_quic_max_udp_payload() function is introduced that currently returns minimal allowed size, depending on address family. --- src/event/ngx_event_quic.c | 22 +++++++++++++++++++--- src/event/ngx_event_quic.h | 3 ++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0fd25bb64..ce07930d6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -157,6 +157,7 @@ static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); +static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); @@ -439,8 +440,8 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - if (qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_OUT) { - qc->ctp.max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; + if (qc->ctp.max_udp_payload_size > ngx_quic_max_udp_payload(c)) { + qc->ctp.max_udp_payload_size = ngx_quic_max_udp_payload(c); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic client maximum packet size truncated"); } @@ -655,7 +656,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->streams.handler = handler; ctp = &qc->ctp; - ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_OUT; + ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; @@ -1122,6 +1123,21 @@ ngx_quic_init_connection(ngx_connection_t *c) } +static ngx_inline size_t +ngx_quic_max_udp_payload(ngx_connection_t *c) +{ + /* TODO: path MTU discovery */ + +#if (NGX_HAVE_INET6) + if (c->sockaddr->sa_family == AF_INET6) { + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; + } +#endif + + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; +} + + static void ngx_quic_input_handler(ngx_event_t *rev) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 495e07996..fe67c3e6a 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -23,7 +23,8 @@ /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ #define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1300 /* TODO */ +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 -- cgit v1.2.3 From a213258b5beb8d83a0907eed00fe402d67610303 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Jun 2020 11:16:35 +0300 Subject: QUIC: fixed off-by-one in frame range handler. The ctx->pnum is incremented after the packet is sent, thus pointing to the next packet number, which should not be used in comparison. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ce07930d6..562a215ea 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2262,7 +2262,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (!found) { - if (max <= ctx->pnum) { + if (max < ctx->pnum) { /* duplicate ACK or ACK for non-ack-eliciting frame */ return NGX_OK; } -- cgit v1.2.3 From 28f1acdb6f6404cfffc7158d109e1c460dac8d94 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Jun 2020 13:58:46 +0300 Subject: QUIC: added ALPN checks. quic-transport draft 29: section 7: * authenticated negotiation of an application protocol (TLS uses ALPN [RFC7301] for this purpose) ... Endpoints MUST explicitly negotiate an application protocol. This avoids situations where there is a disagreement about the protocol that is in use. section 8.1: When using ALPN, endpoints MUST immediately close a connection (see Section 10.3 of [QUIC-TRANSPORT]) with a no_application_protocol TLS alert (QUIC error code 0x178; see Section 4.10) if an application protocol is not negotiated. Changes in ngx_quic_close_quic() function are required to avoid attempts to generated and send packets without proper keys, what happens in case of failed ALPN check. --- src/event/ngx_event_quic.c | 30 +++++++++++++++++++++++++++--- src/event/ngx_event_quic.h | 5 +++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 562a215ea..46827861a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -393,6 +393,31 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, "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 defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + { + unsigned int len; + const unsigned char *data; + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len != NGX_QUIC_ALPN_LEN + || ngx_strncmp(data, NGX_QUIC_ALPN_STR, NGX_QUIC_ALPN_LEN) != 0) + { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + } +#endif SSL_get_peer_quic_transport_params(ssl_conn, &client_params, &client_params_len); @@ -1298,9 +1323,8 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->error_reason ? qc->error_reason : ""); } - level = (qc->state == ssl_encryption_early_data) - ? ssl_encryption_handshake - : qc->state; + level = c->ssl ? SSL_quic_read_level(c->ssl->connection) + : ssl_encryption_initial; (void) ngx_quic_send_cc(c, level, err, qc->error_ftype, qc->error_reason); diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index fe67c3e6a..8974a3392 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -18,6 +18,11 @@ #endif #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) +#define NGX_QUIC_ALPN(s) NGX_QUIC_ALPN_DRAFT(s) +#define NGX_QUIC_ALPN_DRAFT(s) "h3-" #s +#define NGX_QUIC_ALPN_STR NGX_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION) +#define NGX_QUIC_ALPN_LEN (sizeof(NGX_QUIC_ALPN_STR) - 1) + #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ #define NGX_QUIC_MAX_LONG_HEADER 56 /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ -- cgit v1.2.3 From f1fdf6901b197f226bbb2bdba9dccf2d502038be Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Jun 2020 14:29:24 +0300 Subject: QUIC: cleaned up quic encryption state tracking. The patch removes remnants of the old state tracking mechanism, which did not take into account assimetry of read/write states and was not very useful. The encryption state now is entirely tracked using SSL_quic_read/write_level(). --- src/event/ngx_event_quic.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 46827861a..bbffd1472 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -86,8 +86,6 @@ struct ngx_quic_connection_s { ngx_quic_tp_t tp; ngx_quic_tp_t ctp; - enum ssl_encryption_level_t state; - ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; ngx_quic_secrets_t next_key; @@ -296,10 +294,6 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, keys = &c->quic->keys[level]; - if (level == ssl_encryption_early_data) { - c->quic->state = ssl_encryption_early_data; - } - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, rsecret, secret_len, &keys->client); @@ -358,7 +352,6 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } if (level == ssl_encryption_early_data) { - c->quic->state = ssl_encryption_early_data; return 1; } @@ -675,7 +668,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->push.cancelable = 1; c->quic = qc; - qc->state = ssl_encryption_initial; qc->ssl = ssl; qc->tp = *tp; qc->streams.handler = handler; @@ -1142,7 +1134,6 @@ ngx_quic_init_connection(ngx_connection_t *c) #endif qc->max_streams = qc->tp.initial_max_streams_bidi; - qc->state = ssl_encryption_handshake; return NGX_OK; } @@ -1743,12 +1734,14 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (c->quic->state != ssl_encryption_early_data) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unexpected 0-RTT packet"); - return NGX_OK; + keys = &c->quic->keys[ssl_encryption_early_data]; + + if (keys->client.key.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no 0-RTT keys yet, packet ignored"); + return NGX_DECLINED; } - keys = &c->quic->keys[ssl_encryption_early_data]; pkt->secret = &keys->client; pkt->level = ssl_encryption_early_data; @@ -2614,7 +2607,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } } else if (n == 1 && !SSL_in_init(ssl_conn)) { - c->quic->state = ssl_encryption_application; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); -- cgit v1.2.3 From c31e1054034589af9a12119371e502748af61e5a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: QUIC error SERVER_BUSY renamed to CONNECTION_REFUSED in draft-29. --- src/event/ngx_event_quic_transport.c | 2 +- src/event/ngx_event_quic_transport.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 348580ae8..950b2ad3a 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -91,7 +91,7 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, static char *ngx_quic_errors[] = { "NO_ERROR", "INTERNAL_ERROR", - "SERVER_BUSY", + "CONNECTION_REFUSED", "FLOW_CONTROL_ERROR", "STREAM_LIMIT_ERROR", "STREAM_STATE_ERROR", diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index cfa35ab78..d85fd34bb 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -77,7 +77,7 @@ /* 22.4. QUIC Transport Error Codes Registry */ #define NGX_QUIC_ERR_NO_ERROR 0x00 #define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 -#define NGX_QUIC_ERR_SERVER_BUSY 0x02 +#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 #define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 #define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 #define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 -- cgit v1.2.3 From 9d465009149c67325d9ac49503891cc84e5eaef8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Do not close QUIC sockets in ngx_close_listening_sockets(). This breaks graceful shutdown of QUIC connections in terms of quic-transport. --- src/core/ngx_connection.c | 4 ++++ src/core/ngx_connection.h | 1 + src/http/ngx_http.c | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 9ec1cd7ac..5bae54502 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1034,6 +1034,10 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].quic) { + continue; + } + c = ls[i].connection; if (c) { diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index b3a36cf05..294d4b212 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -75,6 +75,7 @@ struct ngx_listening_s { unsigned reuseport:1; unsigned add_reuseport:1; unsigned keepalive:2; + unsigned quic:1; unsigned deferred_accept:1; unsigned delete_deferred:1; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 1b5e387db..0a645722c 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1797,6 +1797,10 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; +#if (NGX_HTTP_SSL) + ls->quic = addr->opt.http3; +#endif + return ls; } -- cgit v1.2.3 From 1cfd67c44a1dd14f063d0aedf7a810b4700cbaea Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Close QUIC connection with NO_ERROR on c->close. That way it makes more sense. Previously it was closed with INTERNAL_ERROR. --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index bbffd1472..95711bdfc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1182,7 +1182,8 @@ ngx_quic_input_handler(ngx_event_t *rev) } if (c->close) { - ngx_quic_close_connection(c, NGX_ERROR); + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_OK); return; } -- cgit v1.2.3 From 157da97d7ad4bf6eae165b46d26f7756f4f8381d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Reject new QUIC connection with CONNECTION_REFUSED on shutdown. --- src/event/ngx_event_quic.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 95711bdfc..1a2fdf2d5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -712,6 +712,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, qc->initialized = 1; + if (ngx_terminate || ngx_exiting) { + qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; + return NGX_ERROR; + } + if (pkt->token.len) { rc = ngx_quic_validate_token(c, pkt); -- cgit v1.2.3 From 82519e1af283a3bd392cbd27419afef0de4180bd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Define KEY_UPDATE_ERROR from quic-tls-24. --- src/event/ngx_event_quic_transport.c | 2 +- src/event/ngx_event_quic_transport.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 950b2ad3a..228a85e11 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -103,7 +103,7 @@ static char *ngx_quic_errors[] = { "INVALID_TOKEN", "APPLICATION_ERROR", "CRYPTO_BUFFER_EXCEEDED", - "unknown error 0xE", + "KEY_UPDATE_ERROR", "unknown error 0xF", "CRYPTO_ERROR", }; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index d85fd34bb..222f8ef51 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -89,7 +89,7 @@ #define NGX_QUIC_ERR_INVALID_TOKEN 0x0B #define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D -/* 0xE is not defined */ +#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E /* 0xF is not defined */ #define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 -- cgit v1.2.3 From d7baead1e82f13d26a90894dfbd0f665c45bfd46 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Close connection with PROTOCOL_VIOLATION on decryption failure. A previously used undefined error code is now replaced with the generic one. Note that quic-transport prescribes keeping connection intact, discarding such QUIC packets individually, in the sense that coalesced packets could be there. This is selectively handled in the next change. --- src/event/ngx_event_quic_protection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 7162c7703..8afa9e842 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1050,7 +1050,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { - pkt->error = NGX_QUIC_ERR_CRYPTO_ERROR; + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; return NGX_ERROR; } @@ -1130,7 +1130,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, #endif if (rc != NGX_OK) { - pkt->error = NGX_QUIC_ERR_CRYPTO_ERROR; + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; return rc; } -- cgit v1.2.3 From fc0036bdd6a2a3bec218004e68646a4653328e92 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Discard short packets which could not be decrypted. So that connections are protected from failing from on-path attacks. Decryption failure of long packets used during handshake still leads to connection close since it barely makes sense to handle them there. --- src/event/ngx_event_quic.c | 6 ++++-- src/event/ngx_event_quic_protection.c | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1a2fdf2d5..29bce24de 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1830,9 +1830,11 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); + + if (rc != NGX_OK) { qc->error = pkt->error; - return NGX_ERROR; + return rc; } /* switch keys on Key Phase change */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 8afa9e842..2d49106f3 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1051,7 +1051,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, != NGX_OK) { pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_long_pkt(pkt->flags)) { @@ -1131,7 +1131,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (rc != NGX_OK) { pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - return rc; + return NGX_DECLINED; } if (badflags) { -- cgit v1.2.3 From 3d27c55ae3e79557002368e7f2cc08ebc6390579 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Get rid of hardcoded numbers used for quic handshake errors. --- src/event/ngx_event_quic.c | 6 ++++-- src/event/ngx_event_quic_transport.c | 5 +++-- src/event/ngx_event_quic_transport.h | 9 ++++++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 29bce24de..1fc0c52ee 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -421,7 +421,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, if (client_params_len == 0) { /* quic-tls 8.2 */ - qc->error = 0x100 + SSL_AD_MISSING_EXTENSION; + 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, @@ -552,7 +552,9 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, return 1; } - if (ngx_quic_send_cc(c, level, 0x100 + alert, 0, "TLS alert") != NGX_OK) { + if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_CRYPTO(alert), 0, "TLS alert") + != NGX_OK) + { return 0; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 228a85e11..b7cb7eee3 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -104,8 +104,6 @@ static char *ngx_quic_errors[] = { "APPLICATION_ERROR", "CRYPTO_BUFFER_EXCEEDED", "KEY_UPDATE_ERROR", - "unknown error 0xF", - "CRYPTO_ERROR", }; @@ -218,6 +216,9 @@ ngx_quic_build_int(u_char **pos, uint64_t value) u_char * ngx_quic_error_text(uint64_t error_code) { + if (error_code >= NGX_QUIC_ERR_CRYPTO_ERROR) { + return (u_char *) "handshake error"; + } if (error_code >= NGX_QUIC_ERR_LAST) { return (u_char *) "unknown error"; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 222f8ef51..902db3caf 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -75,6 +75,7 @@ #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E /* 22.4. QUIC Transport Error Codes Registry */ +/* Keep in sync with ngx_quic_errors[] */ #define NGX_QUIC_ERR_NO_ERROR 0x00 #define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 #define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 @@ -90,10 +91,12 @@ #define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D #define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E -/* 0xF is not defined */ -#define NGX_QUIC_ERR_CRYPTO_ERROR 0x10 -#define NGX_QUIC_ERR_LAST NGX_QUIC_ERR_CRYPTO_ERROR +#define NGX_QUIC_ERR_LAST 0x0F +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 + +#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) + /* Transport parameters */ #define NGX_QUIC_TP_ORIGINAL_DCID 0x00 -- cgit v1.2.3 From 439fad4df5682729c79fe7fe90b48af008cfe998 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: Update Initial salt and Retry secret from quic-tls-29. See sections 5.2 and 5.8 for the current values. --- src/event/ngx_event_quic_protection.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 2d49106f3..6cf55005c 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -138,8 +138,13 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, const EVP_CIPHER *cipher; static const uint8_t salt[20] = +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" + "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; +#else "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; +#endif /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ @@ -903,11 +908,17 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) /* 5.8. Retry Packet Integrity */ static u_char key[16] = - "\x4d\x32\xec\xdb\x2a\x21\x33\xc8" - "\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; +#else + "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; +#endif static u_char nonce[12] = - "\x4d\x16\x11\xd0\x55\x13" - "\xa5\x52\xc5\x87\xd5\x75"; +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; +#else + "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75"; +#endif static ngx_str_t in = ngx_string(""); ad.data = res->data; -- cgit v1.2.3 From cbfecb46fe6b0bd67dc3db47504fd2bb74239645 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 23 Jun 2020 11:57:00 +0300 Subject: README: documented draft-28, draft-29 support. --- README | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README b/README index 1dc6601b4..680deb465 100644 --- a/README +++ b/README @@ -34,10 +34,9 @@ Experimental QUIC support for nginx What works now: - Currently we support IETF-QUIC draft 27 - Earlier drafts are NOT supported as they have incompatible wire format; + Currently we support IETF-QUIC draft-27, draft-28, draft-29. + Earlier drafts are NOT supported as they have incompatible wire format. - Newer drafts development (draft-28 at the time of writing) is in progress. You may look at src/event/ngx_event_quic.h for alternative values of the NGX_QUIC_DRAFT_VERSION macro used to select IETF draft version number. -- cgit v1.2.3 From 9022fc686d447360c3e01ea0ead47dcb1035ecc9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 19 Jun 2020 11:29:30 +0300 Subject: Style. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1fc0c52ee..99a2183f5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2173,6 +2173,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) return NGX_OK; } + static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) -- cgit v1.2.3 From 3168f58306820428fec2c2871fd03a325aa0c65c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 25 Jun 2020 20:31:13 +0300 Subject: HTTP/3: do not emit a DATA frame header for header_only responses. This resulted in the frame error due to the invalid DATA frame length. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7fb297728..d9ea99d2c 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -666,7 +666,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; - if (r->headers_out.content_length_n >= 0) { + if (r->headers_out.content_length_n >= 0 && !r->header_only) { len = 1 + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); -- cgit v1.2.3 From 690e1bc50cf47257ce76eeed2c0392a64bee24b0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 26 Jun 2020 10:05:28 +0300 Subject: HTTP/3: fixed dropping first non-pseudo header. --- src/http/v3/ngx_http_v3_request.c | 49 ++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index d9ea99d2c..5747c8ee6 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -105,6 +105,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); r->request_end = p; + r->state = 0; return NGX_OK; } @@ -127,28 +128,47 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t hash, i, n; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; + enum { + sw_start = 0, + sw_done, + sw_next, + sw_header + }; c = r->connection; st = r->h3_parse; - if (st->header_rep.state == 0) { + switch (r->state) { + + case sw_start: r->parse_start = b->pos; - r->invalid_header = 0; - } - if (st->state == 0) { - if (r->header_name_start == NULL) { - name = &st->header_rep.header.name; + if (st->state) { + r->state = sw_next; + goto done; + } - if (name->len && name->data[0] != ':') { - goto done; - } + name = &st->header_rep.header.name; + + if (name->len && name->data[0] != ':') { + r->state = sw_done; + goto done; } + /* fall through */ + + case sw_done: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); - return NGX_HTTP_PARSE_HEADER_DONE; + + case sw_next: + r->parse_start = b->pos; + r->invalid_header = 0; + break; + + case sw_header: + break; } while (b->pos < b->last) { @@ -158,11 +178,18 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, return NGX_HTTP_PARSE_INVALID_HEADER; } - if (rc != NGX_AGAIN) { + if (rc == NGX_DONE) { + r->state = sw_done; + goto done; + } + + if (rc == NGX_OK) { + r->state = sw_next; goto done; } } + r->state = sw_header; return NGX_AGAIN; done: -- cgit v1.2.3 From b7662c0e80003a676375be7d46071231321260e6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 26 Jun 2020 11:58:00 +0300 Subject: HTTP/3: introduced ngx_http_v3_get_module_srv_conf() macro. The macro helps to access a module's server configuration from a QUIC stream context. --- src/http/v3/ngx_http_v3.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 29cc06ee9..3c5bb721c 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -61,6 +61,12 @@ typedef struct { } ngx_http_v3_connection_t; +#define ngx_http_v3_get_module_srv_conf(c, module) \ + ngx_http_get_module_srv_conf( \ + ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \ + module) + + typedef struct { ngx_str_t name; ngx_str_t value; -- cgit v1.2.3 From 8d41d17a12509d6f80f51a27046b7a10882892ef Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 29 Jun 2020 15:56:14 +0300 Subject: HTTP/3: http3_max_field_size directive to limit string size. Client streams may send literal strings which are now limited in size by the new directive. The default value is 4096. The directive is similar to HTTP/2 directive http2_max_field_size. --- src/http/v3/ngx_http_v3.h | 3 +++ src/http/v3/ngx_http_v3_module.c | 12 ++++++++++++ src/http/v3/ngx_http_v3_parse.c | 11 ++++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 3c5bb721c..d48f13922 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -47,9 +47,12 @@ #define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 #define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 +#define NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE 4096 + typedef struct { ngx_quic_tp_t quic; + size_t max_field_size; } ngx_http_v3_srv_conf_t; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 550b706da..17029934e 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -118,6 +118,13 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, quic.retry), NULL }, + { ngx_string("http3_max_field_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_field_size), + NULL }, + ngx_null_command }; @@ -268,6 +275,8 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) v3cf->quic.retry = NGX_CONF_UNSET; + v3cf->max_field_size = NGX_CONF_UNSET_SIZE; + return v3cf; } @@ -329,6 +338,9 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + ngx_conf_merge_size_value(conf->max_field_size, + prev->max_field_size, + NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE); return NGX_CONF_OK; } diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 3be3802ed..71a643d70 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -399,7 +399,8 @@ ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, u_char ch) { - ngx_uint_t n; + ngx_uint_t n; + ngx_http_v3_srv_conf_t *v3cf; enum { sw_start = 0, sw_value @@ -415,6 +416,14 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, n = st->length; + v3cf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (n > v3cf->max_field_size) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "client exceeded http3_max_field_size limit"); + return NGX_ERROR; + } + if (st->huffman) { n = n * 8 / 5; st->huffstate = 0; -- cgit v1.2.3 From a7ef0da3c8b64f2b5f4d8a7e73e724a74611284c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 15:15:55 +0300 Subject: HTTP/3: fixed prefixed integer encoding and decoding. Previously bytes were ordered from MSB to LSB, but the right order is the reverse. --- src/http/v3/ngx_http_v3.c | 17 ++++++++--------- src/http/v3/ngx_http_v3_parse.c | 13 +++++++------ src/http/v3/ngx_http_v3_parse.h | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index f7f79b9f4..e796da772 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -78,23 +78,22 @@ ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) value -= thresh; - for (n = 10; n > 1; n--) { - if (value >> (7 * (n - 1))) { - break; + if (p == NULL) { + for (n = 2; value >= 128; n++) { + value >>= 7; } - } - if (p == NULL) { - return n + 1; + return n; } *p++ |= thresh; - for ( /* void */ ; n > 1; n--) { - *p++ = 0x80 | (value >> 7 * (n - 1)); + while (value >= 128) { + *p++ = 0x80 | value; + value >>= 7; } - *p++ = value & 0x7f; + *p++ = value; return (uintptr_t) p; } diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 71a643d70..85491082e 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -91,6 +91,7 @@ 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, u_char ch) { + ngx_uint_t mask; enum { sw_start = 0, sw_value @@ -100,25 +101,25 @@ ngx_http_v3_parse_prefix_int(ngx_connection_t *c, case sw_start: - st->mask = (1 << prefix) - 1; - st->value = (ch & st->mask); + mask = (1 << prefix) - 1; + st->value = ch & mask; - if (st->value != st->mask) { + if (st->value != mask) { goto done; } - st->value = 0; + st->shift = 0; st->state = sw_value; break; case sw_value: - st->value = (st->value << 7) + (ch & 0x7f); + st->value += (ch & 0x7f) << st->shift; if (ch & 0x80) { + st->shift += 7; break; } - st->value += st->mask; goto done; } diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index ec78c7c35..17ff6c777 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -22,7 +22,7 @@ typedef struct { typedef struct { ngx_uint_t state; - ngx_uint_t mask; + ngx_uint_t shift; uint64_t value; } ngx_http_v3_parse_prefix_int_t; -- cgit v1.2.3 From a687d08062d8cb029ab82249aa55833cf44be3ce Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 15:34:05 +0300 Subject: HTTP/3: refactored dynamic table implementation. Previously dynamic table was not functional because of zero limit on its size set by default. Now the following changes enable it: - new directives to set SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS - send settings with SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS to the client - send Insert Count Increment to the client - send Header Acknowledgement to the client - evict old dynamic table entries on overflow - decode Required Insert Count from client - block stream if Required Insert Count is not reached --- src/http/ngx_http_request.c | 21 +- src/http/v3/ngx_http_v3.h | 53 +++-- src/http/v3/ngx_http_v3_module.c | 24 ++ src/http/v3/ngx_http_v3_parse.c | 111 +++++++-- src/http/v3/ngx_http_v3_request.c | 8 +- src/http/v3/ngx_http_v3_streams.c | 54 ++++- src/http/v3/ngx_http_v3_tables.c | 479 ++++++++++++++++++++++++++++---------- 7 files changed, 584 insertions(+), 166 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ac5937347..89e554bf2 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -223,7 +223,17 @@ ngx_http_init_connection(ngx_connection_t *c) #if (NGX_HTTP_V3) if (c->type == SOCK_DGRAM) { - hc = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + ngx_http_v3_connection_t *h3c; + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_queue_init(&h3c->blocked); + + hc = &h3c->hc; hc->quic = 1; hc->ssl = 1; @@ -414,6 +424,13 @@ ngx_http_quic_stream_handler(ngx_connection_t *c) pc = c->qs->parent; h3c = pc->data; + if (!h3c->settings_sent) { + h3c->settings_sent = 1; + + /* TODO close QUIC connection on error */ + (void) ngx_http_v3_send_settings(c); + } + if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ngx_http_v3_handle_client_uni_stream(c); return; @@ -1255,7 +1272,7 @@ ngx_http_process_request_line(ngx_event_t *rev) break; } - if (rc == NGX_DONE) { + if (rc == NGX_BUSY) { if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index d48f13922..b51febcdd 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -48,32 +48,48 @@ #define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 #define NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE 4096 +#define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 +#define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 + + +#define ngx_http_v3_get_module_srv_conf(c, module) \ + ngx_http_get_module_srv_conf( \ + ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \ + module) typedef struct { - ngx_quic_tp_t quic; - size_t max_field_size; + ngx_quic_tp_t quic; + size_t max_field_size; + size_t max_table_capacity; + ngx_uint_t max_blocked_streams; } ngx_http_v3_srv_conf_t; typedef struct { - ngx_http_connection_t hc; - - ngx_array_t *dynamic; - ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; -} ngx_http_v3_connection_t; + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; -#define ngx_http_v3_get_module_srv_conf(c, module) \ - ngx_http_get_module_srv_conf( \ - ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \ - module) +typedef struct { + ngx_http_v3_header_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; +} ngx_http_v3_dynamic_table_t; typedef struct { - ngx_str_t name; - ngx_str_t value; -} ngx_http_v3_header_t; + ngx_http_connection_t hc; + ngx_http_v3_dynamic_table_t table; + ngx_queue_t blocked; + ngx_uint_t nblocked; + ngx_uint_t settings_sent; + /* unsigned settings_sent:1; */ + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; +} ngx_http_v3_connection_t; ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b); @@ -88,6 +104,7 @@ uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix); +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, @@ -99,8 +116,12 @@ ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); -ngx_http_v3_header_t *ngx_http_v3_lookup_table(ngx_connection_t *c, - ngx_uint_t dynamic, ngx_uint_t index); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count); ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 17029934e..386209cb7 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -125,6 +125,20 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_field_size), NULL }, + { ngx_string("http3_max_table_capacity"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_table_capacity), + NULL }, + + { ngx_string("http3_max_blocked_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_blocked_streams), + NULL }, + ngx_null_command }; @@ -276,6 +290,8 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) v3cf->quic.retry = NGX_CONF_UNSET; v3cf->max_field_size = NGX_CONF_UNSET_SIZE; + v3cf->max_table_capacity = NGX_CONF_UNSET_SIZE; + v3cf->max_blocked_streams = NGX_CONF_UNSET_UINT; return v3cf; } @@ -342,6 +358,14 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->max_field_size, NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE); + ngx_conf_merge_size_value(conf->max_table_capacity, + prev->max_table_capacity, + NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY); + + ngx_conf_merge_uint_value(conf->max_blocked_streams, + prev->max_blocked_streams, + NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS); + return NGX_CONF_OK; } diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 85491082e..bd91c5b02 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -10,6 +10,10 @@ #include +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); + + ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, u_char ch) @@ -144,6 +148,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, sw_start = 0, sw_length, sw_prefix, + sw_verify, sw_header_rep, sw_done }; @@ -195,9 +200,20 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return NGX_ERROR; } - st->state = sw_header_rep; + 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; + } + + st->state = sw_header_rep; + + /* fall through */ + case sw_header_rep: rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base, @@ -228,6 +244,12 @@ 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_client_ack_header(c, c->qs->id) != NGX_OK) { + return NGX_ERROR; + } + } + st->state = sw_start; return NGX_DONE; } @@ -286,6 +308,10 @@ ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, done: + if (ngx_http_v3_decode_insert_count(c, &st->insert_count) != NGX_OK) { + return NGX_ERROR; + } + if (st->sign) { st->base = st->insert_count - st->delta_base - 1; } else { @@ -294,7 +320,7 @@ done: ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header block prefix done " - "i:%ui, s:%ui, d:%ui, base:%uL", + "insert_count:%ui, sign:%ui, delta_base:%ui, base:%uL", st->insert_count, st->sign, st->delta_base, st->base); st->state = sw_start; @@ -479,7 +505,6 @@ ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { - ngx_http_v3_header_t *h; enum { sw_start = 0, sw_index @@ -518,15 +543,14 @@ done: st->index = st->base - st->index - 1; } - h = ngx_http_v3_lookup_table(c, st->dynamic, st->index); - if (h == NULL) { + if (ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, + &st->value) + != NGX_OK) + { return NGX_ERROR; } - st->name = h->name; - st->value = h->value; st->state = sw_start; - return NGX_DONE; } @@ -535,8 +559,7 @@ ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { - ngx_int_t rc; - ngx_http_v3_header_t *h; + ngx_int_t rc; enum { sw_start = 0, sw_index, @@ -616,12 +639,12 @@ done: st->index = st->base - st->index - 1; } - h = ngx_http_v3_lookup_table(c, st->dynamic, st->index); - if (h == NULL) { + if (ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL) + != NGX_OK) + { return NGX_ERROR; } - st->name = h->name; st->state = sw_start; return NGX_DONE; } @@ -735,7 +758,6 @@ ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { - ngx_http_v3_header_t *h; enum { sw_start = 0, sw_index @@ -768,13 +790,13 @@ done: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi done dynamic[+%ui]", st->index); - h = ngx_http_v3_lookup_table(c, 1, st->base + st->index); - if (h == NULL) { + if (ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, + &st->value) + != NGX_OK) + { return NGX_ERROR; } - st->name = h->name; - st->value = h->value; st->state = sw_start; return NGX_DONE; } @@ -784,8 +806,7 @@ ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { - ngx_int_t rc; - ngx_http_v3_header_t *h; + ngx_int_t rc; enum { sw_start = 0, sw_index, @@ -860,17 +881,57 @@ done: "http3 parse header lpbi done dynamic[+%ui] \"%V\"", st->index, &st->value); - h = ngx_http_v3_lookup_table(c, 1, st->base + st->index); - if (h == NULL) { + if (ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL) + != NGX_OK) + { return NGX_ERROR; } - st->name = h->name; 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) { + return ngx_http_v3_lookup_static(c, index, name, value); + } + + if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { + return NGX_ERROR; + } + + 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; +} + + ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) { @@ -1145,7 +1206,7 @@ done: "http3 parse encoder instruction done"); st->state = sw_start; - return NGX_DONE; + return NGX_AGAIN; } @@ -1429,7 +1490,7 @@ done: "http3 parse decoder instruction done"); st->state = sw_start; - return NGX_DONE; + return NGX_AGAIN; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5747c8ee6..ae65ba9ea 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -64,12 +64,18 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) } while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + rc = ngx_http_v3_parse_headers(c, st, *b->pos); if (rc == NGX_ERROR) { goto failed; } + if (rc == NGX_BUSY) { + return NGX_BUSY; + } + + b->pos++; + if (rc == NGX_AGAIN) { continue; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index bd4583998..229ce5bf4 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -34,10 +34,6 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) { ngx_http_v3_uni_stream_t *us; - ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); - ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); - ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new uni stream id:0x%uxL", c->qs->id); @@ -340,6 +336,56 @@ 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_srv_conf_t *v3cf; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + h3c = c->qs->parent->data; + v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + + n = ngx_http_v3_encode_varlen_int(NULL, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + n += ngx_http_v3_encode_varlen_int(NULL, v3cf->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, v3cf->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, v3cf->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, v3cf->max_blocked_streams); + n = p - buf; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index ecebe943c..a57eccb9f 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -10,10 +10,22 @@ #include -static ngx_array_t *ngx_http_v3_get_dynamic_table(ngx_connection_t *c); +#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) + + +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); +static void ngx_http_v3_cleanup_table(void *data); +static void ngx_http_v3_unblock(void *data); static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c); +typedef struct { + ngx_queue_t queue; + ngx_connection_t *connection; + ngx_uint_t *nblocked; +} ngx_http_v3_block_t; + + static ngx_http_v3_header_t ngx_http_v3_static_table[] = { { ngx_string(":authority"), ngx_string("") }, @@ -148,40 +160,73 @@ ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) { - ngx_array_t *dt; - ngx_connection_t *pc; - ngx_http_v3_header_t *ref, *h; + ngx_str_t name; - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 ref insert %s[$ui] \"%V\"", - dynamic ? "dynamic" : "static", index, value); + if (dynamic) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert dynamic[%ui] \"%V\"", index, value); - pc = c->qs->parent; + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { + return NGX_ERROR; + } - ref = ngx_http_v3_lookup_table(c, dynamic, index); - if (ref == NULL) { - return NGX_ERROR; - } + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert static[%ui] \"%V\"", index, value); - dt = ngx_http_v3_get_dynamic_table(c); - if (dt == NULL) { - return NGX_ERROR; + if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { + return NGX_ERROR; + } } - h = ngx_array_push(dt); - if (h == NULL) { + return ngx_http_v3_insert(c, &name, value); +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + size_t size; + ngx_http_v3_header_t *h; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + size = ngx_http_v3_table_entry_size(name, value); + + if (ngx_http_v3_evict(c, size) != NGX_OK) { return NGX_ERROR; } - h->name = ref->name; + h3c = c->qs->parent->data; + dt = &h3c->table; - h->value.data = ngx_pstrdup(pc->pool, value); - if (h->value.data == NULL) { - h->value.len = 0; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert [%ui] \"%V\":\"%V\", size:%uz", + dt->base + dt->nelts, name, value, size); + + p = ngx_alloc(sizeof(ngx_http_v3_header_t) + name->len + value->len, + c->log); + if (p == NULL) { return NGX_ERROR; } + h = (ngx_http_v3_header_t *) p; + + h->name.data = p + sizeof(ngx_http_v3_header_t); + h->name.len = name->len; + h->value.data = ngx_cpymem(h->name.data, name->data, name->len); h->value.len = value->len; + ngx_memcpy(h->value.data, value->data, value->len); + + dt->elts[dt->nelts++] = h; + dt->size += size; + + /* TODO increment can be sent less often */ + + if (ngx_http_v3_client_inc_insert_count(c, 1) != NGX_OK) { + return NGX_ERROR; + } if (ngx_http_v3_new_header(c) != NGX_OK) { return NGX_ERROR; @@ -192,95 +237,150 @@ ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_int_t -ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value) +ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) { - ngx_array_t *dt; - ngx_connection_t *pc; - ngx_http_v3_header_t *h; + ngx_uint_t max, prev_max; + ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_header_t **elts; + ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 insert \"%V\":\"%V\"", name, value); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 set capacity %ui", capacity); pc = c->qs->parent; + h3c = pc->data; + v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); - dt = ngx_http_v3_get_dynamic_table(c); - if (dt == NULL) { + if (capacity > v3cf->max_table_capacity) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_table_capacity limit"); return NGX_ERROR; } - h = ngx_array_push(dt); - if (h == NULL) { - return NGX_ERROR; - } + dt = &h3c->table; - h->name.data = ngx_pstrdup(pc->pool, name); - if (h->name.data == NULL) { - h->name.len = 0; - h->value.len = 0; - return NGX_ERROR; + if (dt->size > capacity) { + if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { + return NGX_ERROR; + } } - h->name.len = name->len; + max = capacity / 32; + prev_max = dt->capacity / 32; - h->value.data = ngx_pstrdup(pc->pool, value); - if (h->value.data == NULL) { - h->value.len = 0; - return NGX_ERROR; - } + if (max > prev_max) { + elts = ngx_alloc(max * sizeof(void *), c->log); + if (elts == NULL) { + return NGX_ERROR; + } - h->value.len = value->len; + if (dt->elts == NULL) { + cln = ngx_pool_cleanup_add(pc->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } - if (ngx_http_v3_new_header(c) != NGX_OK) { - return NGX_ERROR; + cln->handler = ngx_http_v3_cleanup_table; + cln->data = dt; + + } else { + ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); + ngx_free(dt->elts); + } + + dt->elts = elts; } + dt->capacity = capacity; + return NGX_OK; } -ngx_int_t -ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +static void +ngx_http_v3_cleanup_table(void *data) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 set capacity %ui", capacity); + ngx_http_v3_dynamic_table_t *dt = data; - /* XXX ignore capacity */ + ngx_uint_t n; - return NGX_OK; + for (n = 0; n < dt->nelts; n++) { + ngx_free(dt->elts[n]); + } + + ngx_free(dt->elts); } -ngx_int_t -ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +static ngx_int_t +ngx_http_v3_evict(ngx_connection_t *c, size_t need) { - ngx_array_t *dt; - ngx_http_v3_header_t *ref, *h; + size_t size, target; + ngx_uint_t n; + ngx_http_v3_header_t *h; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + h3c = c->qs->parent->data; + dt = &h3c->table; - ref = ngx_http_v3_lookup_table(c, 1, index); - if (ref == NULL) { + if (need > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); return NGX_ERROR; } - dt = ngx_http_v3_get_dynamic_table(c); - if (dt == NULL) { - return NGX_ERROR; + target = dt->capacity - need; + n = 0; + + while (dt->size > target) { + h = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&h->name, &h->value); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 evict [%ui] \"%V\":\"%V\" size:%uz", + dt->base, &h->name, &h->value, size); + + ngx_free(h); + dt->size -= size; + } + + if (n) { + dt->nelts -= n; + dt->base += n; + ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); } - h = ngx_array_push(dt); - if (h == NULL) { + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_str_t name, value; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + h3c = c->qs->parent->data; + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { return NGX_ERROR; } - *h = *ref; + index = dt->base + dt->nelts - 1 - index; - if (ngx_http_v3_new_header(c) != NGX_OK) { + if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { return NGX_ERROR; } - return NGX_OK; + return ngx_http_v3_insert(c, &name, &value); } @@ -320,94 +420,237 @@ ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) } -static ngx_array_t * -ngx_http_v3_get_dynamic_table(ngx_connection_t *c) +ngx_int_t +ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value) { - ngx_connection_t *pc; - ngx_http_v3_connection_t *h3c; + ngx_uint_t nelts; + ngx_http_v3_header_t *h; - pc = c->qs->parent; - h3c = pc->data; + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); - if (h3c->dynamic) { - return h3c->dynamic; + if (index >= nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup out of bounds: %ui", + index, nelts); + return NGX_ERROR; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create dynamic table"); + h = &ngx_http_v3_static_table[index]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup \"%V\":\"%V\"", + index, &h->name, &h->value); + + if (name) { + *name = h->name; + } - h3c->dynamic = ngx_array_create(pc->pool, 1, sizeof(ngx_http_v3_header_t)); + if (value) { + *value = h->value; + } - return h3c->dynamic; + return NGX_OK; } -ngx_http_v3_header_t * -ngx_http_v3_lookup_table(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index) +ngx_int_t +ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, + ngx_str_t *value) { - ngx_uint_t nelts; - ngx_array_t *dt; - ngx_http_v3_header_t *table; + ngx_http_v3_header_t *h; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup %s[%ui]", - dynamic ? "dynamic" : "static", index); + h3c = c->qs->parent->data; + dt = &h3c->table; - if (dynamic) { - dt = ngx_http_v3_get_dynamic_table(c); - if (dt == NULL) { - return NULL; - } + if (index < dt->base || index - dt->base >= dt->nelts) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", + index, dt->base, dt->base + dt->nelts); + return NGX_ERROR; + } - table = dt->elts; - nelts = dt->nelts; + h = dt->elts[index - dt->base]; - } else { - table = ngx_http_v3_static_table; - nelts = sizeof(ngx_http_v3_static_table) - / sizeof(ngx_http_v3_static_table[0]); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup \"%V\":\"%V\"", + index, &h->name, &h->value); + + if (name) { + *name = h->name; } - if (index >= nelts) { - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 lookup out of bounds: %ui", nelts); - return NULL; + if (value) { + *value = h->value; + } + + return NGX_OK; +} + + +ngx_int_t +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 *v3cf; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + /* QPACK 4.5.1.1. Required Insert Count */ + + if (*insert_count == 0) { + return NGX_OK; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 lookup \"%V\":\"%V\"", - &table[index].name, &table[index].value); + h3c = c->qs->parent->data; + dt = &h3c->table; + + v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + + max_entries = v3cf->max_table_capacity / 32; + full_range = 2 * max_entries; + + if (*insert_count > full_range) { + return NGX_ERROR; + } + + max_value = dt->base + dt->nelts + max_entries; + max_wrapped = (max_value / full_range) * full_range; + req_insert_count = max_wrapped + *insert_count - 1; + + if (req_insert_count > max_value) { + if (req_insert_count <= full_range) { + return NGX_ERROR; + } + + req_insert_count -= full_range; + } - return &table[index]; + if (req_insert_count == 0) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decode insert_count %ui -> %ui", + *insert_count, req_insert_count); + + *insert_count = req_insert_count; + + return NGX_OK; } ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) { - size_t n; - ngx_http_v3_connection_t *h3c; + size_t n; + ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_block_t *block; + ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_connection_t *h3c; + ngx_http_v3_dynamic_table_t *dt; - h3c = c->qs->parent->data; - n = h3c->dynamic ? h3c->dynamic->nelts : 0; + pc = c->qs->parent; + h3c = pc->data; + dt = &h3c->table; + + n = dt->base + dt->nelts; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 check insert count %ui/%ui", insert_count, n); + "http3 check insert count req:%ui, have:%ui", + insert_count, n); - if (n < insert_count) { - /* XXX how to get notified? */ - /* XXX wake all streams on any arrival to the encoder stream? */ - return NGX_AGAIN; + if (n >= insert_count) { + return NGX_OK; } - return NGX_OK; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); + + block = NULL; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_v3_unblock) { + block = cln->data; + break; + } + } + + if (block == NULL) { + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_unblock; + + block = cln->data; + block->queue.prev = NULL; + block->connection = c; + block->nblocked = &h3c->nblocked; + } + + if (block->queue.prev == NULL) { + v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, + ngx_http_v3_module); + + if (h3c->nblocked == v3cf->max_blocked_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_blocked_streams limit"); + return NGX_ERROR; + } + + h3c->nblocked++; + ngx_queue_insert_tail(&h3c->blocked, &block->queue); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 blocked:%ui", h3c->nblocked); + + return NGX_BUSY; +} + + +static void +ngx_http_v3_unblock(void *data) +{ + ngx_http_v3_block_t *block = data; + + if (block->queue.prev) { + ngx_queue_remove(&block->queue); + block->queue.prev = NULL; + (*block->nblocked)--; + } } static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header"); + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; - /* XXX report all waiting streams of a new header */ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new dynamic header, blocked:%ui", h3c->nblocked); + + while (!ngx_queue_empty(&h3c->blocked)) { + q = ngx_queue_head(&h3c->blocked); + block = (ngx_http_v3_block_t *) q; + bc = block->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); + + ngx_http_v3_unblock(block); + ngx_post_event(bc->read, &ngx_posted_events); + } return NGX_OK; } -- cgit v1.2.3 From 86e287a2de91dfc0c7f6acc4220c15debd40a74d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 16:33:36 +0300 Subject: HTTP/3: downgraded literal size error level to NGX_LOG_INFO. Now it's similar to HTTP/2. --- src/http/v3/ngx_http_v3_parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bd91c5b02..b50b21618 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -446,7 +446,7 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, v3cf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); if (n > v3cf->max_field_size) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_field_size limit"); return NGX_ERROR; } -- cgit v1.2.3 From 90f5c334f1acec56b98d6a937ab140782ef4318d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 16:33:59 +0300 Subject: QUIC: Introduced ngx_quic_finalize_connection(). The function finalizes QUIC connection with an application protocol error code and sends a CONNECTION_CLOSE frame with type=0x1d. Also, renamed NGX_QUIC_FT_CONNECTION_CLOSE2 to NGX_QUIC_FT_CONNECTION_CLOSE_APP. --- src/event/ngx_event_quic.c | 103 ++++++++++++++++++++++------------- src/event/ngx_event_quic.h | 2 + src/event/ngx_event_quic_transport.c | 31 +++++++---- src/event/ngx_event_quic_transport.h | 3 +- 4 files changed, 91 insertions(+), 48 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 99a2183f5..526427c2e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -110,9 +110,11 @@ struct ngx_quic_connection_s { uint64_t max_streams; ngx_uint_t error; + enum ssl_encryption_level_t error_level; ngx_uint_t error_ftype; const char *error_reason; + unsigned error_app:1; unsigned send_timer_set:1; unsigned closing:1; unsigned draining:1; @@ -181,9 +183,7 @@ static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c, - enum ssl_encryption_level_t level, ngx_uint_t err, ngx_uint_t frame_type, - const char *reason); +static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, @@ -540,7 +540,8 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -548,13 +549,18 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, "quic ngx_quic_send_alert(), lvl=%d, alert=%d", (int) level, (int) alert); - if (c->quic == NULL) { + qc = c->quic; + if (qc == NULL) { return 1; } - if (ngx_quic_send_cc(c, level, NGX_QUIC_ERR_CRYPTO(alert), 0, "TLS alert") - != NGX_OK) - { + qc->error_level = level; + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "TLS alert"; + qc->error_app = 0; + qc->error_ftype = 0; + + if (ngx_quic_send_cc(c) != NGX_OK) { return 0; } @@ -1262,10 +1268,9 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { - ngx_uint_t i, err; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - enum ssl_encryption_level_t level; + ngx_uint_t i; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; qc = c->quic; @@ -1311,27 +1316,28 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_add_timer(&qc->close, 3 * NGX_QUIC_HARDCODED_PTO); - err = NGX_QUIC_ERR_NO_ERROR; + qc->error = NGX_QUIC_ERR_NO_ERROR; } else { - err = qc->error ? qc->error : NGX_QUIC_ERR_INTERNAL_ERROR; + if (qc->error == 0 && !qc->error_app) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to error: %ui %s", - qc->error, + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to %serror: %ui %s", + qc->error_app ? "app " : "", qc->error, qc->error_reason ? qc->error_reason : ""); } - level = c->ssl ? SSL_quic_read_level(c->ssl->connection) - : ssl_encryption_initial; + qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) + : ssl_encryption_initial; - (void) ngx_quic_send_cc(c, level, err, qc->error_ftype, - qc->error_reason); + (void) ngx_quic_send_cc(c); - if (level == ssl_encryption_handshake) { + if (qc->error_level == ssl_encryption_handshake) { /* for clients that might not have handshake keys */ - (void) ngx_quic_send_cc(c, ssl_encryption_initial, err, - qc->error_ftype, qc->error_reason); + qc->error_level = ssl_encryption_initial; + (void) ngx_quic_send_cc(c); } } @@ -1382,6 +1388,22 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) } +void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = c->quic; + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + qc->error_ftype = 0; + + ngx_quic_close_connection(c, NGX_ERROR); +} + + static void ngx_quic_close_timer_handler(ngx_event_t *ev) { @@ -1887,8 +1909,14 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) * a packet containing a CONNECTION_CLOSE frame and to identify * packets as belonging to the connection. */ - return ngx_quic_send_cc(c, pkt->level, NGX_QUIC_ERR_NO_ERROR, 0, - "connection is closing, packet discarded"); + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); } p = pkt->payload.data; @@ -1926,7 +1954,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) continue; case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE2: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: do_close = 1; continue; } @@ -2098,8 +2126,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, - ngx_uint_t err, ngx_uint_t frame_type, const char *reason) +ngx_quic_send_cc(ngx_connection_t *c) { ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -2122,19 +2149,21 @@ ngx_quic_send_cc(ngx_connection_t *c, enum ssl_encryption_level_t level, return NGX_ERROR; } - frame->level = level; + frame->level = qc->error_level; frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; - frame->u.close.error_code = err; - frame->u.close.frame_type = frame_type; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; + frame->u.close.app = qc->error_app; - if (reason) { - frame->u.close.reason.len = ngx_strlen(reason); - frame->u.close.reason.data = (u_char *) reason; + if (qc->error_reason) { + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; } ngx_snprintf(frame->info, sizeof(frame->info) - 1, - "cc from send_cc err=%ui level=%d ft=%ui reason \"%s\"", - err, level, frame_type, reason ? reason : "-"); + "CONNECTION_CLOSE%s err:%ui level:%d ft:%ui reason:\"%s\"", + qc->error_app ? "_APP" : "", qc->error, qc->error_level, + qc->error_ftype, qc->error_reason ? qc->error_reason : "-"); ngx_quic_queue_frame(c->quic, frame); diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 8974a3392..02ee03869 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -103,6 +103,8 @@ struct ngx_quic_stream_s { void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); +void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); /********************************* DEBUG *************************************/ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index b7cb7eee3..c7c8c46f2 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -726,10 +726,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; - case NGX_QUIC_FT_CONNECTION_CLOSE2: - /* fall through */ - case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: p = ngx_quic_parse_int(p, end, &f->u.close.error_code); if (p == NULL) { @@ -767,7 +765,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } else { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: CONNECTION_CLOSE2:" + "quic frame in: CONNECTION_CLOSE_APP:" " code:0x%xi reason:'%V'", f->u.close.error_code, &f->u.close.reason); } @@ -1174,6 +1172,7 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) return ngx_quic_create_stream(p, &f->u.stream); case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: f->need_ack = 0; return ngx_quic_create_close(p, &f->u.close); @@ -1742,13 +1741,21 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_uint_t type; + + type = cl->app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_CONNECTION_CLOSE); + len = ngx_quic_varint_len(type); len += ngx_quic_varint_len(cl->error_code); - len += ngx_quic_varint_len(cl->frame_type); + + if (!cl->app) { + len += ngx_quic_varint_len(cl->frame_type); + } + len += ngx_quic_varint_len(cl->reason.len); len += cl->reason.len; @@ -1757,9 +1764,13 @@ ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) start = p; - ngx_quic_build_int(&p, NGX_QUIC_FT_CONNECTION_CLOSE); + ngx_quic_build_int(&p, type); ngx_quic_build_int(&p, cl->error_code); - ngx_quic_build_int(&p, cl->frame_type); + + if (!cl->app) { + ngx_quic_build_int(&p, cl->frame_type); + } + ngx_quic_build_int(&p, cl->reason.len); p = ngx_cpymem(p, cl->reason.data, cl->reason.len); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 902db3caf..8aaec1bb0 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -71,7 +71,7 @@ #define NGX_QUIC_FT_PATH_CHALLENGE 0x1A #define NGX_QUIC_FT_PATH_RESPONSE 0x1B #define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C -#define NGX_QUIC_FT_CONNECTION_CLOSE2 0x1D +#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E /* 22.4. QUIC Transport Error Codes Registry */ @@ -185,6 +185,7 @@ typedef struct { uint64_t error_code; uint64_t frame_type; ngx_str_t reason; + ngx_uint_t app; /* unsigned app:1; */ } ngx_quic_close_frame_t; -- cgit v1.2.3 From fd35d92232ca2332125fe9b29e53aaec33aa2176 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 30 Jun 2020 12:30:57 +0300 Subject: HTTP/3: error code definitions for HTTP/3 and QPACK. --- src/http/v3/ngx_http_v3.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index b51febcdd..d74939fd3 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -51,6 +51,29 @@ #define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 #define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 +/* HTTP/3 errors */ +#define NGX_HTTP_V3_ERR_NO_ERROR 0x100 +#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 +#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102 +#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103 +#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104 +#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105 +#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106 +#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107 +#define NGX_HTTP_V3_ERR_ID_ERROR 0x108 +#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109 +#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a +#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b +#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c +#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d +#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f +#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110 + +/* QPACK errors */ +#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 +#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 +#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 + #define ngx_http_v3_get_module_srv_conf(c, module) \ ngx_http_get_module_srv_conf( \ -- cgit v1.2.3 From 707117276ed252e39c75769a140cbac6e18eb74a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 16:47:51 +0300 Subject: HTTP/3: close QUIC connection with HTTP/QPACK errors when needed. Previously errors led only to closing streams. To simplify closing QUIC connection from a QUIC stream context, new macro ngx_http_v3_finalize_connection() is introduced. It calls ngx_quic_finalize_connection() for the parent connection. --- src/http/ngx_http_request.c | 12 +- src/http/v3/ngx_http_v3.h | 3 + src/http/v3/ngx_http_v3_parse.c | 233 +++++++++++++++++++++----------------- src/http/v3/ngx_http_v3_parse.h | 10 ++ src/http/v3/ngx_http_v3_request.c | 18 +++ src/http/v3/ngx_http_v3_streams.c | 58 +++++++--- src/http/v3/ngx_http_v3_tables.c | 22 ++-- 7 files changed, 220 insertions(+), 136 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 89e554bf2..c953386d4 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -416,19 +416,21 @@ static void ngx_http_quic_stream_handler(ngx_connection_t *c) { ngx_event_t *rev; - ngx_connection_t *pc; ngx_http_log_ctx_t *ctx; ngx_http_connection_t *hc; ngx_http_v3_connection_t *h3c; - pc = c->qs->parent; - h3c = pc->data; + h3c = c->qs->parent->data; if (!h3c->settings_sent) { h3c->settings_sent = 1; - /* TODO close QUIC connection on error */ - (void) ngx_http_v3_send_settings(c); + if (ngx_http_v3_send_settings(c) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "could not send settings"); + ngx_http_close_connection(c); + return; + } } if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index d74939fd3..d4ea19aed 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -80,6 +80,9 @@ ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \ module) +#define ngx_http_v3_finalize_connection(c, code, reason) \ + ngx_quic_finalize_connection(c->qs->parent, code, reason) + typedef struct { ngx_quic_tp_t quic; diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index b50b21618..27484e92a 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -160,7 +160,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers"); if (ch != NGX_HTTP_V3_FRAME_HEADERS) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } st->state = sw_length; @@ -183,21 +183,21 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, case sw_prefix: if (st->length-- == 0) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_FRAME_ERROR; } rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; + if (rc == NGX_AGAIN) { + break; } if (rc != NGX_DONE) { - break; + return rc; } if (st->length == 0) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_FRAME_ERROR; } st->state = sw_verify; @@ -218,24 +218,25 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base, ch); + st->length--; - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (--st->length == 0) { - if (rc != NGX_DONE) { - return NGX_ERROR; + if (rc == NGX_AGAIN) { + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; } - goto done; + break; } - if (rc == NGX_DONE) { - return NGX_OK; + if (rc != NGX_DONE) { + return rc; } - break; + if (st->length == 0) { + goto done; + } + + return NGX_OK; } return NGX_AGAIN; @@ -259,6 +260,7 @@ ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, ngx_http_v3_parse_header_block_prefix_t *st, u_char ch) { + ngx_int_t rc; enum { sw_start = 0, sw_req_insert_count, @@ -308,8 +310,9 @@ ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, done: - if (ngx_http_v3_decode_insert_count(c, &st->insert_count) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); + if (rc != NGX_OK) { + return rc; } if (st->sign) { @@ -404,16 +407,10 @@ ngx_http_v3_parse_header_rep(ngx_connection_t *c, rc = NGX_OK; } - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_AGAIN) { - return NGX_AGAIN; + if (rc != NGX_DONE) { + return rc; } - /* rc == NGX_DONE */ - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header representation done"); @@ -448,7 +445,7 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, if (n > v3cf->max_field_size) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_field_size limit"); - return NGX_ERROR; + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; } if (st->huffman) { @@ -505,6 +502,7 @@ ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { + ngx_int_t rc; enum { sw_start = 0, sw_index @@ -543,11 +541,10 @@ done: st->index = st->base - st->index - 1; } - if (ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, - &st->value) - != NGX_OK) - { - return NGX_ERROR; + 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; @@ -614,15 +611,15 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->value = st->literal.value; goto done; } + if (rc != NGX_AGAIN) { + return rc; + } + break; } @@ -639,10 +636,9 @@ done: st->index = st->base - st->index - 1; } - if (ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL) - != NGX_OK) - { - return NGX_ERROR; + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; } st->state = sw_start; @@ -693,13 +689,14 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->name = st->literal.value; st->state = sw_value_len; + break; + } + + if (rc != NGX_AGAIN) { + return rc; } break; @@ -729,15 +726,15 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->value = st->literal.value; goto done; } + if (rc != NGX_AGAIN) { + return rc; + } + break; } @@ -758,6 +755,7 @@ ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { + ngx_int_t rc; enum { sw_start = 0, sw_index @@ -790,11 +788,10 @@ done: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi done dynamic[+%ui]", st->index); - if (ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, - &st->value) - != NGX_OK) - { - return NGX_ERROR; + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; } st->state = sw_start; @@ -861,15 +858,15 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->value = st->literal.value; goto done; } + if (rc != NGX_AGAIN) { + return rc; + } + break; } @@ -881,10 +878,9 @@ done: "http3 parse header lpbi done dynamic[+%ui] \"%V\"", st->index, &st->value); - if (ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL) - != NGX_OK) - { - return NGX_ERROR; + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; } st->state = sw_start; @@ -899,11 +895,15 @@ ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, u_char *p; if (!dynamic) { - return ngx_http_v3_lookup_static(c, index, name, value); + 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_ERROR; + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } if (name) { @@ -940,6 +940,7 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) ngx_int_t rc; enum { sw_start = 0, + sw_first_type, sw_type, sw_length, sw_settings, @@ -953,10 +954,11 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse control"); - st->state = sw_type; + st->state = sw_first_type; /* fall through */ + case sw_first_type: case sw_type: if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { @@ -968,6 +970,12 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse frame type:%ui", st->type); + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } + st->state = sw_length; break; @@ -1008,19 +1016,24 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) rc = ngx_http_v3_parse_settings(c, &st->settings, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } + st->length--; + + if (rc == NGX_AGAIN) { + if (st->length == 0) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } - if (--st->length > 0) { break; } if (rc != NGX_DONE) { - return NGX_ERROR; + return rc; + } + + if (st->length == 0) { + st->state = sw_type; } - st->state = sw_type; break; case sw_max_push_id: @@ -1085,7 +1098,7 @@ ngx_http_v3_parse_settings(ngx_connection_t *c, } if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; } goto done; @@ -1149,12 +1162,12 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) rc = ngx_http_v3_parse_header_inr(c, &st->header, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; + if (rc == NGX_AGAIN) { + break; } if (rc != NGX_DONE) { - break; + return rc; } goto done; @@ -1163,12 +1176,12 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; + if (rc == NGX_AGAIN) { + break; } if (rc != NGX_DONE) { - break; + return rc; } goto done; @@ -1179,8 +1192,9 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) break; } - if (ngx_http_v3_set_capacity(c, st->pint.value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_set_capacity(c, st->pint.value); + if (rc != NGX_OK) { + return rc; } goto done; @@ -1191,8 +1205,9 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) break; } - if (ngx_http_v3_duplicate(c, st->pint.value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_duplicate(c, st->pint.value); + if (rc != NGX_OK) { + return rc; } goto done; @@ -1269,15 +1284,15 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->value = st->literal.value; goto done; } + if (rc != NGX_AGAIN) { + return rc; + } + break; } @@ -1290,9 +1305,9 @@ done: st->dynamic ? "dynamic" : "static", st->index, &st->value); - if (ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value) != NGX_OK) - { - return NGX_ERROR; + rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); + if (rc != NGX_OK) { + return rc; } st->state = sw_start; @@ -1344,13 +1359,14 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->name = st->literal.value; st->state = sw_value_len; + break; + } + + if (rc != NGX_AGAIN) { + return rc; } break; @@ -1380,15 +1396,15 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - if (rc == NGX_DONE) { st->value = st->literal.value; goto done; } + if (rc != NGX_AGAIN) { + return rc; + } + break; } @@ -1400,8 +1416,9 @@ done: "http3 parse header iwnr done \"%V\":\"%V\"", &st->name, &st->value); - if (ngx_http_v3_insert(c, &st->name, &st->value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_insert(c, &st->name, &st->value); + if (rc != NGX_OK) { + return rc; } st->state = sw_start; @@ -1414,6 +1431,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) { ngx_http_v3_parse_decoder_t *st = data; + ngx_int_t rc; enum { sw_start = 0, sw_ack_header, @@ -1451,8 +1469,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) break; } - if (ngx_http_v3_ack_header(c, st->pint.value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_ack_header(c, st->pint.value); + if (rc != NGX_OK) { + return rc; } goto done; @@ -1463,8 +1482,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) break; } - if (ngx_http_v3_cancel_stream(c, st->pint.value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_cancel_stream(c, st->pint.value); + if (rc != NGX_OK) { + return rc; } goto done; @@ -1475,8 +1495,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) break; } - if (ngx_http_v3_inc_insert_count(c, st->pint.value) != NGX_OK) { - return NGX_ERROR; + rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + if (rc != NGX_OK) { + return rc; } goto done; @@ -1521,7 +1542,7 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, } if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } st->state = sw_length; diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index 17ff6c777..0c0af33b7 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -112,6 +112,16 @@ typedef struct { } ngx_http_v3_parse_data_t; +/* + * Parse functions return codes: + * NGX_DONE - parsing done + * NGX_OK - sub-element done + * NGX_AGAIN - more data expected + * NGX_BUSY - waiting for external event + * NGX_ERROR - internal error + * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error + */ + ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, u_char ch); ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ae65ba9ea..0ffa8927d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -66,6 +66,12 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request headers"); + goto failed; + } + if (rc == NGX_ERROR) { goto failed; } @@ -180,6 +186,12 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request headers"); + return NGX_HTTP_PARSE_INVALID_HEADER; + } + if (rc == NGX_ERROR) { return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -359,6 +371,12 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, while (b->pos < b->last) { rc = ngx_http_v3_parse_data(c, st, *b->pos++); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request body"); + goto failed; + } + if (rc == NGX_ERROR) { goto failed; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 229ce5bf4..8eaa7fde6 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -39,6 +39,8 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); ngx_http_v3_close_uni_stream(c); return; } @@ -85,7 +87,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) { u_char ch; ssize_t n; - ngx_int_t index; + ngx_int_t index, rc; ngx_connection_t *c; ngx_http_v3_connection_t *h3c; ngx_http_v3_uni_stream_t *us; @@ -100,12 +102,18 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) n = c->recv(c, &ch, 1); - if (n == NGX_ERROR) { + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; goto failed; } - if (n == NGX_AGAIN || n != 1) { - break; + if (n != 1) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; } switch (ch) { @@ -154,6 +162,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) if (index >= 0) { if (h3c->known_streams[index]) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + rc = NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; goto failed; } @@ -164,6 +173,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) if (n) { us->data = ngx_pcalloc(c->pool, n); if (us->data == NULL) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; goto failed; } } @@ -174,6 +184,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) } if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; goto failed; } @@ -181,6 +192,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) failed: + ngx_http_v3_finalize_connection(c, rc, "could not read stream type"); ngx_http_v3_close_uni_stream(c); } @@ -203,10 +215,22 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) n = c->recv(c, buf, sizeof(buf)); - if (n == NGX_ERROR || n == 0) { + if (n == NGX_ERROR) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; goto failed; } + if (n == 0) { + if (us->index >= 0) { + rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); + ngx_http_v3_close_uni_stream(c); + return; + } + if (n == NGX_AGAIN) { break; } @@ -219,30 +243,34 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) rc = us->handler(c, us->data, buf[i]); - if (rc == NGX_ERROR) { - goto failed; + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; } - if (rc == NGX_DONE) { - goto done; + if (rc > 0) { + goto failed; } - /* rc == NGX_AGAIN */ + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; + } } } if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; goto failed; } return; -done: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read done"); - failed: + ngx_http_v3_finalize_connection(c, rc, "stream error"); ngx_http_v3_close_uni_stream(c); } @@ -257,6 +285,8 @@ ngx_http_v3_dummy_write_handler(ngx_event_t *wev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); ngx_http_v3_close_uni_stream(c); } } diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index a57eccb9f..446601b01 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -167,7 +167,7 @@ ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, "http3 ref insert dynamic[%ui] \"%V\"", index, value); if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } } else { @@ -175,7 +175,7 @@ ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, "http3 ref insert static[%ui] \"%V\"", index, value); if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } } @@ -195,7 +195,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) size = ngx_http_v3_table_entry_size(name, value); if (ngx_http_v3_evict(c, size) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } h3c = c->qs->parent->data; @@ -257,14 +257,14 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) if (capacity > v3cf->max_table_capacity) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_table_capacity limit"); - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } dt = &h3c->table; if (dt->size > capacity) { if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } } @@ -371,13 +371,13 @@ ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) dt = &h3c->table; if (dt->base + dt->nelts <= index) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } index = dt->base + dt->nelts - 1 - index; if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } return ngx_http_v3_insert(c, &name, &value); @@ -515,7 +515,7 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) full_range = 2 * max_entries; if (*insert_count > full_range) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } max_value = dt->base + dt->nelts + max_entries; @@ -524,14 +524,14 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) if (req_insert_count > max_value) { if (req_insert_count <= full_range) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } req_insert_count -= full_range; } if (req_insert_count == 0) { - return NGX_ERROR; + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -601,7 +601,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) if (h3c->nblocked == v3cf->max_blocked_streams) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_blocked_streams limit"); - return NGX_ERROR; + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } h3c->nblocked++; -- cgit v1.2.3 From d839fee75f5247c160c0b7b927dc45a3122cb6a2 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 30 Jun 2020 15:32:09 +0300 Subject: HTTP/3: set r->headers_in.chunked flag after parsing headers. Previously it was set when creating the request object. The side-effect was trying to discard the request body in case of header parse error. --- src/http/ngx_http_request.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index c953386d4..8933bb3c3 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -727,7 +727,6 @@ ngx_http_alloc_request(ngx_connection_t *c) #if (NGX_HTTP_V3) if (hc->quic) { r->http_version = NGX_HTTP_VERSION_30; - r->headers_in.chunked = 1; } #endif @@ -2155,6 +2154,12 @@ ngx_http_process_request_header(ngx_http_request_t *r) } } +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + r->headers_in.chunked = 1; + } +#endif + if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) { if (r->headers_in.keep_alive) { r->headers_in.keep_alive_n = -- cgit v1.2.3 From 0ebcffcf1409a03d2437ad18d65387448382620d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 2 Jul 2020 17:35:57 +0300 Subject: HTTP/3: fixed prefix in decoding Section Acknowledgement. --- src/http/v3/ngx_http_v3_parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 27484e92a..da9826ced 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1465,7 +1465,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) case sw_ack_header: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { + if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { break; } -- cgit v1.2.3 From 3b2eabde0bedc9a1a7d1b53bdbc28bdc14773dd1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 3 Jul 2020 16:41:31 +0300 Subject: HTTP/3: fixed overflow in prefixed integer parser. Previously, the expression (ch & 0x7f) was promoted to a signed integer. Depending on the platform, the size of this integer could be less than 8 bytes, leading to overflow when handling the higher bits of the result. Also, sign bit of this integer could be replicated when adding to the 64-bit st->value. --- src/http/v3/ngx_http_v3_parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index da9826ced..bb8d73296 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -118,7 +118,7 @@ ngx_http_v3_parse_prefix_int(ngx_connection_t *c, case sw_value: - st->value += (ch & 0x7f) << st->shift; + st->value += (uint64_t) (ch & 0x7f) << st->shift; if (ch & 0x80) { st->shift += 7; break; -- cgit v1.2.3 From 3a9874c4ab00d443c66e175502466b0105e8f3be Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 3 Jul 2020 09:26:12 +0300 Subject: HTTP/3: limited prefixed integer size by 62 bits. --- src/http/v3/ngx_http_v3_parse.c | 104 +++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bb8d73296..8b1f745fe 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -119,6 +119,15 @@ ngx_http_v3_parse_prefix_int(ngx_connection_t *c, case sw_value: st->value += (uint64_t) (ch & 0x7f) << st->shift; + + 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; + } + if (ch & 0x80) { st->shift += 7; break; @@ -281,8 +290,9 @@ ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, case sw_req_insert_count: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch); + if (rc != NGX_DONE) { + return rc; } st->insert_count = st->pint.value; @@ -298,8 +308,9 @@ ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, case sw_read_delta_base: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->delta_base = st->pint.value; @@ -521,8 +532,9 @@ ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, case sw_index: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); + if (rc != NGX_DONE) { + return rc; } st->index = st->pint.value; @@ -578,8 +590,9 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, case sw_index: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch); + if (rc != NGX_DONE) { + return rc; } st->index = st->pint.value; @@ -595,8 +608,9 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, case sw_read_value_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -673,8 +687,9 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, case sw_name_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -710,8 +725,9 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, case sw_read_value_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -773,8 +789,9 @@ ngx_http_v3_parse_header_pbi(ngx_connection_t *c, case sw_index: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch); + if (rc != NGX_DONE) { + return rc; } st->index = st->pint.value; @@ -825,8 +842,9 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, case sw_index: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch); + if (rc != NGX_DONE) { + return rc; } st->index = st->pint.value; @@ -842,8 +860,9 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, case sw_read_value_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -1188,8 +1207,9 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) case sw_capacity: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); + if (rc != NGX_DONE) { + return rc; } rc = ngx_http_v3_set_capacity(c, st->pint.value); @@ -1201,8 +1221,9 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) case sw_duplicate: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); + if (rc != NGX_DONE) { + return rc; } rc = ngx_http_v3_duplicate(c, st->pint.value); @@ -1251,8 +1272,9 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, case sw_name_index: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); + if (rc != NGX_DONE) { + return rc; } st->index = st->pint.value; @@ -1268,8 +1290,9 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, case sw_read_value_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -1343,8 +1366,9 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, case sw_name_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -1380,8 +1404,9 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, case sw_read_value_len: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } st->literal.length = st->pint.value; @@ -1465,8 +1490,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) case sw_ack_header: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); + if (rc != NGX_DONE) { + return rc; } rc = ngx_http_v3_ack_header(c, st->pint.value); @@ -1478,8 +1504,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) case sw_cancel_stream: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); + if (rc != NGX_DONE) { + return rc; } rc = ngx_http_v3_cancel_stream(c, st->pint.value); @@ -1491,8 +1518,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) case sw_inc_insert_count: - if (ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); + if (rc != NGX_DONE) { + return rc; } rc = ngx_http_v3_inc_insert_count(c, st->pint.value); -- cgit v1.2.3 From 8d1875d39ab60a6494a1d3b4be34fc35213d74df Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 3 Jul 2020 12:05:05 +0300 Subject: HTTP/3: simplifed handling ngx_http_v3_parse_literal() return code. --- src/http/v3/ngx_http_v3_parse.c | 77 +++++++++++------------------------------ 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 8b1f745fe..4e0532781 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -624,17 +624,12 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, case sw_value: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->value = st->literal.value; - goto done; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } - break; + st->value = st->literal.value; + goto done; } return NGX_AGAIN; @@ -703,17 +698,12 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, case sw_name: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->name = st->literal.value; - st->state = sw_value_len; - break; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } + st->name = st->literal.value; + st->state = sw_value_len; break; case sw_value_len: @@ -741,17 +731,12 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, case sw_value: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->value = st->literal.value; - goto done; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } - break; + st->value = st->literal.value; + goto done; } return NGX_AGAIN; @@ -876,17 +861,12 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, case sw_value: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->value = st->literal.value; - goto done; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } - break; + st->value = st->literal.value; + goto done; } return NGX_AGAIN; @@ -1306,17 +1286,12 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, case sw_value: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->value = st->literal.value; - goto done; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } - break; + st->value = st->literal.value; + goto done; } return NGX_AGAIN; @@ -1382,17 +1357,12 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, case sw_name: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->name = st->literal.value; - st->state = sw_value_len; - break; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } + st->name = st->literal.value; + st->state = sw_value_len; break; case sw_value_len: @@ -1420,17 +1390,12 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, case sw_value: rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - - if (rc == NGX_DONE) { - st->value = st->literal.value; - goto done; - } - - if (rc != NGX_AGAIN) { + if (rc != NGX_DONE) { return rc; } - break; + st->value = st->literal.value; + goto done; } return NGX_AGAIN; -- cgit v1.2.3 From ac9c1dcad8becffd188321600ff4edd2d49dcca6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 3 Jul 2020 12:07:43 +0300 Subject: HTTP/3: put ngx_http_v3_parse_varlen_int() return code in variable. This makes calling this function similar to other parse functions. --- src/http/v3/ngx_http_v3_parse.c | 42 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 4e0532781..e1e9444e1 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -177,8 +177,9 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, case sw_length: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } st->length = st->vlint.value; @@ -960,8 +961,9 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) case sw_first_type: case sw_type: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } st->type = st->vlint.value; @@ -980,8 +982,9 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) case sw_length: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1037,8 +1040,9 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) case sw_max_push_id: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1064,6 +1068,7 @@ ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, ngx_http_v3_parse_settings_t *st, u_char ch) { + ngx_int_t rc; enum { sw_start = 0, sw_id, @@ -1082,8 +1087,9 @@ ngx_http_v3_parse_settings(ngx_connection_t *c, case sw_id: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } st->id = st->vlint.value; @@ -1092,8 +1098,9 @@ ngx_http_v3_parse_settings(ngx_connection_t *c, case sw_value: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { @@ -1512,6 +1519,7 @@ ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, u_char ch) { + ngx_int_t rc; enum { sw_start = 0, sw_type, @@ -1530,8 +1538,9 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, case sw_type: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) { @@ -1543,8 +1552,9 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, case sw_length: - if (ngx_http_v3_parse_varlen_int(c, &st->vlint, ch) != NGX_DONE) { - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } st->length = st->vlint.value; -- cgit v1.2.3 From 3db00b4da74ddc0872be4cdb836aaff7fc21272a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 20:07:24 +0300 Subject: HTTP/3: simplified handling return codes from parse functions. --- src/http/v3/ngx_http_v3_parse.c | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index e1e9444e1..bb52c9e94 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -197,11 +197,6 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, } rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch); - - if (rc == NGX_AGAIN) { - break; - } - if (rc != NGX_DONE) { return rc; } @@ -228,14 +223,9 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base, ch); - st->length--; - - if (rc == NGX_AGAIN) { - if (st->length == 0) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - break; + if (--st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; } if (rc != NGX_DONE) { @@ -1018,14 +1008,8 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) rc = ngx_http_v3_parse_settings(c, &st->settings, ch); - st->length--; - - if (rc == NGX_AGAIN) { - if (st->length == 0) { - return NGX_HTTP_V3_ERR_SETTINGS_ERROR; - } - - break; + if (--st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; } if (rc != NGX_DONE) { @@ -1167,11 +1151,6 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) case sw_inr: rc = ngx_http_v3_parse_header_inr(c, &st->header, ch); - - if (rc == NGX_AGAIN) { - break; - } - if (rc != NGX_DONE) { return rc; } @@ -1181,11 +1160,6 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) case sw_iwnr: rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch); - - if (rc == NGX_AGAIN) { - break; - } - if (rc != NGX_DONE) { return rc; } -- cgit v1.2.3 From 32fd0a7b44ae3a151120dd55ea8a989845ada557 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Jul 2020 15:44:06 +0300 Subject: QUIC: added rtt estimation. According to the quic-recovery 29, Section 5: Estimating the Round-Trip Time. Currently, integer arithmetics is used, which loses sub-millisecond accuracy. --- src/event/ngx_event_quic.c | 102 +++++++++++++++++++++++++++++++++++++++++---- src/event/ngx_event_quic.h | 3 ++ 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 526427c2e..4f380658d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -99,6 +99,11 @@ struct ngx_quic_connection_s { ngx_queue_t free_frames; ngx_msec_t last_cc; + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + #if (NGX_DEBUG) ngx_uint_t nframes; #endif @@ -189,7 +194,10 @@ static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max); + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_msec_t *send_time); +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f); @@ -665,6 +673,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, ngx_queue_init(&qc->free_frames); + qc->avg_rtt = NGX_QUIC_INITIAL_RTT; + qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; + qc->min_rtt = NGX_TIMER_INFINITE; + + /* + * qc->latest_rtt = 0 + */ + qc->retransmit.log = c->log; qc->retransmit.data = c; qc->retransmit.handler = ngx_quic_retransmit_handler; @@ -2210,6 +2226,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ssize_t n; u_char *pos, *end; uint64_t gap, range; + ngx_msec_t send_time; ngx_uint_t i, min, max; ngx_quic_send_ctx_t *ctx; @@ -2234,7 +2251,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, min = ack->largest - ack->first_range; max = ack->largest; - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max) != NGX_OK) { + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { return NGX_ERROR; } @@ -2243,6 +2262,18 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic updated largest received ack: %ui", max); + + /* + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time); + } } pos = ack->ranges_start; @@ -2274,7 +2305,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, min = max - range + 1; - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max) != NGX_OK) { + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { return NGX_ERROR; } } @@ -2285,8 +2318,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t min, uint64_t max) + uint64_t min, uint64_t max, ngx_msec_t *send_time) { + uint64_t found_num; ngx_uint_t found; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -2294,26 +2328,30 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, qc = c->quic; + *send_time = NGX_TIMER_INFINITE; found = 0; + found_num = 0; - q = ngx_queue_head(&ctx->sent); + q = ngx_queue_last(&ctx->sent); while (q != ngx_queue_sentinel(&ctx->sent)) { f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_prev(q); if (f->pnum >= min && f->pnum <= max) { ngx_quic_congestion_ack(c, f); ngx_quic_handle_stream_ack(c, f); - q = ngx_queue_next(q); + if (f->pnum > found_num || !found) { + *send_time = f->last; + found_num = f->pnum; + } + ngx_queue_remove(&f->queue); ngx_quic_free_frame(c, f); found = 1; - - } else { - q = ngx_queue_next(q); } } @@ -2342,6 +2380,52 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = c->quic; + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + + if (level == ssl_encryption_application) { + ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + + } else { + ack_delay = 0; + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample: latest %M, min %M, avg %M, var %M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 02ee03869..3ff54b2d4 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -41,6 +41,9 @@ #define NGX_QUIC_MAX_TOKEN_SIZE 32 /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ +/* quic-recovery, section 6.2.2, kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + #define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */ #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ -- cgit v1.2.3 From 732720f3caf018b154c471114339d2a4d0e6c79b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 10 Jul 2020 15:33:51 +0300 Subject: QUIC: delay field of an ACK frame is now calculated. --- src/event/ngx_event_quic.c | 25 +++++++++++++++++++++++++ src/event/ngx_event_quic_transport.c | 4 ++-- src/event/ngx_event_quic_transport.h | 1 + 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4f380658d..62598d341 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -188,6 +188,8 @@ static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, + struct timeval *received, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); @@ -1877,6 +1879,8 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } + ngx_gettimeofday(&pkt->received); + /* switch keys on Key Phase change */ if (pkt->key_update) { @@ -2132,6 +2136,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) frame->type = NGX_QUIC_FT_ACK; frame->u.ack.largest = pkt->pn; + frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level); ngx_sprintf(frame->info, "ACK for PN=%d from frame handler level=%d", pkt->pn, frame->level); @@ -2141,6 +2146,26 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, + enum ssl_encryption_level_t level) +{ + ngx_int_t ack_delay; + struct timeval tv; + + ack_delay = 0; + + if (level == ssl_encryption_application) { + ngx_gettimeofday(&tv); + ack_delay = (tv.tv_sec - received->tv_sec) * 1000000 + + tv.tv_usec - received->tv_usec; + ack_delay >>= c->quic->ctp.ack_delay_exponent; + } + + return ack_delay; +} + + static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index c7c8c46f2..7f9e49ff0 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1203,7 +1203,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); len += ngx_quic_varint_len(ack->largest); - len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->delay); len += ngx_quic_varint_len(0); len += ngx_quic_varint_len(ack->first_range); @@ -1214,7 +1214,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ngx_quic_build_int(&p, ack->largest); - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->delay); ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, ack->first_range); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 8aaec1bb0..661ffede1 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -283,6 +283,7 @@ typedef struct { struct ngx_quic_secret_s *secret; struct ngx_quic_secret_s *next; + struct timeval received; uint64_t number; uint8_t num_len; uint32_t trunc; -- cgit v1.2.3 From d7ab1bfb7c21aa51ba9ab48b251460291c6be679 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Jul 2020 10:07:20 +0300 Subject: QUIC: caching c->quic in the ngx_quic_handle_ack_frame() function. To minimize difference with the following changes. --- src/event/ngx_event_quic.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 62598d341..5b9593e58 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2248,14 +2248,17 @@ static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *ack) { - ssize_t n; - u_char *pos, *end; - uint64_t gap, range; - ngx_msec_t send_time; - ngx_uint_t i, min, max; - ngx_quic_send_ctx_t *ctx; + ssize_t n; + u_char *pos, *end; + uint64_t gap, range; + ngx_msec_t send_time; + ngx_uint_t i, min, max; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; - ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + qc = c->quic; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_handle_ack_frame level %d", pkt->level); @@ -2267,7 +2270,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, */ if (ack->first_range > ack->largest) { - c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid first range in ack frame"); return NGX_ERROR; @@ -2313,7 +2316,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, pos += n; if (gap >= min) { - c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; @@ -2322,7 +2325,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, max = min - 1 - gap; if (range > max + 1) { - c->quic->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; -- cgit v1.2.3 From 395ec440299ed36eab8f97a96ade729c1e1ab068 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Jul 2020 10:07:15 +0300 Subject: QUIC: renaming. The c->quic->retransmit timer is now called "pto". The ngx_quic_retransmit() function is renamed to "ngx_quic_detect_lost()". This is a preparation for the following patches. --- src/event/ngx_event_quic.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5b9593e58..da2c3edeb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -94,7 +94,7 @@ struct ngx_quic_connection_s { ngx_ssl_t *ssl; ngx_event_t push; - ngx_event_t retransmit; + ngx_event_t pto; ngx_event_t close; ngx_queue_t free_frames; ngx_msec_t last_cc; @@ -245,8 +245,8 @@ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); -static void ngx_quic_retransmit_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_retransmit(ngx_connection_t *c, +static void ngx_quic_pto_handler(ngx_event_t *ev); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_msec_t *waitp); static void ngx_quic_push_handler(ngx_event_t *ev); @@ -683,10 +683,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, * qc->latest_rtt = 0 */ - qc->retransmit.log = c->log; - qc->retransmit.data = c; - qc->retransmit.handler = ngx_quic_retransmit_handler; - qc->retransmit.cancelable = 1; + qc->pto.log = c->log; + qc->pto.data = c; + qc->pto.handler = ngx_quic_pto_handler; + qc->pto.cancelable = 1; qc->push.log = c->log; qc->push.data = c; @@ -1388,8 +1388,8 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_del_timer(&qc->push); } - if (qc->retransmit.timer_set) { - ngx_del_timer(&qc->retransmit); + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); } if (qc->push.posted) { @@ -3260,9 +3260,8 @@ ngx_quic_output(ngx_connection_t *c) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - if (!qc->retransmit.timer_set && !qc->closing) { - ngx_add_timer(&qc->retransmit, - qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO); + if (!qc->pto.timer_set && !qc->closing) { + ngx_add_timer(&qc->pto, qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO); } return NGX_OK; @@ -3543,15 +3542,14 @@ ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) static void -ngx_quic_retransmit_handler(ngx_event_t *ev) +ngx_quic_pto_handler(ngx_event_t *ev) { ngx_uint_t i; ngx_msec_t wait, nswait; ngx_connection_t *c; ngx_quic_connection_t *qc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "quic retransmit timer"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); c = ev->data; qc = c->quic; @@ -3559,7 +3557,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) wait = 0; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ngx_quic_retransmit(c, &qc->send_ctx[i], &nswait) != NGX_OK) { + if (ngx_quic_detect_lost(c, &qc->send_ctx[i], &nswait) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -3573,7 +3571,7 @@ ngx_quic_retransmit_handler(ngx_event_t *ev) } if (wait > 0) { - ngx_add_timer(&qc->retransmit, wait); + ngx_add_timer(&qc->pto, wait); } } @@ -3595,7 +3593,7 @@ ngx_quic_push_handler(ngx_event_t *ev) static ngx_int_t -ngx_quic_retransmit(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, +ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_msec_t *waitp) { uint64_t pn; -- cgit v1.2.3 From 230882ba50a73b162002135af4c2df8e4c018ed3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 15 Jul 2020 15:10:17 +0300 Subject: QUIC: reworked ngx_quic_send_frames() function. Instead of returning NGX_DONE/NGX_OK, the function now itself moves passed frames range into sent queue and sets PTO timer if required. --- src/event/ngx_event_quic.c | 73 ++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index da2c3edeb..daee8dc40 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -241,7 +241,8 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); -static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames); +static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); @@ -3260,10 +3261,6 @@ ngx_quic_output(ngx_connection_t *c) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - if (!qc->pto.timer_set && !qc->closing) { - ngx_add_timer(&qc->pto, qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO); - } - return NGX_OK; } @@ -3272,7 +3269,6 @@ static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { size_t len, hlen, n; - ngx_int_t rc; ngx_uint_t need_ack; ngx_queue_t *q, range; ngx_quic_frame_t *f; @@ -3332,32 +3328,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) break; } - rc = ngx_quic_send_frames(c, &range); - - if (rc == NGX_OK) { - /* - * frames are moved into the sent queue - * to wait for ack/be retransmitted - */ - if (qc->closing) { - /* if we are closing, any ack will be discarded */ - ngx_quic_free_frames(c, &range); - - } else { - ngx_queue_add(&ctx->sent, &range); - } - - cg->in_flight += len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", cg->in_flight); - - } else if (rc == NGX_DONE) { - - /* no ack is expected for this frames, can free them */ - ngx_quic_free_frames(c, &range); - - } else { + if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { return NGX_ERROR; } @@ -3390,7 +3361,8 @@ ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) static ngx_int_t -ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) +ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_queue_t *frames) { ssize_t len; u_char *p; @@ -3401,7 +3373,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; - ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static ngx_str_t initial_token = ngx_null_string; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; @@ -3415,8 +3386,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); - ctx = ngx_quic_get_send_ctx(c->quic, start->level); - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); now = ngx_current_msec; @@ -3507,7 +3476,29 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_queue_t *frames) /* len == NGX_OK || NGX_AGAIN */ ctx->pnum++; - return pkt.need_ack ? NGX_OK : NGX_DONE; + if (pkt.need_ack) { + /* move frames into the sent queue to wait for ack */ + + if (qc->closing) { + /* if we are closing, any ack will be discarded */ + ngx_quic_free_frames(c, frames); + + } else { + ngx_queue_add(&ctx->sent, frames); + ngx_add_timer(&qc->pto, NGX_QUIC_HARDCODED_PTO); + } + + qc->congestion.in_flight += out.len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", + qc->congestion.in_flight); + } else { + /* no ack is expected for this frames, so we can free them */ + ngx_quic_free_frames(c, frames); + } + + return NGX_OK; } @@ -3650,16 +3641,10 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_congestion_lost(c, start->last); - /* NGX_DONE is impossible here, such frames don't get into this queue */ - if (ngx_quic_send_frames(c, &range) != NGX_OK) { + if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { return NGX_ERROR; } - /* move frames group to the end of queue */ - ngx_queue_add(&ctx->sent, &range); - - wait = qc->ctp.max_ack_delay + NGX_QUIC_HARDCODED_PTO; - } while (q != ngx_queue_sentinel(&ctx->sent)); *waitp = wait; -- cgit v1.2.3 From 782a634e38018a3d3d9a2460bc7181a28a6578c1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Jul 2020 17:31:29 +0300 Subject: QUIC: reworked retransmission mechanism. Instead of timer-based retransmissions with constant packet lifetime, this patch implements ack-based loss detection and probe timeout for the cases, when no ack is received, according to the quic-recovery draft 29. --- src/event/ngx_event_quic.c | 132 +++++++++++++++++++++++---------------------- src/event/ngx_event_quic.h | 6 +++ 2 files changed, 75 insertions(+), 63 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index daee8dc40..447b4b7ce 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -247,8 +247,7 @@ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); static void ngx_quic_pto_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, ngx_msec_t *waitp); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack); static void ngx_quic_push_handler(ngx_event_t *ev); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, @@ -2341,7 +2340,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } - return NGX_OK; + return ngx_quic_detect_lost(c, 1); } @@ -3535,34 +3534,15 @@ ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) static void ngx_quic_pto_handler(ngx_event_t *ev) { - ngx_uint_t i; - ngx_msec_t wait, nswait; - ngx_connection_t *c; - ngx_quic_connection_t *qc; + ngx_connection_t *c; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); c = ev->data; - qc = c->quic; - wait = 0; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ngx_quic_detect_lost(c, &qc->send_ctx[i], &nswait) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - if (i == 0) { - wait = nswait; - - } else if (nswait > 0 && (wait == 0 || wait > nswait)) { - wait = nswait; - } - } - - if (wait > 0) { - ngx_add_timer(&qc->pto, wait); + if (ngx_quic_detect_lost(c, 0) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; } } @@ -3584,70 +3564,96 @@ ngx_quic_push_handler(ngx_event_t *ev) static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_msec_t *waitp) +ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) { uint64_t pn; - ngx_msec_t now, wait; + ngx_uint_t i; + ngx_msec_t now, wait, min_wait, thr; ngx_queue_t *q, range; ngx_quic_frame_t *f, *start; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; qc = c->quic; - now = ngx_current_msec; - if (ngx_queue_empty(&ctx->sent)) { - *waitp = 0; - return NGX_OK; - } + min_wait = 0; - q = ngx_queue_head(&ctx->sent); + if (ack) { + thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); + thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); - do { - start = ngx_queue_data(q, ngx_quic_frame_t, queue); + } else { + thr = NGX_QUIC_HARDCODED_PTO; + } - wait = start->last + qc->ctp.max_ack_delay - + NGX_QUIC_HARDCODED_PTO - now; + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if ((ngx_msec_int_t) wait > 0) { - break; - } + ctx = &qc->send_ctx[i]; - pn = start->pnum; + if (ngx_queue_empty(&ctx->sent)) { + continue; + } - ngx_queue_init(&range); + q = ngx_queue_head(&ctx->sent); - /* send frames with same packet number to the wire */ do { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (now - start->first > qc->tp.max_idle_timeout) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "quic retransmission timeout"); - return NGX_DECLINED; - } + wait = start->last + thr - now; - if (f->pnum != pn) { - break; + if ((ngx_msec_int_t) wait >= 0) { + + if (min_wait == 0 || wait < min_wait) { + min_wait = wait; + } + + if (!ack) { + break; + } + + if ((start->pnum > ctx->largest_ack) + || ((ctx->largest_ack - start->pnum) < NGX_QUIC_PKT_THR)) + { + break; + } } - q = ngx_queue_next(q); + pn = start->pnum; - ngx_queue_remove(&f->queue); - ngx_queue_insert_tail(&range, &f->queue); + ngx_queue_init(&range); - } while (q != ngx_queue_sentinel(&ctx->sent)); + /* send frames with same packet number to the wire */ + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_lost(c, start->last); + if (f->pnum != pn) { + break; + } - if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { - return NGX_ERROR; - } + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + ngx_queue_insert_tail(&range, &f->queue); + + } while (q != ngx_queue_sentinel(&ctx->sent)); - } while (q != ngx_queue_sentinel(&ctx->sent)); + ngx_quic_congestion_lost(c, start->last); - *waitp = wait; + if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { + return NGX_ERROR; + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (min_wait > 0) { + ngx_add_timer(&qc->pto, min_wait); + } return NGX_OK; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 3ff54b2d4..a06c7e7f5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -45,6 +45,12 @@ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ #define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */ +/* quic-recovery, section 6.1.1, Packet Threshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* quic-recovery, section 6.1.2, Time Threshold */ +#define NGX_QUIC_TIME_THR 1.125 +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ #define NGX_QUIC_MIN_INITIAL_SIZE 1200 -- cgit v1.2.3 From a5fc86c2a2ece85e5f9c89ae20456911b8931952 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Jul 2020 16:05:44 +0300 Subject: QUIC: implemented probe timeout (PTO) calculation. --- src/event/ngx_event_quic.c | 68 ++++++++++++++++++++++++++++++++++++++++------ src/event/ngx_event_quic.h | 1 - 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 447b4b7ce..e0c197b6e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -104,6 +104,8 @@ struct ngx_quic_connection_s { ngx_msec_t min_rtt; ngx_msec_t rttvar; + ngx_msec_t pto_count; + #if (NGX_DEBUG) ngx_uint_t nframes; #endif @@ -200,6 +202,8 @@ static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_msec_t *send_time); static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_inline ngx_msec_t ngx_quic_pto(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f); @@ -1322,6 +1326,9 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) * to terminate the connection immediately. */ + qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) + : ssl_encryption_initial; + if (rc == NGX_OK) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic immediate close, drain = %d", @@ -1332,7 +1339,9 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->close.handler = ngx_quic_close_timer_handler; qc->close.cancelable = 1; - ngx_add_timer(&qc->close, 3 * NGX_QUIC_HARDCODED_PTO); + ctx = ngx_quic_get_send_ctx(qc, qc->error_level); + + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); qc->error = NGX_QUIC_ERR_NO_ERROR; @@ -1347,9 +1356,6 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) qc->error_reason ? qc->error_reason : ""); } - qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) - : ssl_encryption_initial; - (void) ngx_quic_send_cc(c); if (qc->error_level == ssl_encryption_handshake) { @@ -1751,6 +1757,8 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial); ngx_quic_free_frames(c, &ctx->sent); + qc->pto_count = 0; + return ngx_quic_payload_handler(c, pkt); } @@ -2404,6 +2412,8 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_post_event(&qc->push, &ngx_posted_events); } + qc->pto_count = 0; + return NGX_OK; } @@ -2454,6 +2464,34 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, } +static ngx_inline ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = c->quic; + + /* PTO calculation: quic-recovery, Appendix 8 */ + duration = qc->avg_rtt; + + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration <<= qc->pto_count; + + if (qc->congestion.in_flight == 0) { /* no in-flight packets */ + return duration; + } + + if (ctx == &qc->send_ctx[2] && c->ssl->handshaked) { + /* application send space */ + + duration += qc->tp.max_ack_delay << qc->pto_count; + } + + return duration; +} + + static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { @@ -2766,6 +2804,8 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake completed successfully"); + c->ssl->handshaked = 1; + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; @@ -2799,6 +2839,8 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) */ ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_handshake); ngx_quic_free_frames(c, &ctx->sent); + + c->quic->pto_count = 0; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3484,7 +3526,10 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } else { ngx_queue_add(&ctx->sent, frames); - ngx_add_timer(&qc->pto, NGX_QUIC_HARDCODED_PTO); + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); } qc->congestion.in_flight += out.len; @@ -3544,6 +3589,8 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_quic_close_connection(c, NGX_ERROR); return; } + + c->quic->pto_count++; } @@ -3579,12 +3626,13 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) min_wait = 0; +#if (NGX_SUPPRESS_WARN) + thr = 0; +#endif + if (ack) { thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); - - } else { - thr = NGX_QUIC_HARDCODED_PTO; } for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { @@ -3595,6 +3643,10 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) continue; } + if (!ack) { + thr = ngx_quic_pto(c, ctx); + } + q = ngx_queue_head(&ctx->sent); do { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index a06c7e7f5..7d4281d55 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -44,7 +44,6 @@ /* quic-recovery, section 6.2.2, kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ -#define NGX_QUIC_HARDCODED_PTO 1000 /* 1s, TODO: collect */ /* quic-recovery, section 6.1.1, Packet Threshold */ #define NGX_QUIC_PKT_THR 3 /* packets */ /* quic-recovery, section 6.1.2, Time Threshold */ -- cgit v1.2.3 From e05337214e3ef059777c089f4ad03682c626341c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Jul 2020 15:02:38 +0300 Subject: QUIC: added limit of queued data. The ngx_quic_queue_frame() functions puts a frame into send queue and schedules a push timer to actually send data. The patch adds tracking for data amount in the queue and sends data immediately if amount of data exceeds limit. --- src/event/ngx_event_quic.c | 42 +++++++++++++++++++++++------------- src/event/ngx_event_quic.h | 4 ++++ src/event/ngx_event_quic_transport.h | 1 + 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e0c197b6e..8c13c6a04 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -73,6 +73,8 @@ typedef struct { ngx_queue_t frames; ngx_queue_t sent; + + size_t frames_len; } ngx_quic_send_ctx_t; @@ -3273,10 +3275,24 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) ngx_queue_insert_tail(&ctx->frames, &frame->queue); - /* TODO: check PUSH flag on stream and call output */ + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + ctx->frames_len += frame->len; - if (!qc->push.timer_set && !qc->closing) { - ngx_add_timer(&qc->push, qc->tp.max_ack_delay); + if (qc->closing) { + return; + } + + /* TODO: TCP_NODELAY analogue ? TCP_CORK and others... */ + + if (ctx->frames_len < NGX_QUIC_MIN_DATA_NODELAY) { + if (!qc->push.timer_set) { + ngx_add_timer(&qc->push, qc->tp.max_ack_delay); + } + + } else { + ngx_post_event(&qc->push, &ngx_posted_events); } } @@ -3309,7 +3325,7 @@ ngx_quic_output(ngx_connection_t *c) static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - size_t len, hlen, n; + size_t len, hlen; ngx_uint_t need_ack; ngx_queue_t *q, range; ngx_quic_frame_t *f; @@ -3340,9 +3356,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) /* process group of frames that fits into packet */ f = ngx_queue_data(q, ngx_quic_frame_t, queue); - n = ngx_quic_create_frame(NULL, f); - - if (len && hlen + len + n > qc->ctp.max_udp_payload_size) { + if (len && hlen + len + f->len > qc->ctp.max_udp_payload_size) { break; } @@ -3350,7 +3364,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) need_ack = 1; } - if (need_ack && cg->in_flight + len + n > cg->window) { + if (need_ack && cg->in_flight + len + f->len > cg->window) { break; } @@ -3360,8 +3374,9 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_queue_remove(&f->queue); ngx_queue_insert_tail(&range, &f->queue); + ctx->frames_len -= f->len; - len += n; + len += f->len; } while (q != ngx_queue_sentinel(&ctx->frames)); @@ -4271,7 +4286,6 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) static void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { - ssize_t n; ngx_msec_t timer; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; @@ -4279,9 +4293,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) qc = c->quic; cg = &qc->congestion; - n = ngx_quic_create_frame(NULL, f); - - cg->in_flight -= n; + cg->in_flight -= f->len; timer = f->last - cg->recovery_start; @@ -4290,14 +4302,14 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) } if (cg->window < cg->ssthresh) { - cg->window += n; + cg->window += f->len; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion slow start win:%uz, ss:%uz, if:%uz", cg->window, cg->ssthresh, cg->in_flight); } else { - cg->window += qc->tp.max_udp_payload_size * n / cg->window; + cg->window += qc->tp.max_udp_payload_size * f->len / cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion avoidance win:%uz, ss:%uz, if:%uz", diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 7d4281d55..783820a2f 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,6 +54,10 @@ #define NGX_QUIC_MIN_INITIAL_SIZE 1200 +/* if we have so much data, send immediately */ +/* TODO: configurable ? */ +#define NGX_QUIC_MIN_DATA_NODELAY 512 /* bytes */ + #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 661ffede1..f1a278769 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -250,6 +250,7 @@ struct ngx_quic_frame_s { uint64_t pnum; ngx_msec_t first; ngx_msec_t last; + ssize_t len; ngx_uint_t need_ack; /* unsigned need_ack:1; */ -- cgit v1.2.3 From 91b6487d8dd5b309082a12f0bf597fc845e05912 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Jul 2020 16:36:02 +0300 Subject: QUIC: added anti-amplification limit. According to quic-transport draft 29, section 21.12.1.1: Prior to validation, endpoints are limited in what they are able to send. During the handshake, a server cannot send more than three times the data it receives; clients that initiate new connections or migrate to a new network path are limited. --- src/event/ngx_event_quic.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8c13c6a04..95f613045 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -114,6 +114,7 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; + size_t received; uint64_t cur_streams; uint64_t max_streams; @@ -130,6 +131,7 @@ struct ngx_quic_connection_s { unsigned key_phase:1; unsigned in_retry:1; unsigned initialized:1; + unsigned validated:1; }; @@ -689,6 +691,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, * qc->latest_rtt = 0 */ + qc->received = pkt->raw->last - pkt->raw->start; + qc->pto.log = c->log; qc->pto.data = c; qc->pto.handler = ngx_quic_pto_handler; @@ -763,6 +767,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, } /* NGX_OK */ + qc->validated = 1; } else if (tp->retry) { return ngx_quic_retry(c); @@ -1240,6 +1245,7 @@ ngx_quic_input_handler(ngx_event_t *rev) } b.last += n; + qc->received += n; if (ngx_quic_input(c, &b) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); @@ -1639,6 +1645,8 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + qc->validated = 1; + pkt->secret = &keys->client; pkt->level = ssl_encryption_initial; pkt->plaintext = buf; @@ -1759,6 +1767,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial); ngx_quic_free_frames(c, &ctx->sent); + qc->validated = 1; qc->pto_count = 0; return ngx_quic_payload_handler(c, pkt); @@ -3368,6 +3377,22 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) break; } + if (!qc->validated) { + /* + * Prior to validation, endpoints are limited in what they + * are able to send. During the handshake, a server cannot + * send more than three times the data it receives; + */ + + if (((c->sent + len + f->len) / 3) > qc->received) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic hit amplification limit" + " received %uz sent %O", + qc->received, c->sent); + break; + } + } + q = ngx_queue_next(q); f->first = ngx_current_msec; -- cgit v1.2.3 From 8d2d2c5f8fd579200d4d1f2abd7fa4af3ca258e6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 20 Jul 2020 15:19:03 +0300 Subject: Fixed format specifiers. --- src/event/ngx_event_quic.c | 33 +++++++++++----------- src/event/ngx_event_quic_protection.c | 2 +- src/event/ngx_event_quic_transport.c | 52 +++++++++++++++++------------------ src/http/v3/ngx_http_v3_parse.c | 2 +- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 95f613045..b8177f30e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -645,7 +645,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid initial packet: 0x%xi", pkt->flags); + "quic invalid initial packet: 0x%xd", pkt->flags); return NGX_ERROR; } @@ -1614,7 +1614,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid initial packet: 0x%xi", pkt->flags); + "quic invalid initial packet: 0x%xd", pkt->flags); return NGX_ERROR; } @@ -2157,7 +2157,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) frame->u.ack.largest = pkt->pn; frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level); - ngx_sprintf(frame->info, "ACK for PN=%d from frame handler level=%d", + ngx_sprintf(frame->info, "ACK for PN=%uL from frame handler level=%d", pkt->pn, frame->level); ngx_quic_queue_frame(c->quic, frame); @@ -2552,7 +2552,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, if (f->offset > fs->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic out-of-order frame: expecting %ui got %ui", + "quic out-of-order frame: expecting %uL got %uL", fs->received, f->offset); return ngx_quic_buffer_frame(c, fs, frame); @@ -2769,8 +2769,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { - int sslerr; - ssize_t n; + int n, sslerr; ngx_ssl_conn_t *ssl_conn; ngx_quic_send_ctx_t *ctx; ngx_quic_crypto_frame_t *f; @@ -2889,7 +2888,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (sn == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi is new", f->stream_id); + "quic stream id 0x%xL is new", f->stream_id); if (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; @@ -3263,7 +3262,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, if (sn == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi is new", f->id); + "quic stream id 0x%xL is new", f->id); if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; @@ -3951,7 +3950,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) rev = c->read; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi recv: eof:%d, avail:%z", + "quic stream id 0x%xL recv: eof:%d, avail:%z", qs->id, rev->pending_eof, b->last - b->pos); if (b->pos == b->last) { @@ -3963,7 +3962,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi recv() not ready", qs->id); + "quic stream id 0x%xL recv() not ready", qs->id); return NGX_AGAIN; } @@ -3981,7 +3980,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi recv: %z of %uz", qs->id, len, size); + "quic stream id 0x%xL recv: %z of %uz", qs->id, len, size); if (!rev->pending_eof) { frame = ngx_quic_alloc_frame(pc, 0); @@ -4023,7 +4022,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(pc->quic, frame); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi recv: increased max data: %ui", + "quic stream id 0x%xL recv: increased max data: %uL", qs->id, qc->streams.recv_max_data); } @@ -4051,7 +4050,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi send: %uz", qs->id, size); + "quic stream id 0x%xL send: %uz", qs->id, size); /* * we need to fit at least 1 frame into a packet, thus account head/tail; @@ -4127,7 +4126,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) p += fsize; n += fsize; - ngx_sprintf(frame->info, "stream 0x%xi len=%ui level=%d", + ngx_sprintf(frame->info, "stream 0x%xL len=%ui level=%d", qs->id, fsize, frame->level); ngx_quic_queue_frame(qc, frame); @@ -4164,7 +4163,7 @@ ngx_quic_stream_cleanup_handler(void *data) qc = pc->quic; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi cleanup", qs->id); + "quic stream id 0x%xL cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_frames(pc, &qs->fs.frames); @@ -4181,7 +4180,7 @@ ngx_quic_stream_cleanup_handler(void *data) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xi send fin", qs->id); + "quic stream id 0x%xL send fin", qs->id); frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { @@ -4200,7 +4199,7 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stream.length = 0; frame->u.stream.data = NULL; - ngx_sprintf(frame->info, "stream 0x%xi fin=1 level=%d", + ngx_sprintf(frame->info, "stream 0x%xL fin=1 level=%d", qs->id, frame->level); ngx_quic_queue_frame(qc, frame); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 6cf55005c..feba3eb3a 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1089,7 +1089,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, #endif ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic clear flags: %xi", clearflags); + "quic clear flags: %xd", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic packet number: %uL, len: %xi", pn, pnl); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7f9e49ff0..bba9ae9cb 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -261,7 +261,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic long packet flags:%xi version:%xD", + "quic long packet flags:%xd version:%xD", pkt->flags, pkt->version); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { @@ -271,7 +271,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic unsupported version: 0x%xi", pkt->version); + "quic unsupported version: 0x%xD", pkt->version); return NGX_ERROR; } @@ -457,7 +457,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic short packet flags:%xi", pkt->flags); + "quic short packet flags:%xd", pkt->flags); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); @@ -669,8 +669,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ack.ranges_end = p; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in ACK largest:%ui delay:%ui" - " count:%ui first:%ui", f->u.ack.largest, + "quic frame in ACK largest:%uL delay:%uL" + " count:%uL first:%uL", f->u.ack.largest, f->u.ack.delay, f->u.ack.range_count, f->u.ack.first_range); @@ -685,7 +685,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ACK ECN counters: %ui %ui %ui", + "quic ACK ECN counters: %uL %uL %uL", f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); } @@ -722,7 +722,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: NCID seq:%ui retire:%ui len:%ui", + "quic frame in: NCID seq:%uL retire:%uL len:%ud", f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; @@ -758,7 +758,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in CONNECTION_CLOSE" - " err:%s code:0x%xi type:0x%xi reason:'%V'", + " err:%s code:0x%xL type:0x%xL reason:'%V'", ngx_quic_error_text(f->u.close.error_code), f->u.close.error_code, f->u.close.frame_type, &f->u.close.reason); @@ -766,7 +766,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in: CONNECTION_CLOSE_APP:" - " code:0x%xi reason:'%V'", + " code:0x%xL reason:'%V'", f->u.close.error_code, &f->u.close.reason); } @@ -819,8 +819,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STREAM type:0x%xi id:0x%xi offset:0x%xi " - "len:0x%xi bits off:%d len:%d fin:%d", + "quic frame in: STREAM type:0x%xi id:0x%xL offset:0x%xL " + "len:0x%xL bits off:%d len:%d fin:%d", f->type, f->u.stream.stream_id, f->u.stream.offset, f->u.stream.length, f->u.stream.off, f->u.stream.len, f->u.stream.fin); @@ -839,7 +839,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_DATA max_data:%ui", + "quic frame in: MAX_DATA max_data:%uL", f->u.max_data.max_data); break; @@ -855,7 +855,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in: RESET_STREAM" - " id:0x%xi error_code:0x%xi final_size:0x%xi", + " id:0x%xL error_code:0x%xL final_size:0x%xL", f->u.reset_stream.id, f->u.reset_stream.error_code, f->u.reset_stream.final_size); break; @@ -873,7 +873,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STOP_SENDING id:0x%xi error_code:0x%xi", + "quic frame in: STOP_SENDING id:0x%xL error_code:0x%xL", f->u.stop_sending.id, f->u.stop_sending.error_code); break; @@ -890,7 +890,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STREAMS_BLOCKED limit:%ui bidi:%d", + "quic frame in: STREAMS_BLOCKED limit:%uL bidi:%ui", f->u.streams_blocked.limit, f->u.streams_blocked.bidi); @@ -915,7 +915,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_STREAMS limit:%ui bidi:%d", + "quic frame in: MAX_STREAMS limit:%uL bidi:%ui", f->u.max_streams.limit, f->u.max_streams.bidi); break; @@ -933,7 +933,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_STREAM_DATA id:%ui limit:%ui", + "quic frame in: MAX_STREAM_DATA id:%uL limit:%uL", f->u.max_stream_data.id, f->u.max_stream_data.limit); break; @@ -946,7 +946,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: DATA_BLOCKED limit:%ui", + "quic frame in: DATA_BLOCKED limit:%uL", f->u.data_blocked.limit); break; @@ -964,7 +964,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in: STREAM_DATA_BLOCKED" - " id:%ui limit:%ui", + " id:%uL limit:%uL", f->u.stream_data_blocked.id, f->u.stream_data_blocked.limit); break; @@ -978,7 +978,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in: RETIRE_CONNECTION_ID" - " sequence_number:%ui", + " sequence_number:%uL", f->u.retire_cid.sequence_number); break; @@ -1100,7 +1100,7 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic frame type 0x%xi is not " - "allowed in packet with flags 0x%xi", + "allowed in packet with flags 0x%xd", frame_type, pkt->flags); return NGX_DECLINED; @@ -1130,7 +1130,7 @@ ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ACK range: gap %ui range %ui", *gap, *range); + "quic ACK range: gap %uL range %uL", *gap, *range); return p - start; } @@ -1482,7 +1482,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, case NGX_QUIC_TP_STATELESS_RESET_TOKEN: ngx_log_error(NGX_LOG_INFO, log, 0, "quic client sent forbidden transport param" - " id 0x%xi", id); + " id 0x%xL", id); return NGX_ERROR; } @@ -1490,7 +1490,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse" - " transport param id 0x%xi length", id); + " transport param id 0x%xL length", id); return NGX_ERROR; } @@ -1499,13 +1499,13 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (rc == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse" - " transport param id 0x%xi data", id); + " transport param id 0x%xL data", id); return NGX_ERROR; } if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, log, 0, - "quic unknown transport param id 0x%xi,skipped", id); + "quic unknown transport param id 0x%xL, skipped", id); } p += len; diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bb52c9e94..f9e26a4f5 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -325,7 +325,7 @@ done: ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header block prefix done " - "insert_count:%ui, sign:%ui, delta_base:%ui, base:%uL", + "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", st->insert_count, st->sign, st->delta_base, st->base); st->state = sw_start; -- cgit v1.2.3 From 0f1478706d2b818d4079db14d1466cbbf0b7edc0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 18 Jul 2020 00:08:29 +0300 Subject: QUIC: fixed stream read event log. Previously, the main connection log was there. Now it's the stream connection log. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b8177f30e..41d30ca10 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3892,8 +3892,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) sn->c->send = ngx_quic_stream_send; sn->c->send_chain = ngx_quic_stream_send_chain; - sn->c->read->log = c->log; - sn->c->write->log = c->log; + sn->c->read->log = log; + sn->c->write->log = log; log->connection = sn->c->number; -- cgit v1.2.3 From a305de07e934dd1ff21111d0314821a34880ab13 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 18 Jul 2020 00:08:04 +0300 Subject: QUIC: do not verify the selected ALPN protocol. The right protocol is selected by the HTTP code. In the QUIC code only verify that some protocol was selected and trigger an error otherwise. --- src/event/ngx_event_quic.c | 4 +--- src/event/ngx_event_quic.h | 5 ----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 41d30ca10..669637674 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -417,9 +417,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, SSL_get0_alpn_selected(c->ssl->connection, &data, &len); - if (len != NGX_QUIC_ALPN_LEN - || ngx_strncmp(data, NGX_QUIC_ALPN_STR, NGX_QUIC_ALPN_LEN) != 0) - { + if (len == 0) { qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; qc->error_reason = "unsupported protocol in ALPN extension"; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 783820a2f..99c460407 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -18,11 +18,6 @@ #endif #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) -#define NGX_QUIC_ALPN(s) NGX_QUIC_ALPN_DRAFT(s) -#define NGX_QUIC_ALPN_DRAFT(s) "h3-" #s -#define NGX_QUIC_ALPN_STR NGX_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION) -#define NGX_QUIC_ALPN_LEN (sizeof(NGX_QUIC_ALPN_STR) - 1) - #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ #define NGX_QUIC_MAX_LONG_HEADER 56 /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ -- cgit v1.2.3 From b813b9ec358862a2a94868bc057420d6eca5c05d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Jul 2020 23:09:22 +0300 Subject: QUIC: added "quic" listen parameter. The parameter allows processing HTTP/0.9-2 over QUIC. Also, introduced ngx_http_quic_module and moved QUIC settings there --- auto/lib/openssl/conf | 1 + auto/modules | 20 +- auto/options | 3 + src/core/ngx_connection.c | 2 + src/core/ngx_connection.h | 5 +- src/event/ngx_event.c | 10 + src/event/ngx_event_quic.c | 24 ++- src/event/ngx_event_quic.h | 12 +- src/http/modules/ngx_http_quic_module.c | 344 ++++++++++++++++++++++++++++++++ src/http/modules/ngx_http_quic_module.h | 25 +++ src/http/modules/ngx_http_ssl_module.c | 33 ++- src/http/ngx_http.c | 35 +++- src/http/ngx_http.h | 3 + src/http/ngx_http_core_module.c | 30 +++ src/http/ngx_http_core_module.h | 2 + src/http/ngx_http_request.c | 149 ++++---------- src/http/ngx_http_request.h | 1 - src/http/v3/ngx_http_v3.h | 5 +- src/http/v3/ngx_http_v3_module.c | 243 ---------------------- src/http/v3/ngx_http_v3_streams.c | 49 ++++- 20 files changed, 598 insertions(+), 398 deletions(-) create mode 100644 src/http/modules/ngx_http_quic_module.c create mode 100644 src/http/modules/ngx_http_quic_module.h diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index f4dcab725..94ccd5795 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -166,6 +166,7 @@ END exit 1 fi + have=NGX_QUIC . auto/have fi diff --git a/auto/modules b/auto/modules index 67339e7fa..2bcc06e62 100644 --- a/auto/modules +++ b/auto/modules @@ -404,12 +404,9 @@ if [ $HTTP = YES ]; then ngx_module_type=HTTP if [ $HTTP_V3 = YES ]; then - USE_OPENSSL=YES - USE_OPENSSL_QUIC=YES have=NGX_HTTP_V3 . auto/have have=NGX_HTTP_HEADERS . auto/have - - HTTP_SSL=YES + HTTP_QUIC=YES # XXX for Huffman HTTP_V2=YES @@ -696,6 +693,21 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_QUIC = YES ]; then + USE_OPENSSL_QUIC=YES + have=NGX_HTTP_QUIC . auto/have + HTTP_SSL=YES + + ngx_module_name=ngx_http_quic_module + ngx_module_incs= + ngx_module_deps=src/http/modules/ngx_http_quic_module.h + ngx_module_srcs=src/http/modules/ngx_http_quic_module.c + ngx_module_libs= + ngx_module_link=$HTTP_QUIC + + . auto/module + fi + if [ $HTTP_SSL = YES ]; then USE_OPENSSL=YES have=NGX_HTTP_SSL . auto/have diff --git a/auto/options b/auto/options index ddb861783..a1ca80ce5 100644 --- a/auto/options +++ b/auto/options @@ -58,6 +58,7 @@ HTTP_CACHE=YES HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO +HTTP_QUIC=NO HTTP_V2=NO HTTP_V3=NO HTTP_SSI=YES @@ -225,6 +226,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --http-scgi-temp-path=*) NGX_HTTP_SCGI_TEMP_PATH="$value" ;; --with-http_ssl_module) HTTP_SSL=YES ;; + --with-http_quic_module) HTTP_QUIC=YES ;; --with-http_v2_module) HTTP_V2=YES ;; --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; @@ -441,6 +443,7 @@ cat << END --with-file-aio enable file AIO support --with-http_ssl_module enable ngx_http_ssl_module + --with-http_quic_module enable ngx_http_quic_module --with-http_v2_module enable ngx_http_v2_module --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index ba07c63e8..1fb569d30 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1034,9 +1034,11 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { +#if (NGX_QUIC) if (ls[i].quic) { continue; } +#endif c = ls[i].connection; diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 294d4b212..2ce0f153b 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -150,9 +150,12 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; -#if (NGX_SSL || NGX_COMPAT) +#if (NGX_QUIC || NGX_COMPAT) ngx_quic_connection_t *quic; ngx_quic_stream_t *qs; +#endif + +#if (NGX_SSL || NGX_COMPAT) ngx_ssl_connection_t *ssl; #endif diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index f0ab73afe..de32630fd 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -268,6 +268,8 @@ 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; @@ -284,6 +286,8 @@ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) return NGX_OK; } +#endif + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -362,6 +366,8 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) } } +#if (NGX_QUIC) + if (c->qs) { if (!wev->active && !wev->ready) { @@ -374,6 +380,8 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) return NGX_OK; } +#endif + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -944,9 +952,11 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat) { int sndlowat; +#if (NGX_QUIC) if (c->qs) { return NGX_OK; } +#endif #if (NGX_HAVE_LOWAT_EVENT) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 669637674..d72052c82 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -93,6 +93,8 @@ struct ngx_quic_connection_s { ngx_quic_secrets_t next_key; ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_conf_t *conf; + ngx_ssl_t *ssl; ngx_event_t push; @@ -160,7 +162,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_tp_t *tp, ngx_quic_header_t *pkt, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt, ngx_connection_handler_pt handler); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); static ngx_int_t ngx_quic_retry(ngx_connection_t *c); @@ -585,7 +587,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void -ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, +ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_connection_handler_pt handler) { ngx_buf_t *b; @@ -604,7 +606,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, pkt.data = b->start; pkt.len = b->last - b->start; - if (ngx_quic_new_connection(c, ssl, tp, &pkt, handler) != NGX_OK) { + if (ngx_quic_new_connection(c, ssl, conf, &pkt, handler) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -619,8 +621,9 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, static ngx_int_t -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, - ngx_quic_header_t *pkt, ngx_connection_handler_pt handler) +ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt, + ngx_connection_handler_pt handler) { ngx_int_t rc; ngx_uint_t i; @@ -703,7 +706,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, c->quic = qc; qc->ssl = ssl; - qc->tp = *tp; + qc->conf = conf; + qc->tp = conf->tp; qc->streams.handler = handler; ctp = &qc->ctp; @@ -767,7 +771,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, /* NGX_OK */ qc->validated = 1; - } else if (tp->retry) { + } else if (conf->retry) { return ngx_quic_retry(c); } @@ -949,7 +953,7 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) return NGX_ERROR; } - key = c->quic->tp.token_key; + key = c->quic->conf->token_key; iv = token->data; if (RAND_bytes(iv, iv_len) <= 0 @@ -1023,7 +1027,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) /* NEW_TOKEN in a previous connection */ cipher = EVP_aes_256_cbc(); - key = c->quic->tp.token_key; + key = c->quic->conf->token_key; iv = pkt->token.data; iv_len = EVP_CIPHER_iv_length(cipher); @@ -2237,7 +2241,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) ngx_str_t token; ngx_quic_frame_t *frame; - if (!c->quic->tp.retry) { + if (!c->quic->conf->retry) { return NGX_OK; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 99c460407..882858ed0 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -78,15 +78,19 @@ typedef struct { ngx_str_t initial_scid; ngx_str_t retry_scid; - ngx_flag_t retry; - u_char token_key[32]; /* AES 256 */ - /* TODO */ u_char stateless_reset_token[16]; void *preferred_address; } ngx_quic_tp_t; +typedef struct { + ngx_quic_tp_t tp; + ngx_flag_t retry; + u_char token_key[32]; /* AES 256 */ +} ngx_quic_conf_t; + + typedef struct { uint64_t sent; uint64_t received; @@ -107,7 +111,7 @@ struct ngx_quic_stream_s { }; -void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *tp, +void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_connection_handler_pt handler); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c new file mode 100644 index 000000000..d971c8d26 --- /dev/null +++ b/src/http/modules/ngx_http_quic_module.c @@ -0,0 +1,344 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + + +static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_quic_add_variables(ngx_conf_t *cf); +static void *ngx_http_quic_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, + void *data); + + +static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = + { ngx_http_quic_max_ack_delay }; +static ngx_conf_post_t ngx_http_quic_max_udp_payload_size_post = + { ngx_http_quic_max_udp_payload_size }; +static ngx_conf_num_bounds_t ngx_http_quic_ack_delay_exponent_bounds = + { ngx_conf_check_num_bounds, 0, 20 }; +static ngx_conf_num_bounds_t ngx_http_quic_active_connection_id_limit_bounds = + { ngx_conf_check_num_bounds, 2, -1 }; + + +static ngx_command_t ngx_http_quic_commands[] = { + + { ngx_string("quic_max_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_idle_timeout), + NULL }, + + { ngx_string("quic_max_ack_delay"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_ack_delay), + &ngx_http_quic_max_ack_delay_post }, + + { ngx_string("quic_max_udp_payload_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_udp_payload_size), + &ngx_http_quic_max_udp_payload_size_post }, + + { ngx_string("quic_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_data), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_local"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_remote"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote), + NULL }, + + { ngx_string("quic_initial_max_stream_data_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni), + NULL }, + + { ngx_string("quic_initial_max_streams_bidi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi), + NULL }, + + { ngx_string("quic_initial_max_streams_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni), + NULL }, + + { ngx_string("quic_ack_delay_exponent"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), + &ngx_http_quic_ack_delay_exponent_bounds }, + + { ngx_string("quic_active_migration"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.disable_active_migration), + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.active_connection_id_limit), + &ngx_http_quic_active_connection_id_limit_bounds }, + + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, retry), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_quic_module_ctx = { + ngx_http_quic_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_quic_create_srv_conf, /* create server configuration */ + ngx_http_quic_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_quic_module = { + NGX_MODULE_V1, + &ngx_http_quic_module_ctx, /* module context */ + ngx_http_quic_commands, /* 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 +}; + + +static ngx_http_variable_t ngx_http_quic_vars[] = { + + { ngx_string("quic"), NULL, ngx_http_variable_quic, 0, 0, 0 }, + + ngx_http_null_variable +}; + + +static ngx_int_t +ngx_http_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->connection->qs) { + + v->len = 4; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + v->data = (u_char *) "quic"; + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_quic_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_quic_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_quic_create_srv_conf(ngx_conf_t *cf) +{ + ngx_quic_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->tp.original_dcid = { 0, NULL }; + * conf->tp.initial_scid = { 0, NULL }; + * conf->tp.retry_scid = { 0, NULL }; + * conf->tp.stateless_reset_token = { 0 } + * conf->tp.preferred_address = NULL + */ + + conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; + conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC; + conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; + conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; + conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; + conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT; + conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + conf->retry = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_quic_conf_t *prev = parent; + ngx_quic_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, + prev->tp.max_idle_timeout, 60000); + + ngx_conf_merge_msec_value(conf->tp.max_ack_delay, + prev->tp.max_ack_delay, + NGX_QUIC_DEFAULT_MAX_ACK_DELAY); + + ngx_conf_merge_size_value(conf->tp.max_udp_payload_size, + prev->tp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_data, + prev->tp.initial_max_data, + 16 * NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local, + prev->tp.initial_max_stream_data_bidi_local, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote, + prev->tp.initial_max_stream_data_bidi_remote, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni, + prev->tp.initial_max_stream_data_uni, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi, + prev->tp.initial_max_streams_bidi, 16); + + ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni, + prev->tp.initial_max_streams_uni, 16); + + ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent, + prev->tp.ack_delay_exponent, + NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); + + ngx_conf_merge_uint_value(conf->tp.disable_active_migration, + prev->tp.disable_active_migration, 1); + + ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, + prev->tp.active_connection_id_limit, 2); + + ngx_conf_merge_value(conf->retry, prev->retry, 0); + + if (conf->retry) { + if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) +{ + ngx_msec_t *sp = data; + + if (*sp > 16384) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_ack_delay\" must be less than 16384"); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_QUIC_MIN_INITIAL_SIZE + || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_udp_payload_size\" must be between " + "%d and %d", + NGX_QUIC_MIN_INITIAL_SIZE, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h new file mode 100644 index 000000000..e744eb197 --- /dev/null +++ b/src/http/modules/ngx_http_quic_module.h @@ -0,0 +1,25 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#ifndef _NGX_HTTP_QUIC_H_INCLUDED_ +#define _NGX_HTTP_QUIC_H_INCLUDED_ + + +#include +#include +#include + + +#define NGX_HTTP_QUIC_ALPN(s) NGX_HTTP_QUIC_ALPN_DRAFT(s) +#define NGX_HTTP_QUIC_ALPN_DRAFT(s) "\x05hq-" #s +#define NGX_HTTP_QUIC_ALPN_ADVERTISE NGX_HTTP_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION) + + +extern ngx_module_t ngx_http_quic_module; + + +#endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 7daa4daf2..409514821 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -402,7 +402,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2 || NGX_HTTP_V3) +#if (NGX_HTTP_V2 || NGX_HTTP_QUIC) ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2 || NGX_DEBUG) @@ -419,7 +419,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } #endif -#if (NGX_HTTP_V2 || NGX_HTTP_V3) +#if (NGX_HTTP_V2 || NGX_HTTP_QUIC) hc = c->data; #endif @@ -436,6 +436,12 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE; srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1; } else +#endif +#if (NGX_HTTP_QUIC) + if (hc->addr_conf->quic) { + srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1; + } else #endif { srv = (unsigned char *) NGX_HTTP_NPN_ADVERTISE; @@ -1247,6 +1253,7 @@ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { ngx_uint_t a, p, s; + const char *name; ngx_http_conf_addr_t *addr; ngx_http_conf_port_t *port; ngx_http_ssl_srv_conf_t *sscf; @@ -1296,26 +1303,36 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl && !addr[a].opt.http3) { + if (!addr[a].opt.ssl && !addr[a].opt.quic) { continue; } + if (addr[a].opt.http3) { + name = "http3"; + + } else if (addr[a].opt.quic) { + name = "quic"; + + } else { + name = "ssl"; + } + cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; if (sscf->certificates == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } - if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "\"ssl_protocols\" did not enable TLSv1.3 for " - "the \"listen ... http3\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directives in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } } diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 7da64f906..b075f30be 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1200,10 +1200,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) ngx_uint_t ssl; #endif +#if (NGX_HTTP_QUIC) + ngx_uint_t quic; +#endif #if (NGX_HTTP_V2) ngx_uint_t http2; #endif -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) ngx_uint_t http3; #endif @@ -1238,10 +1241,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) ssl = lsopt->ssl || addr[i].opt.ssl; #endif +#if (NGX_HTTP_QUIC) + quic = lsopt->quic || addr[i].opt.quic; +#endif #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) http3 = lsopt->http3 || addr[i].opt.http3; #endif @@ -1277,10 +1283,13 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) addr[i].opt.ssl = ssl; #endif +#if (NGX_HTTP_QUIC) + addr[i].opt.quic = quic; +#endif #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) addr[i].opt.http3 = http3; #endif @@ -1326,12 +1335,12 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif -#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC) +#if (NGX_HTTP_QUIC && !defined NGX_OPENSSL_QUIC) - if (lsopt->http3) { + if (lsopt->quic) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "nginx was built with OpenSSL that lacks QUIC " - "support, HTTP/3 is not enabled for %V", + "support, QUIC is not enabled for %V", &lsopt->addr_text); } @@ -1797,8 +1806,8 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; -#if (NGX_HTTP_SSL) - ls->quic = addr->opt.http3; +#if (NGX_HTTP_QUIC) + ls->quic = addr->opt.quic; #endif return ls; @@ -1830,10 +1839,13 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #if (NGX_HTTP_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; #endif +#if (NGX_HTTP_QUIC) + addrs[i].conf.quic = addr[i].opt.quic; +#endif #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) addrs[i].conf.http3 = addr[i].opt.http3; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1898,10 +1910,13 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #if (NGX_HTTP_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; #endif +#if (NGX_HTTP_QUIC) + addrs6[i].conf.quic = addr[i].opt.quic; +#endif #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif -#if (NGX_HTTP_SSL) +#if (NGX_HTTP_V3) addrs6[i].conf.http3 = addr[i].opt.http3; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index a0946c95a..480802d40 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -50,6 +50,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r, #if (NGX_HTTP_SSL) #include #endif +#if (NGX_HTTP_QUIC) +#include +#endif struct ngx_http_log_ctx_s { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 9bb89ee37..ef8b649ef 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4079,8 +4079,22 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[n].data, "quic") == 0) { +#if (NGX_HTTP_QUIC) + lsopt.quic = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_http_quic_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strcmp(value[n].data, "http3") == 0) { #if (NGX_HTTP_V3) + lsopt.quic = 1; lsopt.http3 = 1; lsopt.type = SOCK_DGRAM; continue; @@ -4201,6 +4215,22 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } +#if (NGX_HTTP_SSL) + +#if (NGX_HTTP_V3) + if (lsopt.ssl && lsopt.http3) { + return "\"ssl\" parameter is incompatible with \"http3\""; + } +#endif + +#if (NGX_HTTP_QUIC) + if (lsopt.ssl && lsopt.quic) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + +#endif + for (n = 0; n < u.naddrs; n++) { lsopt.sockaddr = u.addrs[n].sockaddr; lsopt.socklen = u.addrs[n].socklen; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index e7c117c9e..02e2a9e59 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -74,6 +74,7 @@ typedef struct { unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; + unsigned quic:1; unsigned http2:1; unsigned http3:1; #if (NGX_HAVE_INET6) @@ -238,6 +239,7 @@ struct ngx_http_addr_conf_s { ngx_http_virtual_names_t *virtual_names; unsigned ssl:1; + unsigned quic:1; unsigned http2:1; unsigned http3:1; unsigned proxy_protocol:1; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 8933bb3c3..bfa8e11c5 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -64,9 +64,6 @@ static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); #endif -#if (NGX_HTTP_V3) -static void ngx_http_quic_stream_handler(ngx_connection_t *c); -#endif static char *ngx_http_client_errors[] = { @@ -221,26 +218,7 @@ ngx_http_init_connection(ngx_connection_t *c) ngx_http_in6_addr_t *addr6; #endif -#if (NGX_HTTP_V3) - if (c->type == SOCK_DGRAM) { - ngx_http_v3_connection_t *h3c; - - h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); - if (h3c == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_queue_init(&h3c->blocked); - - hc = &h3c->hc; - hc->quic = 1; - hc->ssl = 1; - - } else -#endif - hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); - + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { ngx_http_close_connection(c); return; @@ -325,6 +303,46 @@ ngx_http_init_connection(ngx_connection_t *c) /* the default server configuration for the address:port */ hc->conf_ctx = hc->addr_conf->default_server->ctx; +#if (NGX_HTTP_QUIC) + + if (hc->addr_conf->quic) { + ngx_quic_conf_t *qcf; + ngx_http_ssl_srv_conf_t *sscf; + +#if (NGX_HTTP_V3) + + if (hc->addr_conf->http3) { + ngx_int_t rc; + + rc = ngx_http_v3_init_connection(c); + + if (rc == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (rc == NGX_DONE) { + return; + } + } + +#endif + + if (c->qs == NULL) { + c->log->connection = c->number; + + qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_quic_module); + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, + ngx_http_ssl_module); + + ngx_quic_run(c, &sscf->ssl, qcf, ngx_http_init_connection); + return; + } + } + +#endif + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { ngx_http_close_connection(c); @@ -346,23 +364,6 @@ ngx_http_init_connection(ngx_connection_t *c) rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; - if (c->shared) { - rev->ready = 1; - } - -#if (NGX_HTTP_V3) - if (hc->quic) { - ngx_http_v3_srv_conf_t *v3cf; - ngx_http_ssl_srv_conf_t *sscf; - - v3cf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - - ngx_quic_run(c, &sscf->ssl, &v3cf->quic, ngx_http_quic_stream_handler); - return; - } -#endif - #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -410,72 +411,6 @@ ngx_http_init_connection(ngx_connection_t *c) } -#if (NGX_HTTP_V3) - -static void -ngx_http_quic_stream_handler(ngx_connection_t *c) -{ - ngx_event_t *rev; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc; - ngx_http_v3_connection_t *h3c; - - h3c = c->qs->parent->data; - - if (!h3c->settings_sent) { - h3c->settings_sent = 1; - - if (ngx_http_v3_send_settings(c) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "could not send settings"); - ngx_http_close_connection(c); - return; - } - } - - if (c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_http_v3_handle_client_uni_stream(c); - return; - } - - hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); - if (hc == NULL) { - ngx_http_close_connection(c); - return; - } - - ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); - c->data = hc; - - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); - if (ctx == NULL) { - ngx_http_close_connection(c); - return; - } - - ctx->connection = c; - ctx->request = NULL; - ctx->current_request = NULL; - - c->log->handler = ngx_http_log_error; - c->log->data = ctx; - c->log->action = "waiting for request"; - - c->log_error = NGX_ERROR_INFO; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 new stream id:0x%uXL", c->qs->id); - - rev = c->read; - rev->handler = ngx_http_wait_request_handler; - c->write->handler = ngx_http_empty_handler; - - rev->handler(rev); -} - -#endif - - static void ngx_http_wait_request_handler(ngx_event_t *rev) { @@ -725,7 +660,7 @@ ngx_http_alloc_request(ngx_connection_t *c) r->http_version = NGX_HTTP_VERSION_10; #if (NGX_HTTP_V3) - if (hc->quic) { + if (hc->addr_conf->http3) { r->http_version = NGX_HTTP_VERSION_30; } #endif diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 1994000a4..3d33d93d4 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -324,7 +324,6 @@ typedef struct { ngx_chain_t *free; unsigned ssl:1; - unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index d4ea19aed..51117f720 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -118,6 +118,8 @@ typedef struct { } ngx_http_v3_connection_t; +ngx_int_t ngx_http_v3_init_connection(ngx_connection_t *c); + ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b); ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t allow_underscores); @@ -130,9 +132,6 @@ uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix); -ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); -void ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c); - ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 386209cb7..58d75f6f7 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,114 +10,8 @@ #include -static char *ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data); -static char *ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post, - void *data); - - -static ngx_conf_post_t ngx_http_v3_max_ack_delay_post = - { ngx_http_v3_max_ack_delay }; -static ngx_conf_post_t ngx_http_v3_max_udp_payload_size_post = - { ngx_http_v3_max_udp_payload_size }; -static ngx_conf_num_bounds_t ngx_http_v3_ack_delay_exponent_bounds = - { ngx_conf_check_num_bounds, 0, 20 }; -static ngx_conf_num_bounds_t ngx_http_v3_active_connection_id_limit_bounds = - { ngx_conf_check_num_bounds, 2, -1 }; - - static ngx_command_t ngx_http_v3_commands[] = { - { ngx_string("quic_max_idle_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.max_idle_timeout), - NULL }, - - { ngx_string("quic_max_ack_delay"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.max_ack_delay), - &ngx_http_v3_max_ack_delay_post }, - - { ngx_string("quic_max_udp_payload_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.max_udp_payload_size), - &ngx_http_v3_max_udp_payload_size_post }, - - { ngx_string("quic_initial_max_data"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_data), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_local"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_local), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_remote"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_bidi_remote), - NULL }, - - { ngx_string("quic_initial_max_stream_data_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_stream_data_uni), - NULL }, - - { ngx_string("quic_initial_max_streams_bidi"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_bidi), - NULL }, - - { ngx_string("quic_initial_max_streams_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.initial_max_streams_uni), - NULL }, - - { ngx_string("quic_ack_delay_exponent"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.ack_delay_exponent), - &ngx_http_v3_ack_delay_exponent_bounds }, - - { ngx_string("quic_active_migration"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.disable_active_migration), - NULL }, - - { ngx_string("quic_active_connection_id_limit"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), - &ngx_http_v3_active_connection_id_limit_bounds }, - - { ngx_string("quic_retry"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.retry), - NULL }, - { ngx_string("http3_max_field_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -143,8 +37,6 @@ static ngx_command_t ngx_http_v3_commands[] = { }; -static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); @@ -185,8 +77,6 @@ ngx_module_t ngx_http_v3_module = { static ngx_http_variable_t ngx_http_v3_vars[] = { - { ngx_string("quic"), NULL, ngx_http_variable_quic, - 0, 0, 0 }, { ngx_string("http3"), NULL, ngx_http_variable_http3, 0, 0, 0 }, @@ -195,26 +85,6 @@ static ngx_http_variable_t ngx_http_v3_vars[] = { }; -static ngx_int_t -ngx_http_variable_quic(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data) -{ - if (r->connection->qs) { - - v->len = 4; - v->valid = 1; - v->no_cacheable = 1; - v->not_found = 0; - v->data = (u_char *) "quic"; - return NGX_OK; - } - - v->not_found = 1; - - return NGX_OK; -} - - static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) @@ -264,31 +134,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) return NULL; } - /* - * set by ngx_pcalloc(): - * v3cf->quic.original_dcid = { 0, NULL }; - * v3cf->quic.initial_scid = { 0, NULL }; - * v3cf->quic.retry_scid = { 0, NULL }; - * v3cf->quic.stateless_reset_token = { 0 } - * conf->quic.preferred_address = NULL - */ - - v3cf->quic.max_idle_timeout = NGX_CONF_UNSET_MSEC; - v3cf->quic.max_ack_delay = NGX_CONF_UNSET_MSEC; - - v3cf->quic.max_udp_payload_size = NGX_CONF_UNSET_SIZE; - v3cf->quic.initial_max_data = NGX_CONF_UNSET_SIZE; - v3cf->quic.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; - v3cf->quic.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; - v3cf->quic.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; - v3cf->quic.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; - v3cf->quic.initial_max_streams_uni = NGX_CONF_UNSET_UINT; - v3cf->quic.ack_delay_exponent = NGX_CONF_UNSET_UINT; - v3cf->quic.disable_active_migration = NGX_CONF_UNSET_UINT; - v3cf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; - - v3cf->quic.retry = NGX_CONF_UNSET; - v3cf->max_field_size = NGX_CONF_UNSET_SIZE; v3cf->max_table_capacity = NGX_CONF_UNSET_SIZE; v3cf->max_blocked_streams = NGX_CONF_UNSET_UINT; @@ -303,57 +148,6 @@ 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_conf_merge_msec_value(conf->quic.max_idle_timeout, - prev->quic.max_idle_timeout, 60000); - - ngx_conf_merge_msec_value(conf->quic.max_ack_delay, - prev->quic.max_ack_delay, - NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - - ngx_conf_merge_size_value(conf->quic.max_udp_payload_size, - prev->quic.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - ngx_conf_merge_size_value(conf->quic.initial_max_data, - prev->quic.initial_max_data, - 16 * NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_local, - prev->quic.initial_max_stream_data_bidi_local, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_bidi_remote, - prev->quic.initial_max_stream_data_bidi_remote, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.initial_max_stream_data_uni, - prev->quic.initial_max_stream_data_uni, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_uint_value(conf->quic.initial_max_streams_bidi, - prev->quic.initial_max_streams_bidi, 16); - - ngx_conf_merge_uint_value(conf->quic.initial_max_streams_uni, - prev->quic.initial_max_streams_uni, 16); - - ngx_conf_merge_uint_value(conf->quic.ack_delay_exponent, - prev->quic.ack_delay_exponent, - NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - - ngx_conf_merge_uint_value(conf->quic.disable_active_migration, - prev->quic.disable_active_migration, 1); - - ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, - prev->quic.active_connection_id_limit, 2); - - ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); - - if (conf->quic.retry) { - if (RAND_bytes(conf->quic.token_key, sizeof(conf->quic.token_key)) <= 0) { - return NGX_CONF_ERROR; - } - } - ngx_conf_merge_size_value(conf->max_field_size, prev->max_field_size, NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE); @@ -368,40 +162,3 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_OK; } - - -static char * -ngx_http_v3_max_ack_delay(ngx_conf_t *cf, void *post, void *data) -{ - ngx_msec_t *sp = data; - - if (*sp > 16384) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_ack_delay\" must be less than 16384"); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * -ngx_http_v3_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_udp_payload_size\" must be between " - "%d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 8eaa7fde6..04070f9a6 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -27,22 +27,55 @@ static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); +static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); -void -ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) +ngx_int_t +ngx_http_v3_init_connection(ngx_connection_t *c) { + ngx_http_connection_t *hc; ngx_http_v3_uni_stream_t *us; + ngx_http_v3_connection_t *h3c; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 new uni stream id:0x%uxL", c->qs->id); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init connection"); + + hc = c->data; + + if (c->qs == NULL) { + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { + return NGX_ERROR; + } + + h3c->hc = *hc; + + ngx_queue_init(&h3c->blocked); + + c->data = h3c; + return NGX_OK; + } + + h3c = c->qs->parent->data; + + if (!h3c->settings_sent) { + h3c->settings_sent = 1; + + if (ngx_http_v3_send_settings(c) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "could not send settings"); + return NGX_ERROR; + } + } + + if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + return NGX_OK; + } us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, NULL); - ngx_http_v3_close_uni_stream(c); - return; + return NGX_ERROR; } us->index = -1; @@ -53,6 +86,8 @@ ngx_http_v3_handle_client_uni_stream(ngx_connection_t *c) c->write->handler = ngx_http_v3_dummy_write_handler; ngx_http_v3_read_uni_stream_type(c->read); + + return NGX_DONE; } @@ -366,7 +401,7 @@ failed: } -ngx_int_t +static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c) { u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; -- cgit v1.2.3 From 36f2873f6b0d8512c053935614fcc6ae9d969858 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Jul 2020 23:08:39 +0300 Subject: QUIC: added "quic" listen parameter in Stream. Also, introduced ngx_stream_quic_module. --- auto/modules | 14 ++ auto/options | 3 + src/event/ngx_event_quic.c | 23 ++- src/event/ngx_event_quic.h | 1 + src/http/modules/ngx_http_quic_module.c | 1 + src/stream/ngx_stream.c | 6 + src/stream/ngx_stream.h | 6 + src/stream/ngx_stream_core_module.c | 32 +++ src/stream/ngx_stream_handler.c | 21 ++ src/stream/ngx_stream_quic_module.c | 343 ++++++++++++++++++++++++++++++++ src/stream/ngx_stream_quic_module.h | 20 ++ 11 files changed, 459 insertions(+), 11 deletions(-) create mode 100644 src/stream/ngx_stream_quic_module.c create mode 100644 src/stream/ngx_stream_quic_module.h diff --git a/auto/modules b/auto/modules index 2bcc06e62..d213fb451 100644 --- a/auto/modules +++ b/auto/modules @@ -1065,6 +1065,20 @@ if [ $STREAM != NO ]; then ngx_module_incs= + if [ $STREAM_QUIC = YES ]; then + USE_OPENSSL_QUIC=YES + have=NGX_STREAM_QUIC . auto/have + STREAM_SSL=YES + + ngx_module_name=ngx_stream_quic_module + ngx_module_deps=src/stream/ngx_stream_quic_module.h + ngx_module_srcs=src/stream/ngx_stream_quic_module.c + ngx_module_libs= + ngx_module_link=$STREAM_QUIC + + . auto/module + fi + if [ $STREAM_SSL = YES ]; then USE_OPENSSL=YES have=NGX_STREAM_SSL . auto/have diff --git a/auto/options b/auto/options index a1ca80ce5..450fe729e 100644 --- a/auto/options +++ b/auto/options @@ -118,6 +118,7 @@ MAIL_SMTP=YES STREAM=NO STREAM_SSL=NO +STREAM_QUIC=NO STREAM_REALIP=NO STREAM_LIMIT_CONN=YES STREAM_ACCESS=YES @@ -315,6 +316,7 @@ use the \"--with-mail_ssl_module\" option instead" --with-stream) STREAM=YES ;; --with-stream=dynamic) STREAM=DYNAMIC ;; --with-stream_ssl_module) STREAM_SSL=YES ;; + --with-stream_quic_module) STREAM_QUIC=YES ;; --with-stream_realip_module) STREAM_REALIP=YES ;; --with-stream_geoip_module) STREAM_GEOIP=YES ;; --with-stream_geoip_module=dynamic) @@ -534,6 +536,7 @@ cat << END --with-stream enable TCP/UDP proxy module --with-stream=dynamic enable dynamic TCP/UDP proxy module --with-stream_ssl_module enable ngx_stream_ssl_module + --with-stream_quic_module enable ngx_stream_quic_module --with-stream_realip_module enable ngx_stream_realip_module --with-stream_geoip_module enable ngx_stream_geoip_module --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d72052c82..16035bfb5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -413,20 +413,20 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, */ #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - { - unsigned int len; - const unsigned char *data; + if (qc->conf->require_alpn) { + unsigned int len; + const unsigned char *data; - SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); - if (len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; - qc->error_reason = "unsupported protocol in ALPN extension"; + if (len == 0) { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; - } + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } } #endif @@ -3882,6 +3882,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) } sn->c->qs = sn; + sn->c->type = SOCK_STREAM; sn->c->pool = pool; sn->c->ssl = c->ssl; sn->c->sockaddr = c->sockaddr; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 882858ed0..151e15cc5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -87,6 +87,7 @@ typedef struct { typedef struct { ngx_quic_tp_t tp; ngx_flag_t retry; + ngx_flag_t require_alpn; u_char token_key[32]; /* AES 256 */ } ngx_quic_conf_t; diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index d971c8d26..9888e2eae 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -241,6 +241,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; + conf->require_alpn = 1; return conf; } diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index 78356754e..bc4aa09a3 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -571,6 +571,9 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, addrs[i].conf.ctx = addr[i].opt.ctx; #if (NGX_STREAM_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; +#endif +#if (NGX_STREAM_QUIC) + addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs[i].conf.addr_text = addr[i].opt.addr_text; @@ -606,6 +609,9 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, addrs6[i].conf.ctx = addr[i].opt.ctx; #if (NGX_STREAM_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; +#endif +#if (NGX_STREAM_QUIC) + addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs6[i].conf.addr_text = addr[i].opt.addr_text; diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h index 9e3583295..6cf5eee09 100644 --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -16,6 +16,10 @@ #include #endif +#if (NGX_STREAM_QUIC) +#include +#endif + typedef struct ngx_stream_session_s ngx_stream_session_t; @@ -51,6 +55,7 @@ typedef struct { unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; + unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -73,6 +78,7 @@ typedef struct { ngx_stream_conf_ctx_t *ctx; ngx_str_t addr_text; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_stream_addr_conf_t; diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 9b6afe974..6990ae3f6 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -325,6 +325,9 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); if (c->type == SOCK_STREAM +#if (NGX_STREAM_QUIC) + && c->qs == NULL +#endif && cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { @@ -741,6 +744,29 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[i].data, "quic") == 0) { +#if (NGX_STREAM_QUIC) + ngx_stream_ssl_conf_t *sslcf; + + sslcf = ngx_stream_conf_get_module_srv_conf(cf, + ngx_stream_ssl_module); + + sslcf->listen = 1; + sslcf->file = cf->conf_file->file.name.data; + sslcf->line = cf->conf_file->line; + + ls->quic = 1; + ls->type = SOCK_DGRAM; + + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_stream_quic_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { @@ -852,6 +878,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } #endif +#if (NGX_STREAM_SSL && NGX_STREAM_QUIC) + if (ls->ssl && ls->quic) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + if (ls->so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 669b6a18d..672de85ac 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -115,6 +115,27 @@ ngx_stream_init_connection(ngx_connection_t *c) } } +#if (NGX_STREAM_QUIC) + + if (addr_conf->quic) { + ngx_quic_conf_t *qcf; + ngx_stream_ssl_conf_t *scf; + + if (c->qs == NULL) { + c->log->connection = c->number; + + qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, + ngx_stream_quic_module); + scf = ngx_stream_get_module_srv_conf(addr_conf->ctx, + ngx_stream_ssl_module); + + ngx_quic_run(c, &scf->ssl, qcf, ngx_stream_init_connection); + return; + } + } + +#endif + s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t)); if (s == NULL) { ngx_stream_close_connection(c); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c new file mode 100644 index 000000000..362855f1a --- /dev/null +++ b/src/stream/ngx_stream_quic_module.c @@ -0,0 +1,343 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include +#include +#include + + +static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); +static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, + void *data); + + +static ngx_conf_post_t ngx_stream_quic_max_ack_delay_post = + { ngx_stream_quic_max_ack_delay }; +static ngx_conf_post_t ngx_stream_quic_max_udp_payload_size_post = + { ngx_stream_quic_max_udp_payload_size }; +static ngx_conf_num_bounds_t ngx_stream_quic_ack_delay_exponent_bounds = + { ngx_conf_check_num_bounds, 0, 20 }; +static ngx_conf_num_bounds_t + ngx_stream_quic_active_connection_id_limit_bounds = + { ngx_conf_check_num_bounds, 2, -1 }; + + +static ngx_command_t ngx_stream_quic_commands[] = { + + { ngx_string("quic_max_idle_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_idle_timeout), + NULL }, + + { ngx_string("quic_max_ack_delay"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_ack_delay), + &ngx_stream_quic_max_ack_delay_post }, + + { ngx_string("quic_max_udp_payload_size"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.max_udp_payload_size), + &ngx_stream_quic_max_udp_payload_size_post }, + + { ngx_string("quic_initial_max_data"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_data), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_local"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_remote"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote), + NULL }, + + { ngx_string("quic_initial_max_stream_data_uni"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni), + NULL }, + + { ngx_string("quic_initial_max_streams_bidi"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi), + NULL }, + + { ngx_string("quic_initial_max_streams_uni"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni), + NULL }, + + { ngx_string("quic_ack_delay_exponent"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), + &ngx_stream_quic_ack_delay_exponent_bounds }, + + { ngx_string("quic_active_migration"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.disable_active_migration), + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, tp.active_connection_id_limit), + &ngx_stream_quic_active_connection_id_limit_bounds }, + + { ngx_string("quic_retry"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, retry), + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_quic_module_ctx = { + ngx_stream_quic_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_quic_create_srv_conf, /* create server configuration */ + ngx_stream_quic_merge_srv_conf, /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_quic_module = { + NGX_MODULE_V1, + &ngx_stream_quic_module_ctx, /* module context */ + ngx_stream_quic_commands, /* module directives */ + NGX_STREAM_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 +}; + + +static ngx_stream_variable_t ngx_stream_quic_vars[] = { + + { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 }, + + ngx_stream_null_variable +}; + + +static ngx_int_t +ngx_stream_variable_quic(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + if (s->connection->qs) { + + v->len = 4; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + v->data = (u_char *) "quic"; + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_quic_add_variables(ngx_conf_t *cf) +{ + ngx_stream_variable_t *var, *v; + + for (v = ngx_stream_quic_vars; v->name.len; v++) { + var = ngx_stream_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) +{ + ngx_quic_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->tp.original_dcid = { 0, NULL }; + * conf->tp.initial_scid = { 0, NULL }; + * conf->tp.retry_scid = { 0, NULL }; + * conf->tp.stateless_reset_token = { 0 } + * conf->tp.preferred_address = NULL + * conf->require_alpn = 0; + */ + + conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; + conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC; + conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; + conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; + conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; + conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; + conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT; + conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + conf->retry = NGX_CONF_UNSET; + + return conf; +} + + +static char * +ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_quic_conf_t *prev = parent; + ngx_quic_conf_t *conf = child; + + ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, + prev->tp.max_idle_timeout, 60000); + + ngx_conf_merge_msec_value(conf->tp.max_ack_delay, + prev->tp.max_ack_delay, + NGX_QUIC_DEFAULT_MAX_ACK_DELAY); + + ngx_conf_merge_size_value(conf->tp.max_udp_payload_size, + prev->tp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_data, + prev->tp.initial_max_data, + 16 * NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local, + prev->tp.initial_max_stream_data_bidi_local, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote, + prev->tp.initial_max_stream_data_bidi_remote, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni, + prev->tp.initial_max_stream_data_uni, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi, + prev->tp.initial_max_streams_bidi, 16); + + ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni, + prev->tp.initial_max_streams_uni, 16); + + ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent, + prev->tp.ack_delay_exponent, + NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); + + ngx_conf_merge_uint_value(conf->tp.disable_active_migration, + prev->tp.disable_active_migration, 1); + + ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, + prev->tp.active_connection_id_limit, 2); + + ngx_conf_merge_value(conf->retry, prev->retry, 0); + + if (conf->retry) { + if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) +{ + ngx_msec_t *sp = data; + + if (*sp > 16384) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_ack_delay\" must be less than 16384"); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_QUIC_MIN_INITIAL_SIZE + || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_udp_payload_size\" must be between " + "%d and %d", + NGX_QUIC_MIN_INITIAL_SIZE, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} diff --git a/src/stream/ngx_stream_quic_module.h b/src/stream/ngx_stream_quic_module.h new file mode 100644 index 000000000..6ac4d96f0 --- /dev/null +++ b/src/stream/ngx_stream_quic_module.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_STREAM_QUIC_H_INCLUDED_ +#define _NGX_STREAM_QUIC_H_INCLUDED_ + + +#include +#include +#include + + +extern ngx_module_t ngx_stream_quic_module; + + +#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */ -- cgit v1.2.3 From 3073ad1381c4d8f8aae4501d66497164167b2081 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Jul 2020 23:08:23 +0300 Subject: QUIC: eliminated connection handler argument in ngx_quic_run(). Now c->listening->handler() is called instead. --- src/event/ngx_event_quic.c | 15 +++++---------- src/event/ngx_event_quic.h | 3 +-- src/http/ngx_http_request.c | 2 +- src/stream/ngx_stream_handler.c | 2 +- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 16035bfb5..202c0082a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -36,7 +36,6 @@ typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; - ngx_connection_handler_pt handler; ngx_uint_t id_counter; @@ -162,8 +161,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt, - ngx_connection_handler_pt handler); + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); static ngx_int_t ngx_quic_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); @@ -587,8 +585,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void -ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, - ngx_connection_handler_pt handler) +ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) { ngx_buf_t *b; ngx_quic_header_t pkt; @@ -606,7 +603,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, pkt.data = b->start; pkt.len = b->last - b->start; - if (ngx_quic_new_connection(c, ssl, conf, &pkt, handler) != NGX_OK) { + if (ngx_quic_new_connection(c, ssl, conf, &pkt) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -622,8 +619,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt, - ngx_connection_handler_pt handler) + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; ngx_uint_t i; @@ -708,7 +704,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->ssl = ssl; qc->conf = conf; qc->tp = conf->tp; - qc->streams.handler = handler; ctp = &qc->ctp; ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); @@ -2949,7 +2944,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_handle_max_streams(c); } - qc->streams.handler(sn->c); + sn->c->listening->handler(sn->c); if (f->offset == 0) { return NGX_OK; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 151e15cc5..a56ead272 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -112,8 +112,7 @@ struct ngx_quic_stream_s { }; -void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, - ngx_connection_handler_pt handler); +void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf); ngx_connection_t *ngx_quic_create_uni_stream(ngx_connection_t *c); void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index bfa8e11c5..9b6d461e0 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -336,7 +336,7 @@ ngx_http_init_connection(ngx_connection_t *c) sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - ngx_quic_run(c, &sscf->ssl, qcf, ngx_http_init_connection); + ngx_quic_run(c, &sscf->ssl, qcf); return; } } diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 672de85ac..2b0848a67 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -129,7 +129,7 @@ ngx_stream_init_connection(ngx_connection_t *c) scf = ngx_stream_get_module_srv_conf(addr_conf->ctx, ngx_stream_ssl_module); - ngx_quic_run(c, &scf->ssl, qcf, ngx_stream_init_connection); + ngx_quic_run(c, &scf->ssl, qcf); return; } } -- cgit v1.2.3 From cdc0d61ea0c55f89c9fbc35b4c785c1f5d7f895e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Jul 2020 11:03:42 +0300 Subject: HTTP/3: do not call shutdown() for QUIC streams. Previously, this triggered an alert "shutdown() failed" in error log. --- src/http/ngx_http_request.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 9b6d461e0..285879f2f 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3504,11 +3504,13 @@ ngx_http_set_lingering_close(ngx_http_request_t *r) } } - if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { - ngx_connection_error(c, ngx_socket_errno, - ngx_shutdown_socket_n " failed"); - ngx_http_close_request(r, 0); - return; + if (c->fd != NGX_INVALID_FILE) { + if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); + ngx_http_close_request(r, 0); + return; + } } if (rev->ready) { -- cgit v1.2.3 From 5468904e3be8d1f85ba51de51e01747fa02ea6d7 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Jul 2020 13:34:48 +0300 Subject: SSL: fixed compilation without QUIC after 0d2b2664b41c. --- src/event/ngx_event_openssl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index a5188dc0e..dc0861ce9 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2777,10 +2777,12 @@ ngx_ssl_shutdown(ngx_connection_t *c) int n, sslerr, mode; ngx_err_t err; +#if (NGX_QUIC) if (c->qs) { /* QUIC streams inherit SSL object */ return NGX_OK; } +#endif ngx_ssl_ocsp_cleanup(c); -- cgit v1.2.3 From ca0b9871bc74d7d4548d76eb4d4c2a3a5ebb22ec Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Jul 2020 14:48:49 +0300 Subject: QUIC: fixed bulding perl module by reducing header pollution. The ngx_http_perl_module module doesn't have a notion of including additional search paths through --with-cc-opt, which results in compile error incomplete type 'enum ssl_encryption_level_t' when building nginx without QUIC support. The enum is visible from quic event headers and eventually pollutes ngx_core.h. The fix is to limit including headers to compile units that are real consumers. --- src/core/ngx_core.h | 2 -- src/event/ngx_event_quic.c | 2 ++ src/event/ngx_event_quic_protection.c | 2 ++ src/event/ngx_event_quic_transport.c | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index a8959ddcc..ade35be73 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -86,8 +86,6 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #if (NGX_OPENSSL_QUIC) #include -#include -#include #endif #endif #include diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 202c0082a..9a8c94243 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include /* 0-RTT and 1-RTT data exist in the same packet number space, diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index feba3eb3a..721944b97 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #define NGX_QUIC_IV_LEN 12 diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index bba9ae9cb..f1fc09449 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -7,6 +7,7 @@ #include #include #include +#include #if (NGX_HAVE_NONALIGNED) -- cgit v1.2.3 From 548d515fbaf39b8b567daffecf2bdc71ecbfa579 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Jul 2020 13:45:34 +0300 Subject: QUIC: updated README to mention "quic" listen parameter. --- README | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README b/README index 680deb465..98f211175 100644 --- a/README +++ b/README @@ -89,13 +89,24 @@ Experimental QUIC support for nginx -L../boringssl/build/crypto" $ make + When configuring nginx, you can enable QUIC and HTTP/3 using the + following new configuration options: + + --with-http_v3_module - enable QUIC and HTTP/3 + --with-http_quic_module - enable QUIC for older HTTP versions + --with-stream_quic_module - enable QUIC in Stream + 3. Configuration - The "listen" directive got a new option: "http3" - which enables HTTP/3 over QUIC on the specified port. + The HTTP "listen" directive got two new options: "http3" and "quic". + The "http3" option enables HTTP/3 over QUIC on the specified port. + The "quic" option enables QUIC for older HTTP versions on this port. + + The Stream "listen" directive got a new option "quic" which enables + QUIC as client transport protocol instead of TCP or plain UDP. - Along with "http3", you also have to specify "reuseport" option [6] - to make it work properly with multiple workers. + Along with "http3" or "quic", you also have to specify "reuseport" + option [6] to make it work properly with multiple workers. A number of directives were added that specify transport parameter values: -- cgit v1.2.3 From 04b2a169a47ac5054350e670bc5d6fd10f2434fc Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Jul 2020 16:00:00 +0300 Subject: HTTP/3: header encoding functions. --- src/http/v3/ngx_http_v3.c | 128 +++++++++++++++++++++++++++ src/http/v3/ngx_http_v3.h | 12 +++ src/http/v3/ngx_http_v3_request.c | 176 +++++++++++++++++++------------------- 3 files changed, 226 insertions(+), 90 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index e796da772..a80310faf 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -97,3 +97,131 @@ ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) return (uintptr_t) p; } + + +uintptr_t +ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count, + ngx_uint_t sign, ngx_uint_t delta_base) +{ + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) + + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); + + *p = sign ? 0x80 : 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +{ + /* Indexed Header Field */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 6); + } + + *p = dynamic ? 0x80 : 0xc0; + + return ngx_http_v3_encode_prefix_int(p, index, 6); +} + + +uintptr_t +ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + u_char *data, size_t len) +{ + /* Literal Header Field With Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = dynamic ? 0x60 : 0x70; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p = ngx_cpymem(p, data, len); + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +{ + /* Literal Header Field Without Name Reference */ + + 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; + } + + *p = 0x30; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + + ngx_strlow(p, name->data, name->len); + p += name->len; + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + p = ngx_cpymem(p, value->data, value->len); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index) +{ + /* Indexed Header Field With Post-Base Index */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4); + } + + *p = 0x10; + + return ngx_http_v3_encode_prefix_int(p, index, 4); +} + + +uintptr_t +ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, + size_t len) +{ + /* Literal Header Field With Post-Base Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 3) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = 0x08; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p = ngx_cpymem(p, data, len); + } + + return (uintptr_t) p; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 51117f720..28e0e6f11 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -132,6 +132,18 @@ uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix); +uintptr_t ngx_http_v3_encode_header_block_prefix(u_char *p, + ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); +uintptr_t ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index); +uintptr_t ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index, u_char *data, size_t len); +uintptr_t ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, + ngx_str_t *value); +uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index); +uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, + u_char *data, size_t len); + ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0ffa8927d..5fa232a91 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,16 @@ #include +/* static table indices */ +#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_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_SERVER 92 + + static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); @@ -416,7 +426,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) u_char *p; size_t len, n; ngx_buf_t *b; - ngx_uint_t i, j; + ngx_uint_t i; ngx_chain_t *hl, *cl, *bl; ngx_list_part_t *part; ngx_table_elt_t *header; @@ -427,14 +437,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - len = 2; + len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); } else { - len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4) - + ngx_http_v3_encode_prefix_int(NULL, 3, 7); + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -450,15 +462,14 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - len += ngx_http_v3_encode_prefix_int(NULL, 92, 4) - + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); } if (r->headers_out.date == NULL) { - len += ngx_http_v3_encode_prefix_int(NULL, 6, 4) - + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len, - 7) - + ngx_cached_http_time.len; + len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { @@ -470,22 +481,29 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - len += ngx_http_v3_encode_prefix_int(NULL, 53, 4) - + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); } - if (r->headers_out.content_length_n > 0) { - len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); - } else if (r->headers_out.content_length_n == 0) { - len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - len += ngx_http_v3_encode_prefix_int(NULL, 10, 4) + 1 - + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT"); + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } /* XXX location */ @@ -493,8 +511,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) #if (NGX_HTTP_GZIP) if (r->gzip_vary) { if (clcf->gzip_vary) { - /* Vary: Accept-Encoding */ - len += ngx_http_v3_encode_prefix_int(NULL, 59, 6); + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } else { r->gzip_vary = 0; @@ -521,10 +539,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) continue; } - len += ngx_http_v3_encode_prefix_int(NULL, header[i].key.len, 3) - + header[i].key.len - + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 ) - + header[i].value.len; + len += ngx_http_v3_encode_header_l(NULL, &header[i].key, + &header[i].value); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); @@ -534,20 +550,17 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - *b->last++ = 0; - *b->last++ = 0; + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - /* :status: 200 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6); + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); } else { - /* :status: 200 */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); } @@ -565,23 +578,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - /* server */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); - b->last = ngx_cpymem(b->last, p, n); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); } if (r->headers_out.date == NULL) { - /* date */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - ngx_cached_http_time.len, 7); - b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, - ngx_cached_http_time.len); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { @@ -593,23 +599,21 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - /* content-type: text/plain */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); p = b->last; - b->last = ngx_copy(b->last, r->headers_out.content_type.data, - r->headers_out.content_type.len); + b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, + r->headers_out.content_type.len); if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { b->last = ngx_cpymem(b->last, "; charset=", sizeof("; charset=") - 1); - b->last = ngx_copy(b->last, r->headers_out.charset.data, - r->headers_out.charset.len); + b->last = ngx_cpymem(b->last, r->headers_out.charset.data, + r->headers_out.charset.len); /* update r->headers_out.content_type for possible logging */ @@ -618,36 +622,38 @@ ngx_http_v3_create_header(ngx_http_request_t *r) } } - if (r->headers_out.content_length_n > 0) { - /* content-length: 0 */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); - p = b->last++; - b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); - *p = b->last - p - 1; + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); - } else if (r->headers_out.content_length_n == 0) { - /* content-length: 0 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); + } else if (r->headers_out.content_length_n == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - /* last-modified */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4); - p = b->last++; + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); - *p = b->last - p - 1; } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - /* vary: accept-encoding */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6); + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } #endif @@ -670,19 +676,9 @@ ngx_http_v3_create_header(ngx_http_request_t *r) continue; } - *b->last = 0x30; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - header[i].key.len, - 3); - for (j = 0; j < header[i].key.len; j++) { - *b->last++ = ngx_tolower(header[i].key.data[j]); - } - - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - header[i].value.len, - 7); - b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); + b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, + &header[i].key, + &header[i].value); } if (r->header_only) { -- cgit v1.2.3 From fc5a7234b469cf939c7e44b76ca1146ac7ecf519 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 12:31:40 +0300 Subject: HTTP/3: generate Location response header for absolute redirects. --- src/http/v3/ngx_http_v3_request.c | 83 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5fa232a91..0edd8f514 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -14,6 +14,7 @@ #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_STATUS_200 25 #define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 #define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 @@ -426,12 +427,15 @@ ngx_http_v3_create_header(ngx_http_request_t *r) u_char *p; size_t len, n; ngx_buf_t *b; - ngx_uint_t i; + ngx_str_t host; + ngx_uint_t i, port; ngx_chain_t *hl, *cl, *bl; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; c = r->connection; @@ -506,7 +510,52 @@ ngx_http_v3_create_header(ngx_http_request_t *r) sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } - /* XXX location */ + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NULL; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + n = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + n += sizeof(":65535") - 1; + } + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, n); + + } else { + ngx_str_null(&host); + port = 0; + } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { @@ -650,6 +699,36 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); } + if (host.data) { + n = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (port) { + n += ngx_sprintf(b->last, ":%ui", port) - b->last; + } + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + NULL, n); + + p = b->last; + b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); + b->last = ngx_cpymem(b->last, host.data, host.len); + + if (port) { + b->last = ngx_sprintf(b->last, ":%ui", port); + } + + b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = b->last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + #if (NGX_HTTP_GZIP) if (r->gzip_vary) { b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, -- cgit v1.2.3 From 6d7ddb54711c00e6a43ae16c9151ad9d9c89a86c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Jul 2020 12:33:00 +0300 Subject: HTTP/3: encode frame ids with ngx_http_v3_encode_varlen_int(). Even though typically frame ids fit into a single byte, calling ngx_http_v3_encode_varlen_int() adds to the code clarity. --- src/http/v3/ngx_http_v3_request.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0edd8f514..af9cbd2b3 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -774,14 +774,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = b->last - b->pos; - len = 1 + ngx_http_v3_encode_varlen_int(NULL, n); + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); b = ngx_create_temp_buf(c->pool, len); if (b == NULL) { return NULL; } - *b->last++ = NGX_HTTP_V3_FRAME_HEADERS; + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); hl = ngx_alloc_chain_link(c->pool); @@ -793,7 +795,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->next = cl; if (r->headers_out.content_length_n >= 0 && !r->header_only) { - len = 1 + ngx_http_v3_encode_varlen_int(NULL, + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); b = ngx_create_temp_buf(c->pool, len); @@ -801,7 +804,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - *b->last++ = NGX_HTTP_V3_FRAME_DATA; + 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, r->headers_out.content_length_n); -- cgit v1.2.3 From 47ed87f85548e329bbfa76dfc8749ac0f21e55ca Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Jul 2020 12:38:08 +0300 Subject: HTTP/3: renamed ngx_http_v3.c to ngx_http_v3_encode.c. The file contains only encoding functions. --- auto/modules | 2 +- src/http/v3/ngx_http_v3.c | 227 --------------------------------------- src/http/v3/ngx_http_v3_encode.c | 227 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 228 deletions(-) delete mode 100644 src/http/v3/ngx_http_v3.c create mode 100644 src/http/v3/ngx_http_v3_encode.c diff --git a/auto/modules b/auto/modules index d213fb451..e8f773a4f 100644 --- a/auto/modules +++ b/auto/modules @@ -415,7 +415,7 @@ if [ $HTTP = YES ]; then ngx_module_incs=src/http/v3 ngx_module_deps="src/http/v3/ngx_http_v3.h \ src/http/v3/ngx_http_v3_parse.h" - ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + ngx_module_srcs="src/http/v3/ngx_http_v3_encode.c \ src/http/v3/ngx_http_v3_parse.c \ src/http/v3/ngx_http_v3_tables.c \ src/http/v3/ngx_http_v3_streams.c \ diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c deleted file mode 100644 index a80310faf..000000000 --- a/src/http/v3/ngx_http_v3.c +++ /dev/null @@ -1,227 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include - - -uintptr_t -ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) -{ - if (value <= 63) { - if (p == NULL) { - return 1; - } - - *p++ = value; - return (uintptr_t) p; - } - - if (value <= 16383) { - if (p == NULL) { - return 2; - } - - *p++ = 0x40 | (value >> 8); - *p++ = value; - return (uintptr_t) p; - } - - if (value <= 1073741823) { - if (p == NULL) { - return 4; - } - - *p++ = 0x80 | (value >> 24); - *p++ = (value >> 16); - *p++ = (value >> 8); - *p++ = value; - return (uintptr_t) p; - } - - if (p == NULL) { - return 8; - } - - *p++ = 0xc0 | (value >> 56); - *p++ = (value >> 48); - *p++ = (value >> 40); - *p++ = (value >> 32); - *p++ = (value >> 24); - *p++ = (value >> 16); - *p++ = (value >> 8); - *p++ = value; - return (uintptr_t) p; -} - - -uintptr_t -ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) -{ - ngx_uint_t thresh, n; - - thresh = (1 << prefix) - 1; - - if (value < thresh) { - if (p == NULL) { - return 1; - } - - *p++ |= value; - return (uintptr_t) p; - } - - value -= thresh; - - if (p == NULL) { - for (n = 2; value >= 128; n++) { - value >>= 7; - } - - return n; - } - - *p++ |= thresh; - - while (value >= 128) { - *p++ = 0x80 | value; - value >>= 7; - } - - *p++ = value; - - return (uintptr_t) p; -} - - -uintptr_t -ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count, - ngx_uint_t sign, ngx_uint_t delta_base) -{ - if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) - + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); - } - - *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); - - *p = sign ? 0x80 : 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); - - return (uintptr_t) p; -} - - -uintptr_t -ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) -{ - /* Indexed Header Field */ - - if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, index, 6); - } - - *p = dynamic ? 0x80 : 0xc0; - - return ngx_http_v3_encode_prefix_int(p, index, 6); -} - - -uintptr_t -ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, - u_char *data, size_t len) -{ - /* Literal Header Field With Name Reference */ - - if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, index, 4) - + ngx_http_v3_encode_prefix_int(NULL, len, 7) - + len; - } - - *p = dynamic ? 0x60 : 0x70; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); - - *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); - - if (data) { - p = ngx_cpymem(p, data, len); - } - - return (uintptr_t) p; -} - - -uintptr_t -ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) -{ - /* Literal Header Field Without Name Reference */ - - 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; - } - - *p = 0x30; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); - - ngx_strlow(p, name->data, name->len); - p += name->len; - - *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); - - p = ngx_cpymem(p, value->data, value->len); - - return (uintptr_t) p; -} - - -uintptr_t -ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index) -{ - /* Indexed Header Field With Post-Base Index */ - - if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, index, 4); - } - - *p = 0x10; - - return ngx_http_v3_encode_prefix_int(p, index, 4); -} - - -uintptr_t -ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, - size_t len) -{ - /* Literal Header Field With Post-Base Name Reference */ - - if (p == NULL) { - return ngx_http_v3_encode_prefix_int(NULL, index, 3) - + ngx_http_v3_encode_prefix_int(NULL, len, 7) - + len; - } - - *p = 0x08; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); - - *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); - - if (data) { - p = ngx_cpymem(p, data, len); - } - - return (uintptr_t) p; -} diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c new file mode 100644 index 000000000..a80310faf --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.c @@ -0,0 +1,227 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +uintptr_t +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) +{ + if (value <= 63) { + if (p == NULL) { + return 1; + } + + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 16383) { + if (p == NULL) { + return 2; + } + + *p++ = 0x40 | (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 1073741823) { + if (p == NULL) { + return 4; + } + + *p++ = 0x80 | (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (p == NULL) { + return 8; + } + + *p++ = 0xc0 | (value >> 56); + *p++ = (value >> 48); + *p++ = (value >> 40); + *p++ = (value >> 32); + *p++ = (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) +{ + ngx_uint_t thresh, n; + + thresh = (1 << prefix) - 1; + + if (value < thresh) { + if (p == NULL) { + return 1; + } + + *p++ |= value; + return (uintptr_t) p; + } + + value -= thresh; + + if (p == NULL) { + for (n = 2; value >= 128; n++) { + value >>= 7; + } + + return n; + } + + *p++ |= thresh; + + while (value >= 128) { + *p++ = 0x80 | value; + value >>= 7; + } + + *p++ = value; + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count, + ngx_uint_t sign, ngx_uint_t delta_base) +{ + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) + + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); + + *p = sign ? 0x80 : 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +{ + /* Indexed Header Field */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 6); + } + + *p = dynamic ? 0x80 : 0xc0; + + return ngx_http_v3_encode_prefix_int(p, index, 6); +} + + +uintptr_t +ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + u_char *data, size_t len) +{ + /* Literal Header Field With Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = dynamic ? 0x60 : 0x70; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p = ngx_cpymem(p, data, len); + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +{ + /* Literal Header Field Without Name Reference */ + + 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; + } + + *p = 0x30; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + + ngx_strlow(p, name->data, name->len); + p += name->len; + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + p = ngx_cpymem(p, value->data, value->len); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index) +{ + /* Indexed Header Field With Post-Base Index */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4); + } + + *p = 0x10; + + return ngx_http_v3_encode_prefix_int(p, index, 4); +} + + +uintptr_t +ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, + size_t len) +{ + /* Literal Header Field With Post-Base Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 3) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = 0x08; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p = ngx_cpymem(p, data, len); + } + + return (uintptr_t) p; +} -- cgit v1.2.3 From f2368597c2b2b0263e5472c2002a52e7594094f0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 13:12:01 +0300 Subject: HTTP/3: renamed server configuration variables from v3cf to h3scf. Now they are similar to HTTP/2 where they are called h2scf. --- src/http/v3/ngx_http_v3_module.c | 14 +++++++------- src/http/v3/ngx_http_v3_parse.c | 6 +++--- src/http/v3/ngx_http_v3_streams.c | 12 ++++++------ src/http/v3/ngx_http_v3_tables.c | 20 ++++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 58d75f6f7..00169ef4e 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -127,18 +127,18 @@ ngx_http_v3_add_variables(ngx_conf_t *cf) static void * ngx_http_v3_create_srv_conf(ngx_conf_t *cf) { - ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_srv_conf_t *h3scf; - v3cf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); - if (v3cf == NULL) { + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { return NULL; } - v3cf->max_field_size = NGX_CONF_UNSET_SIZE; - v3cf->max_table_capacity = NGX_CONF_UNSET_SIZE; - v3cf->max_blocked_streams = NGX_CONF_UNSET_UINT; + h3scf->max_field_size = NGX_CONF_UNSET_SIZE; + h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; + h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; - return v3cf; + return h3scf; } diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index f9e26a4f5..8a68fddd8 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -426,7 +426,7 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, u_char ch) { ngx_uint_t n; - ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_srv_conf_t *h3scf; enum { sw_start = 0, sw_value @@ -442,9 +442,9 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, n = st->length; - v3cf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (n > v3cf->max_field_size) { + if (n > h3scf->max_field_size) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_field_size limit"); return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 04070f9a6..63b88f259 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -407,7 +407,7 @@ 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_srv_conf_t *v3cf; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -418,23 +418,23 @@ ngx_http_v3_send_settings(ngx_connection_t *c) } h3c = c->qs->parent->data; - v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); n = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); - n += ngx_http_v3_encode_varlen_int(NULL, v3cf->max_table_capacity); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->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, v3cf->max_blocked_streams); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->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, v3cf->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, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, v3cf->max_blocked_streams); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); n = p - buf; if (cc->send(cc, buf, n) != (ssize_t) n) { diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 446601b01..bf4f1449c 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -243,7 +243,7 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_v3_header_t **elts; - ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -252,9 +252,9 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) pc = c->qs->parent; h3c = pc->data; - v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); - if (capacity > v3cf->max_table_capacity) { + if (capacity > h3scf->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; @@ -496,7 +496,7 @@ 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 *v3cf; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -509,9 +509,9 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) h3c = c->qs->parent->data; dt = &h3c->table; - v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); - max_entries = v3cf->max_table_capacity / 32; + max_entries = h3scf->max_table_capacity / 32; full_range = 2 * max_entries; if (*insert_count > full_range) { @@ -551,7 +551,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_v3_block_t *block; - ngx_http_v3_srv_conf_t *v3cf; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -595,10 +595,10 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) } if (block->queue.prev == NULL) { - v3cf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, - ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, + ngx_http_v3_module); - if (h3c->nblocked == v3cf->max_blocked_streams) { + if (h3c->nblocked == h3scf->max_blocked_streams) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_blocked_streams limit"); return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; -- cgit v1.2.3 From c8a194b29aed55fee195b5e11a20af6d1eeef961 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 11:40:10 +0300 Subject: Style: moved function declarations to match usual code style. Plus a few other minor style changes. --- src/http/v3/ngx_http_v3_module.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 00169ef4e..2483b9cb6 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,6 +10,14 @@ #include +static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); + + static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("http3_max_field_size"), @@ -37,14 +45,6 @@ static ngx_command_t ngx_http_v3_commands[] = { }; -static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); -static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); -static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, - void *parent, void *child); - - static ngx_http_module_t ngx_http_v3_module_ctx = { ngx_http_v3_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ @@ -78,8 +78,7 @@ ngx_module_t ngx_http_v3_module = { static ngx_http_variable_t ngx_http_v3_vars[] = { - { ngx_string("http3"), NULL, ngx_http_variable_http3, - 0, 0, 0 }, + { ngx_string("http3"), NULL, ngx_http_variable_http3, 0, 0, 0 }, ngx_http_null_variable }; @@ -123,7 +122,6 @@ ngx_http_v3_add_variables(ngx_conf_t *cf) } - static void * ngx_http_v3_create_srv_conf(ngx_conf_t *cf) { -- cgit v1.2.3 From 5e036a6bef567bbe4e87ce7958ab76663ed3242e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 14 Jul 2020 16:52:44 +0300 Subject: HTTP/3: support $server_protocol variable. Now it holds "HTTP/3.0". Previously it was empty. --- src/http/ngx_http_parse.c | 4 ++++ src/http/ngx_http_request.c | 4 ---- src/http/v3/ngx_http_v3_request.c | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index b59a6ce4c..a351f2b27 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -833,6 +833,10 @@ done: r->request_end = p; } + if (r->http_protocol.data) { + r->http_protocol.len = r->request_end - r->http_protocol.data; + } + r->http_version = r->http_major * 1000 + r->http_minor; r->state = sw_start; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 285879f2f..82deef674 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1139,10 +1139,6 @@ ngx_http_process_request_line(ngx_event_t *rev) r->method_name.len = r->method_end - r->method_start; r->method_name.data = r->method_start; - if (r->http_protocol.data) { - r->http_protocol.len = r->request_end - r->http_protocol.data; - } - if (ngx_http_process_request_uri(r) != NGX_OK) { break; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index af9cbd2b3..adc40d368 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -110,6 +110,8 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) continue; } + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + len = (r->method_end - r->method_start) + 1 + (r->uri_end - r->uri_start) + 1 + sizeof("HTTP/3") - 1; -- cgit v1.2.3 From a1f7106bf76bad578e63a62cf992c06912a15274 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Jul 2020 18:51:42 +0300 Subject: QUIC: limited the number of server-initiated streams. Also, ngx_quic_create_uni_stream() is replaced with ngx_quic_open_stream() which is capable of creating a bidi stream. --- src/event/ngx_event_quic.c | 107 +++++++++++++++++++++++++++++++++----- src/event/ngx_event_quic.h | 2 +- src/http/v3/ngx_http_v3_streams.c | 28 +++++----- 3 files changed, 111 insertions(+), 26 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9a8c94243..9cf25568c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -39,12 +39,15 @@ typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; - ngx_uint_t id_counter; - uint64_t received; uint64_t sent; uint64_t recv_max_data; 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; } ngx_quic_streams_t; @@ -243,6 +246,8 @@ static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +static 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); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -494,6 +499,9 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, } #endif + qc->streams.server_max_streams_bidi = qc->ctp.initial_max_streams_bidi; + qc->streams.server_max_streams_uni = qc->ctp.initial_max_streams_uni; + qc->client_tp_done = 1; } @@ -2100,6 +2108,17 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_NEW_CONNECTION_ID: case NGX_QUIC_FT_RETIRE_CONNECTION_ID: case NGX_QUIC_FT_PATH_CHALLENGE: @@ -3273,6 +3292,35 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (f->bidi) { + if (qc->streams.server_max_streams_bidi < f->limit) { + qc->streams.server_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; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { @@ -3750,26 +3798,61 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) ngx_connection_t * -ngx_quic_create_uni_stream(ngx_connection_t *c) +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { - ngx_uint_t id; + size_t rcvbuf_size; + uint64_t id; ngx_quic_stream_t *qs, *sn; ngx_quic_connection_t *qc; qs = c->qs; qc = qs->parent->quic; - id = (qc->streams.id_counter << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED - | NGX_QUIC_STREAM_UNIDIRECTIONAL; + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_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); + return NULL; + } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server uni stream #%ui id %ui", - qc->streams.id_counter, id); + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream %uL/%uL id:%uL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_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); + return NULL; + } + + id = (qc->streams.server_streams_uni << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED + | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.id_counter++; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server uni stream %uL/%uL id:%uL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + rcvbuf_size = 0; + } - sn = ngx_quic_create_stream(qs->parent, id, 0); + sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); if (sn == NULL) { return NULL; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index a56ead272..2d43defef 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -113,7 +113,7 @@ struct ngx_quic_stream_s { void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf); -ngx_connection_t *ngx_quic_create_uni_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); diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 63b88f259..2ac3f7d74 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -55,16 +55,8 @@ ngx_http_v3_init_connection(ngx_connection_t *c) return NGX_OK; } - h3c = c->qs->parent->data; - - if (!h3c->settings_sent) { - h3c->settings_sent = 1; - - if (ngx_http_v3_send_settings(c) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "could not send settings"); - return NGX_ERROR; - } + if (ngx_http_v3_send_settings(c) == NGX_ERROR) { + return NGX_ERROR; } if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { @@ -361,7 +353,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) } } - sc = ngx_quic_create_uni_stream(c); + sc = ngx_quic_open_stream(c, 0); if (sc == NULL) { return NULL; } @@ -410,14 +402,19 @@ ngx_http_v3_send_settings(ngx_connection_t *c) ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; + h3c = c->qs->parent->data; + + if (h3c->settings_sent) { + return NGX_OK; + } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); if (cc == NULL) { - return NGX_ERROR; + return NGX_DECLINED; } - h3c = c->qs->parent->data; h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); n = ngx_http_v3_encode_varlen_int(NULL, @@ -441,12 +438,17 @@ ngx_http_v3_send_settings(ngx_connection_t *c) goto failed; } + h3c->settings_sent = 1; + return NGX_OK; failed: ngx_http_v3_close_uni_stream(cc); + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "could not send settings"); + return NGX_ERROR; } -- cgit v1.2.3 From 77384356cea5750aee576b7d4a68aa0b0a1c13a1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Jul 2020 19:15:17 +0300 Subject: QUIC: limited the number of client-initiated streams. The limits on active bidi and uni client streams are maintained at their initial values initial_max_streams_bidi and initial_max_streams_uni by sending a MAX_STREAMS frame upon each client stream closure. Also, the following is changed for data arriving to non-existing streams: - if a stream was already closed, such data is ignored - when creating a new stream, all streams of the same type with lower ids are created too --- src/event/ngx_event_quic.c | 177 ++++++++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 92 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9cf25568c..a42bd50b9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -48,6 +48,11 @@ typedef struct { 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; } ngx_quic_streams_t; @@ -122,9 +127,6 @@ struct ngx_quic_connection_s { ngx_quic_congestion_t congestion; size_t received; - uint64_t cur_streams; - uint64_t max_streams; - ngx_uint_t error; enum ssl_encryption_level_t error_level; ngx_uint_t error_ftype; @@ -233,7 +235,6 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); -static ngx_int_t ngx_quic_handle_max_streams(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, ngx_quic_max_data_frame_t *f); static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, @@ -722,6 +723,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->streams.recv_max_data = qc->tp.initial_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->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, ngx_max(2 * qc->tp.max_udp_payload_size, 14720)); @@ -1182,8 +1186,6 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif - qc->max_streams = qc->tp.initial_max_streams_bidi; - return NGX_OK; } @@ -2885,6 +2887,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { size_t n; + uint64_t id; ngx_buf_t *b; ngx_event_t *rev; ngx_quic_stream_t *sn; @@ -2913,9 +2916,35 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - n = (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - ? qc->tp.initial_max_stream_data_uni - : qc->tp.initial_max_stream_data_bidi_remote; + if (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if ((f->stream_id >> 2) < qc->streams.client_streams_uni) { + return NGX_OK; + } + + if ((f->stream_id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NGX_ERROR; + } + + id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (f->stream_id >> 2) + 1; + n = qc->tp.initial_max_stream_data_uni; + + } else { + if ((f->stream_id >> 2) < qc->streams.client_streams_bidi) { + return NGX_OK; + } + + if ((f->stream_id >> 2) >= qc->streams.client_max_streams_bidi) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NGX_ERROR; + } + + id = (qc->streams.client_streams_bidi << 2); + qc->streams.client_streams_bidi = (f->stream_id >> 2) + 1; + n = qc->tp.initial_max_stream_data_bidi_remote; + } if (n < NGX_QUIC_STREAM_BUFSIZE) { n = NGX_QUIC_STREAM_BUFSIZE; @@ -2928,8 +2957,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } /* - * TODO: check IDs are increasing ? create all lower-numbered? - * * 2.1. Stream Types and Identifiers * * Within each type, streams are created with numerically increasing @@ -2937,36 +2964,31 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, * streams of that type with lower-numbered stream IDs also being * opened. */ - sn = ngx_quic_create_stream(c, f->stream_id, n); - if (sn == NULL) { - return NGX_ERROR; - } - rev = sn->c->read; + for ( /* void */ ; id <= f->stream_id; id += 0x04) { - if (f->offset == 0) { + sn = ngx_quic_create_stream(c, id, n); + if (sn == NULL) { + return NGX_ERROR; + } - b = sn->b; - b->last = ngx_cpymem(b->last, f->data, f->length); + if (id == f->stream_id && f->offset == 0) { + b = sn->b; + b->last = ngx_cpymem(b->last, f->data, f->length); - sn->fs.received += f->length; + sn->fs.received += f->length; - rev->ready = 1; + rev = sn->c->read; + rev->ready = 1; - if (f->fin) { - rev->pending_eof = 1; + if (f->fin) { + rev->pending_eof = 1; + } } - } else { - rev->ready = 0; - } - - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - ngx_quic_handle_max_streams(c); + sn->c->listening->handler(sn->c); } - sn->c->listening->handler(sn->c); - if (f->offset == 0) { return NGX_OK; } @@ -3032,43 +3054,6 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } -static ngx_int_t -ngx_quic_handle_max_streams(ngx_connection_t *c) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = c->quic; - qc->cur_streams++; - - if (qc->cur_streams + NGX_QUIC_STREAMS_INC / 2 < qc->max_streams) { - return NGX_OK; - } - - frame = ngx_quic_alloc_frame(c, 0); - if (frame == NULL) { - return NGX_ERROR; - } - - qc->max_streams = ngx_max(qc->max_streams + NGX_QUIC_STREAMS_INC, - NGX_QUIC_STREAMS_LIMIT); - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAMS; - frame->u.max_streams.limit = qc->max_streams; - frame->u.max_streams.bidi = 1; - - ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d", - (int) frame->u.max_streams.limit, - (int) frame->u.max_streams.bidi, - frame->level); - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, ngx_quic_max_data_frame_t *f) @@ -3112,27 +3097,6 @@ static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) { - ngx_quic_frame_t *frame; - - frame = ngx_quic_alloc_frame(c, 0); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_MAX_STREAMS; - frame->u.max_streams.limit = ngx_max(f->limit * 2, NGX_QUIC_STREAMS_LIMIT); - frame->u.max_streams.bidi = f->bidi; - - c->quic->max_streams = frame->u.max_streams.limit; - - ngx_sprintf(frame->info, "MAX_STREAMS limit:%d bidi:%d level=%d", - (int) frame->u.max_streams.limit, - (int) frame->u.max_streams.bidi, - frame->level); - - ngx_quic_queue_frame(c->quic, frame); - return NGX_OK; } @@ -3921,6 +3885,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) ngx_pool_cleanup_t *cln; ngx_quic_connection_t *qc; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%uL create", id); + qc = c->quic; pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); @@ -4257,9 +4224,35 @@ ngx_quic_stream_cleanup_handler(void *data) return; } - if ((qs->id & 0x03) == NGX_QUIC_STREAM_UNIDIRECTIONAL) { - /* do not send fin for client unidirectional streams */ - return; + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc, 0); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_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.bidi = 1; + } + + ngx_sprintf(frame->info, "MAX_STREAMS limit:%uL bidi:%ui level=%d", + frame->u.max_streams.limit, + frame->u.max_streams.bidi, + (int) frame->level); + + ngx_quic_queue_frame(qc, frame); + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + /* do not send fin for client unidirectional streams */ + return; + } } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From 6d064c94e0600f7ff15e2815784f9f89cc4858b4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 13:41:24 +0300 Subject: HTTP/3: server pushes. New directives are added: - http3_max_concurrent_pushes - http3_push - http3_push_preload --- src/http/ngx_http.h | 1 + src/http/ngx_http_request.c | 3 +- src/http/v3/ngx_http_v3.h | 21 ++ src/http/v3/ngx_http_v3_module.c | 133 ++++++- src/http/v3/ngx_http_v3_parse.c | 36 +- src/http/v3/ngx_http_v3_request.c | 704 +++++++++++++++++++++++++++++++++++++- src/http/v3/ngx_http_v3_streams.c | 135 ++++++++ 7 files changed, 1020 insertions(+), 13 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 480802d40..2a3d81a37 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -93,6 +93,7 @@ ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, void ngx_http_init_connection(ngx_connection_t *c); void ngx_http_close_connection(ngx_connection_t *c); +u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); #if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME) int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 82deef674..54e5e2866 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -55,7 +55,6 @@ static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); static void ngx_http_log_request(ngx_http_request_t *r); -static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); static u_char *ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr, u_char *buf, size_t len); @@ -3838,7 +3837,7 @@ ngx_http_close_connection(ngx_connection_t *c) } -static u_char * +u_char * ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len) { u_char *p; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 28e0e6f11..aab27b3ac 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -50,6 +50,7 @@ #define NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE 4096 #define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 #define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 +#define NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES 10 /* HTTP/3 errors */ #define NGX_HTTP_V3_ERR_NO_ERROR 0x100 @@ -89,9 +90,17 @@ typedef struct { size_t max_field_size; size_t max_table_capacity; ngx_uint_t max_blocked_streams; + ngx_uint_t max_concurrent_pushes; } ngx_http_v3_srv_conf_t; +typedef struct { + ngx_flag_t push_preload; + ngx_flag_t push; + ngx_array_t *pushes; +} ngx_http_v3_loc_conf_t; + + typedef struct { ngx_str_t name; ngx_str_t value; @@ -110,8 +119,15 @@ typedef struct { typedef struct { ngx_http_connection_t hc; ngx_http_v3_dynamic_table_t table; + ngx_queue_t blocked; ngx_uint_t nblocked; + + ngx_queue_t pushing; + ngx_uint_t npushing; + uint64_t next_push_id; + uint64_t max_push_id; + ngx_uint_t settings_sent; /* unsigned settings_sent:1; */ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; @@ -144,6 +160,8 @@ uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index); uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, size_t len); +ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, + uint64_t push_id); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, @@ -163,6 +181,9 @@ ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count); ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value); +ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, + uint64_t max_push_id); +ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 2483b9cb6..89748f5f4 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -16,6 +16,10 @@ static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); +static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_v3_commands[] = { @@ -41,6 +45,27 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_blocked_streams), NULL }, + { ngx_string("http3_max_concurrent_pushes"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), + NULL }, + + { ngx_string("http3_push"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_v3_push, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("http3_push_preload"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_v3_loc_conf_t, push_preload), + NULL }, + ngx_null_command }; @@ -55,8 +80,8 @@ static ngx_http_module_t ngx_http_v3_module_ctx = { ngx_http_v3_create_srv_conf, /* create server configuration */ ngx_http_v3_merge_srv_conf, /* merge server configuration */ - NULL, /* create location configuration */ - NULL /* merge location configuration */ + ngx_http_v3_create_loc_conf, /* create location configuration */ + ngx_http_v3_merge_loc_conf /* merge location configuration */ }; @@ -135,6 +160,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->max_field_size = NGX_CONF_UNSET_SIZE; h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; + h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; return h3scf; } @@ -158,5 +184,108 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->max_blocked_streams, NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS); + ngx_conf_merge_uint_value(conf->max_concurrent_pushes, + prev->max_concurrent_pushes, + NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES); + + return NGX_CONF_OK; +} + + +static void * +ngx_http_v3_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_v3_loc_conf_t *h3lcf; + + h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); + if (h3lcf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * h3lcf->pushes = NULL; + */ + + h3lcf->push_preload = NGX_CONF_UNSET; + h3lcf->push = NGX_CONF_UNSET; + + return h3lcf; +} + + +static char * +ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_loc_conf_t *prev = parent; + ngx_http_v3_loc_conf_t *conf = child; + + ngx_conf_merge_value(conf->push, prev->push, 1); + + if (conf->push && conf->pushes == NULL) { + conf->pushes = prev->pushes; + } + + ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); + + return NGX_CONF_OK; +} + + +static char * +ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_v3_loc_conf_t *h3lcf = conf; + + ngx_str_t *value; + ngx_http_complex_value_t *cv; + ngx_http_compile_complex_value_t ccv; + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + + if (h3lcf->pushes) { + return "\"off\" parameter cannot be used with URI"; + } + + if (h3lcf->push == 0) { + return "is duplicate"; + } + + h3lcf->push = 0; + return NGX_CONF_OK; + } + + if (h3lcf->push == 0) { + return "URI cannot be used with \"off\" parameter"; + } + + h3lcf->push = 1; + + if (h3lcf->pushes == NULL) { + h3lcf->pushes = ngx_array_create(cf->pool, 1, + sizeof(ngx_http_complex_value_t)); + if (h3lcf->pushes == NULL) { + return NGX_CONF_ERROR; + } + } + + cv = ngx_array_push(h3lcf->pushes); + if (cv == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 8a68fddd8..1a7aa17f8 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -933,6 +933,7 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) sw_first_type, sw_type, sw_length, + sw_cancel_push, sw_settings, sw_max_push_id, sw_skip @@ -988,6 +989,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) switch (st->type) { + case NGX_HTTP_V3_FRAME_CANCEL_PUSH: + st->state = sw_cancel_push; + break; + case NGX_HTTP_V3_FRAME_SETTINGS: st->state = sw_settings; break; @@ -1004,6 +1009,26 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) break; + case sw_cancel_push: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + + if (--st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_cancel_push(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_type; + break; + case sw_settings: rc = ngx_http_v3_parse_settings(c, &st->settings, ch); @@ -1025,12 +1050,19 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) case sw_max_push_id: rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + + if (--st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + if (rc != NGX_DONE) { return rc; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse MAX_PUSH_ID:%uL", st->vlint.value); + rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } st->state = sw_type; break; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index adc40d368..a0259be11 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -11,18 +11,37 @@ /* 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_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 static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, + ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, + ngx_str_t *path, ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_create_push_request( + ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, + const char *name, ngx_str_t *value); +static void ngx_http_v3_push_request_handler(ngx_event_t *ev); +static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, + ngx_str_t *path, uint64_t push_id); struct { @@ -431,7 +450,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_buf_t *b; ngx_str_t host; ngx_uint_t i, port; - ngx_chain_t *hl, *cl, *bl; + ngx_chain_t *out, *hl, *cl, **ll; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; @@ -443,6 +462,17 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); + out = NULL; + ll = &out; + + if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + && r->method != NGX_HTTP_HEAD) + { + if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { + return NULL; + } + } + len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { @@ -796,6 +826,9 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; + *ll = hl; + ll = &cl->next; + if (r->headers_out.content_length_n >= 0 && !r->header_only) { len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + ngx_http_v3_encode_varlen_int(NULL, @@ -811,17 +844,18 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); - bl = ngx_alloc_chain_link(c->pool); - if (bl == NULL) { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { return NULL; } - bl->buf = b; - bl->next = NULL; - cl->next = bl; + cl->buf = b; + cl->next = NULL; + + *ll = cl; } - return hl; + return out; } @@ -853,3 +887,659 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r) return cl; } + + +static ngx_int_t +ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) +{ + u_char *start, *end, *last; + ngx_str_t path; + ngx_int_t rc; + ngx_uint_t i, push; + ngx_table_elt_t **h; + ngx_http_v3_loc_conf_t *h3lcf; + ngx_http_complex_value_t *pushes; + + h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); + + if (h3lcf->pushes) { + pushes = h3lcf->pushes->elts; + + for (i = 0; i < h3lcf->pushes->nelts; i++) { + + if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { + return NGX_ERROR; + } + + if (path.len == 0) { + continue; + } + + if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { + continue; + } + + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + } + + if (!h3lcf->push_preload) { + return NGX_OK; + } + + h = r->headers_out.link.elts; + + for (i = 0; i < r->headers_out.link.nelts; i++) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 parse link: \"%V\"", &h[i]->value); + + start = h[i]->value.data; + end = h[i]->value.data + h[i]->value.len; + + next_link: + + while (start < end && *start == ' ') { start++; } + + if (start == end || *start++ != '<') { + continue; + } + + while (start < end && *start == ' ') { start++; } + + for (last = start; last < end && *last != '>'; last++) { + /* void */ + } + + if (last == start || last == end) { + continue; + } + + path.len = last - start; + path.data = start; + + start = last + 1; + + while (start < end && *start == ' ') { start++; } + + if (start == end) { + continue; + } + + if (*start == ',') { + start++; + goto next_link; + } + + if (*start++ != ';') { + continue; + } + + last = ngx_strlchr(start, end, ','); + + if (last == NULL) { + last = end; + } + + push = 0; + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 6 + && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) + { + start += 6; + + if (start == last || *start == ' ' || *start == ';') { + push = 0; + break; + } + + goto next_param; + } + + if (last - start >= 11 + && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) + { + start += 11; + + if (start == last || *start == ' ' || *start == ';') { + push = 1; + } + + goto next_param; + } + + if (last - start >= 4 + && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) + { + start += 4; + + while (start < last && *start == ' ') { start++; } + + if (start == last || *start++ != '"') { + goto next_param; + } + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 7 + && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) + { + start += 7; + + if (start < last && (*start == ' ' || *start == '"')) { + push = 1; + break; + } + } + + while (start < last && *start != ' ' && *start != '"') { + start++; + } + + if (start == last) { + break; + } + + if (*start == '"') { + break; + } + + start++; + } + } + + next_param: + + start = ngx_strlchr(start, last, ';'); + + if (start == NULL) { + break; + } + + start++; + } + + if (push) { + while (path.len && path.data[path.len - 1] == ' ') { + path.len--; + } + } + + if (push && path.len + && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) + { + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + + if (last < end) { + start = last + 1; + goto next_link; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, + ngx_chain_t ***ll) +{ + uint64_t push_id; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = r->connection; + h3c = c->qs->parent->data; + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", + path, h3c->npushing, h3scf->max_concurrent_pushes, + h3c->next_push_id, h3c->max_push_id); + + if (!ngx_path_separator(path->data[0])) { + ngx_log_error(NGX_LOG_WARN, c->log, 0, + "non-absolute path \"%V\" not pushed", path); + return NGX_DECLINED; + } + + if (h3c->next_push_id > h3c->max_push_id) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_push_id"); + return NGX_ABORT; + } + + if (h3c->npushing >= h3scf->max_concurrent_pushes) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_concurrent_pushes"); + return NGX_ABORT; + } + + push_id = h3c->next_push_id++; + + rc = ngx_http_v3_create_push_request(r, path, push_id); + if (rc != NGX_OK) { + return rc; + } + + cl = ngx_http_v3_create_push_promise(r, path, push_id); + if (cl == NULL) { + return NGX_ERROR; + } + + for (**ll = cl; **ll; *ll = &(**ll)->next); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, + uint64_t push_id) +{ + ngx_pool_t *pool; + ngx_connection_t *c, *pc; + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_connection_t *h3c; + + pc = pr->connection; + + r = NULL; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "http3 create push request id:%uL", push_id); + + c = ngx_http_v3_create_push_stream(pc, push_id); + if (c == NULL) { + return NGX_ABORT; + } + + hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + goto failed; + } + + h3c = c->qs->parent->data; + ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); + c->data = hc; + + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + goto failed; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log->handler = ngx_http_log_error; + c->log->data = ctx; + c->log->action = "processing pushed request headers"; + + c->log_error = NGX_ERROR_INFO; + + r = ngx_http_create_request(c); + if (r == NULL) { + goto failed; + } + + c->data = r; + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + r->method_name = ngx_http_core_get_method; + r->method = NGX_HTTP_GET; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + goto failed; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + goto failed; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + r->schema.data = ngx_pstrdup(r->pool, &pr->schema); + if (r->schema.data == NULL) { + goto failed; + } + + r->schema.len = pr->schema.len; + + r->uri_start = ngx_pstrdup(r->pool, path); + if (r->uri_start == NULL) { + goto failed; + } + + r->uri_end = r->uri_start + path->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) + != NGX_OK) + { + goto failed; + } + + if (pr->headers_in.accept_encoding) { + if (ngx_http_v3_set_push_header(r, "accept-encoding", + &pr->headers_in.accept_encoding->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.accept_language) { + if (ngx_http_v3_set_push_header(r, "accept-language", + &pr->headers_in.accept_language->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.user_agent) { + if (ngx_http_v3_set_push_header(r, "user-agent", + &pr->headers_in.user_agent->value) + != NGX_OK) + { + goto failed; + } + } + + c->read->handler = ngx_http_v3_push_request_handler; + c->read->handler = ngx_http_v3_push_request_handler; + + ngx_post_event(c->read, &ngx_posted_events); + + return NGX_OK; + +failed: + + if (r) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, + ngx_str_t *value) +{ + u_char *p; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 push header \"%s\": \"%V\"", name, value); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + p = ngx_pnalloc(r->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) name; + h->key.len = ngx_strlen(name); + h->hash = ngx_hash_key(h->key.data, h->key.len); + h->lowcase_key = (u_char *) name; + h->value.data = p; + h->value.len = value->len; + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_push_request_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + c = ev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); + + ngx_http_process_request(r); +} + + +static ngx_chain_t * +ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, + uint64_t push_id) +{ + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create push promise id:%uL", push_id); + + len = ngx_http_v3_encode_varlen_int(NULL, push_id); + + len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + NULL, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, NULL, + r->headers_in.user_agent->value.len); + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); + + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); + + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + r->headers_in.server.data, + r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + path->data, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + r->schema.data, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, + r->headers_in.accept_encoding->value.data, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, + r->headers_in.accept_language->value.data, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, + r->headers_in.user_agent->value.data, + r->headers_in.user_agent->value.len); + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_PUSH_PROMISE); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + return hl; +} diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 2ac3f7d74..8d5147f4d 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -21,10 +21,19 @@ typedef struct { } ngx_http_v3_uni_stream_t; +typedef struct { + ngx_queue_t queue; + uint64_t id; + ngx_connection_t *connection; + ngx_uint_t *npushing; +} ngx_http_v3_push_t; + + static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); +static void ngx_http_v3_push_cleanup(void *data); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); @@ -50,6 +59,7 @@ ngx_http_v3_init_connection(ngx_connection_t *c) h3c->hc = *hc; ngx_queue_init(&h3c->blocked); + ngx_queue_init(&h3c->pushing); c->data = h3c; return NGX_OK; @@ -321,6 +331,70 @@ ngx_http_v3_dummy_write_handler(ngx_event_t *wev) /* XXX async & buffered stream writes */ +ngx_connection_t * +ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; + size_t n; + ngx_connection_t *sc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_push_t *push; + ngx_http_v3_connection_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create push stream id:%uL", push_id); + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + return NULL; + } + + p = buf; + p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); + n = p - buf; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); + if (cln == NULL) { + goto failed; + } + + h3c = c->qs->parent->data; + h3c->npushing++; + + cln->handler = ngx_http_v3_push_cleanup; + + push = cln->data; + push->id = push_id; + push->connection = sc; + push->npushing = &h3c->npushing; + + ngx_queue_insert_tail(&h3c->pushing, &push->queue); + + return sc; + +failed: + + ngx_http_v3_close_uni_stream(sc); + + return NULL; +} + + +static void +ngx_http_v3_push_cleanup(void *data) +{ + ngx_http_v3_push_t *push = data; + + ngx_queue_remove(&push->queue); + (*push->npushing)--; +} + + static ngx_connection_t * ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) { @@ -682,3 +756,64 @@ ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) return NGX_OK; } + + +ngx_int_t +ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) +{ + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 MAX_PUSH_ID:%uL", max_push_id); + + if (max_push_id < h3c->max_push_id) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + h3c->max_push_id = max_push_id; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) +{ + ngx_queue_t *q; + ngx_http_request_t *r; + ngx_http_v3_push_t *push; + ngx_http_v3_connection_t *h3c; + + h3c = c->qs->parent->data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 CANCEL_PUSH:%uL", push_id); + + if (push_id >= h3c->next_push_id) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + for (q = ngx_queue_head(&h3c->pushing); + q != ngx_queue_sentinel(&h3c->pushing); + q = ngx_queue_next(&h3c->pushing)) + { + push = (ngx_http_v3_push_t *) q; + + if (push->id != push_id) { + continue; + } + + r = push->connection->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 cancel push"); + + ngx_http_finalize_request(r, NGX_HTTP_CLOSE); + + break; + } + + return NGX_OK; +} -- cgit v1.2.3 From 007a3996cc61a288811ba3652cae95ef6270deca Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 28 Jul 2020 15:53:42 +0300 Subject: QUIC: added HTTP/3 directives list to README. Also removed server push from TODO list. --- README | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README b/README index 98f211175..a95b87838 100644 --- a/README +++ b/README @@ -62,7 +62,6 @@ Experimental QUIC support for nginx - QUIC recovery (proper congestion and flow control) - NAT Rebinding - Address Mobility - - Server push - HTTP/3 trailers Since the code is experimental and still under development, @@ -135,6 +134,15 @@ Experimental QUIC support for nginx ssl_protocols TLSv1.3; + A number of directives were added that configure HTTP/3: + + http3_max_field_size + http3_max_table_capacity + http3_max_blocked_streams + http3_max_concurrent_pushes + http3_push + http3_push_preload + Two additional variables are available: $quic and $http3. The value of $quic is "quic" if QUIC connection is used, and empty string otherwise. The value of $http3 is a string -- cgit v1.2.3 From c2e9b362ed5e82105c39748d6994d7472241dab4 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 28 Jul 2020 17:11:25 +0300 Subject: QUIC: consistent Stream ID logging format. --- src/event/ngx_event_quic.c | 16 +++++++++------- src/event/ngx_event_quic_transport.c | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a42bd50b9..6a53ef9f4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3124,7 +3124,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, if (sn == NULL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unknown stream id:%uL", f->id); + "quic unknown stream id:0x%xL", f->id); return NGX_ERROR; } @@ -3141,7 +3141,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, frame->u.max_stream_data.id = f->id; frame->u.max_stream_data.limit = n; - ngx_sprintf(frame->info, "MAX_STREAM_DATA id:%d limit:%d level=%d", + ngx_sprintf(frame->info, "MAX_STREAM_DATA id:0x%xL limit:%d level=%d", (int) frame->u.max_stream_data.id, (int) frame->u.max_stream_data.limit, frame->level); @@ -3173,7 +3173,8 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "unknown stream id:%uL", f->id); + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "unknown stream id:0x%xL", f->id); if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; @@ -3786,7 +3787,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) | NGX_QUIC_STREAM_SERVER_INITIATED; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream %uL/%uL id:%uL", + "quic creating server bidi stream %uL/%uL id:0x%xL", qc->streams.server_streams_bidi, qc->streams.server_max_streams_bidi, id); @@ -3808,7 +3809,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) | NGX_QUIC_STREAM_UNIDIRECTIONAL; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server uni stream %uL/%uL id:%uL", + "quic creating server uni stream %uL/%uL id:0x%xL", qc->streams.server_streams_uni, qc->streams.server_max_streams_uni, id); @@ -3886,7 +3887,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%uL create", id); + "quic stream id 0x%xL create", id); qc = c->quic; @@ -4044,7 +4045,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + (b->end - b->last); - ngx_sprintf(frame->info, "MAX_STREAM_DATA id:%d limit:%d l=%d on recv", + ngx_sprintf(frame->info, + "MAX_STREAM_DATA id:0x%xL limit:%d l=%d on recv", (int) frame->u.max_stream_data.id, (int) frame->u.max_stream_data.limit, frame->level); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index f1fc09449..dd50ba390 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -934,7 +934,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_STREAM_DATA id:%uL limit:%uL", + "quic frame in: MAX_STREAM_DATA id:0x%xL limit:%uL", f->u.max_stream_data.id, f->u.max_stream_data.limit); break; @@ -965,7 +965,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic frame in: STREAM_DATA_BLOCKED" - " id:%uL limit:%uL", + " id:0x%xL limit:%uL", f->u.stream_data_blocked.id, f->u.stream_data_blocked.limit); break; -- cgit v1.2.3 From 4ded4e3402b7e2ec847e55e47aaa44728a139665 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 28 Jul 2020 18:54:20 +0300 Subject: QUIC: fixed format specifiers and removed casts. --- src/event/ngx_event_quic.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 6a53ef9f4..7c7620322 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3141,9 +3141,9 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, frame->u.max_stream_data.id = f->id; frame->u.max_stream_data.limit = n; - ngx_sprintf(frame->info, "MAX_STREAM_DATA id:0x%xL limit:%d level=%d", - (int) frame->u.max_stream_data.id, - (int) frame->u.max_stream_data.limit, + ngx_sprintf(frame->info, "MAX_STREAM_DATA id:0x%xL limit:%uL level=%d", + frame->u.max_stream_data.id, + frame->u.max_stream_data.limit, frame->level); ngx_quic_queue_frame(c->quic, frame); @@ -4046,9 +4046,9 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) + (b->end - b->last); ngx_sprintf(frame->info, - "MAX_STREAM_DATA id:0x%xL limit:%d l=%d on recv", - (int) frame->u.max_stream_data.id, - (int) frame->u.max_stream_data.limit, + "MAX_STREAM_DATA id:0x%xL limit:%uL l=%d on recv", + frame->u.max_stream_data.id, + frame->u.max_stream_data.limit, frame->level); ngx_quic_queue_frame(pc->quic, frame); @@ -4068,8 +4068,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->type = NGX_QUIC_FT_MAX_DATA; frame->u.max_data.max_data = qc->streams.recv_max_data; - ngx_sprintf(frame->info, "MAX_DATA max_data:%d level=%d on recv", - (int) frame->u.max_data.max_data, frame->level); + ngx_sprintf(frame->info, "MAX_DATA max_data:%uL level=%d on recv", + frame->u.max_data.max_data, frame->level); ngx_quic_queue_frame(pc->quic, frame); -- cgit v1.2.3 From 7d1a1fb6de71ba0ad7ac324a6c43a10a0f5d8ae6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 7 Aug 2020 12:34:11 +0300 Subject: QUIC: fixed possible use-after-free on stream cleanup. A QUIC stream could be destroyed by handler while in ngx_quic_stream_input(). To detect this, ngx_quic_find_stream() is used to check that it still exists. Previously, a stream id was passed to this routine off the frame structure. In case of stream cleanup, it is freed along with other frames belonging to the stream on cleanup. Then, a cleanup handler reuses last frames to update MAX_STREAMS and serve other purpose. Thus, ngx_quic_find_stream() is passed a reused frame with zeroed out part pointed by stream_id. If a stream with id 0x0 still exists, this leads to use-after-free. --- src/event/ngx_event_quic.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7c7620322..47c52067e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3006,6 +3006,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { + uint64_t id; ngx_buf_t *b; ngx_event_t *rev; ngx_quic_stream_t *sn; @@ -3016,6 +3017,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) sn = data; f = &frame->u.stream; + id = f->stream_id; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic existing stream"); @@ -3046,7 +3048,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } /* check if stream was destroyed by handler */ - if (ngx_quic_find_stream(&qc->streams.tree, f->stream_id) == NULL) { + if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { return NGX_DONE; } -- cgit v1.2.3 From e4ca695700b996f1347662a2666881fdc09ea703 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 7 Aug 2020 12:34:15 +0300 Subject: QUIC: fixed ACK Ranges processing. According to quic-transport draft 29, section 19.3.1: The value of the Gap field establishes the largest packet number value for the subsequent ACK Range using the following formula: largest = previous_smallest - gap - 2 Thus, given a largest packet number for the range, the smallest value is determined by the formula: smallest = largest - ack_range While here, changed min/max to uint64_t for consistency. --- src/event/ngx_event_quic.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 47c52067e..866c7ea91 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2289,9 +2289,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, { ssize_t n; u_char *pos, *end; - uint64_t gap, range; + uint64_t min, max, gap, range; ngx_msec_t send_time; - ngx_uint_t i, min, max; + ngx_uint_t i; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -2328,7 +2328,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (ctx->largest_ack < max) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic updated largest received ack: %ui", max); + "quic updated largest received ack: %uL", max); /* * An endpoint generates an RTT sample on receiving an @@ -2354,23 +2354,23 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } pos += n; - if (gap >= min) { + if (gap + 2 > min) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; } - max = min - 1 - gap; + max = min - gap - 2; - if (range > max + 1) { + if (range > max) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid range %ui in ack frame", i); return NGX_ERROR; } - min = max - range + 1; + min = max - range; if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) != NGX_OK) @@ -2393,6 +2393,9 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_frame_t *f; ngx_quic_connection_t *qc; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle ack range: min:%uL max:%uL", min, max); + qc = c->quic; *send_time = NGX_TIMER_INFINITE; -- cgit v1.2.3 From 68c5d80ee5381db9ea20e2ef247153e300fe837c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 11 Aug 2020 10:41:39 +0300 Subject: QUIC: fixed ngx_http_test_reading() for QUIC streams. Previously this function generated an error trying to figure out if client shut down the write end of the connection. The reason for this error was that a QUIC stream has no socket descriptor. However checking for eof is not the right thing to do for an HTTP/3 QUIC stream since HTTP/3 clients are expected to shut down the write end of the stream after sending the request. Now the function handles QUIC streams separately. It checks if c->read->error is set. The error flags for c->read and c->write are now set for all streams when closing the QUIC connection instead of setting the pending_eof flag. --- src/event/ngx_event_quic.c | 16 +++++++++++++--- src/http/ngx_http_request.c | 13 +++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 866c7ea91..3584546b3 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1460,7 +1460,7 @@ ngx_quic_close_timer_handler(ngx_event_t *ev) static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { - ngx_event_t *rev; + ngx_event_t *rev, *wev; ngx_rbtree_t *tree; ngx_rbtree_node_t *node; ngx_quic_stream_t *qs; @@ -1486,8 +1486,12 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) qs = (ngx_quic_stream_t *) node; rev = qs->c->read; + rev->error = 1; rev->ready = 1; - rev->pending_eof = 1; + + wev = qs->c->write; + wev->error = 1; + wev->ready = 1; ngx_post_event(rev, &ngx_posted_events); @@ -4005,6 +4009,10 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) qc = pc->quic; rev = c->read; + if (rev->error) { + return NGX_ERROR; + } + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id 0x%xL recv: eof:%d, avail:%z", qs->id, rev->pending_eof, b->last - b->pos); @@ -4093,6 +4101,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) u_char *p, *end; size_t fsize, limit, n, len; uint64_t sent, unacked; + ngx_event_t *wev; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -4101,8 +4110,9 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) qs = c->qs; pc = qs->parent; qc = pc->quic; + wev = c->write; - if (qc->closing) { + if (wev->error) { return NGX_ERROR; } diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 54e5e2866..30a22fa22 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3022,6 +3022,19 @@ ngx_http_test_reading(ngx_http_request_t *r) #endif +#if (NGX_HTTP_QUIC) + + if (c->qs) { + if (c->read->error) { + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { -- cgit v1.2.3 From e97c50cdd6a33954a39fb2d0d9b116e318a26659 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 11 Aug 2020 19:10:57 +0300 Subject: QUIC: create streams for STREAM_DATA_BLOCKED and MAX_STREAM_DATA. Creating client-initiated streams is moved from ngx_quic_handle_stream_frame() to a separate function ngx_quic_create_client_stream(). This function is responsible for creating streams with lower ids as well. Also, simplified and fixed initial data buffering in ngx_quic_handle_stream_frame(). It is now done before calling the initial handler as the handler can destroy the stream. --- src/event/ngx_event_quic.c | 235 +++++++++++++++++++++++++++------------------ 1 file changed, 144 insertions(+), 91 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3584546b3..8650103b1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -34,6 +34,8 @@ */ #define NGX_QUIC_MAX_BUFFERED 65535 +#define NGX_QUIC_STREAM_GONE (void *) -1 + typedef struct { ngx_rbtree_t tree; @@ -270,6 +272,8 @@ static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id); +static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, + uint64_t id); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size); static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, @@ -2893,10 +2897,8 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - size_t n; - uint64_t id; - ngx_buf_t *b; - ngx_event_t *rev; + ngx_pool_t *pool; + ngx_connection_t *sc; ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -2915,92 +2917,34 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); if (sn == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL is new", f->stream_id); + sn = ngx_quic_create_client_stream(c, f->stream_id); - if (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED) { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + if (sn == NULL) { return NGX_ERROR; } - if (f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if ((f->stream_id >> 2) < qc->streams.client_streams_uni) { - return NGX_OK; - } - - if ((f->stream_id >> 2) >= qc->streams.client_max_streams_uni) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NGX_ERROR; - } - - id = (qc->streams.client_streams_uni << 2) - | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client_streams_uni = (f->stream_id >> 2) + 1; - n = qc->tp.initial_max_stream_data_uni; - - } else { - if ((f->stream_id >> 2) < qc->streams.client_streams_bidi) { - return NGX_OK; - } - - if ((f->stream_id >> 2) >= qc->streams.client_max_streams_bidi) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NGX_ERROR; - } - - id = (qc->streams.client_streams_bidi << 2); - qc->streams.client_streams_bidi = (f->stream_id >> 2) + 1; - n = qc->tp.initial_max_stream_data_bidi_remote; - } - - if (n < NGX_QUIC_STREAM_BUFSIZE) { - n = NGX_QUIC_STREAM_BUFSIZE; - } - - if (n < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no space in stream buffer"); - return NGX_ERROR; + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } - /* - * 2.1. Stream Types and Identifiers - * - * Within each type, streams are created with numerically increasing - * stream IDs. A stream ID that is used out of order results in all - * streams of that type with lower-numbered stream IDs also being - * opened. - */ - - for ( /* void */ ; id <= f->stream_id; id += 0x04) { - - sn = ngx_quic_create_stream(c, id, n); - if (sn == NULL) { - return NGX_ERROR; - } - - if (id == f->stream_id && f->offset == 0) { - b = sn->b; - b->last = ngx_cpymem(b->last, f->data, f->length); - - sn->fs.received += f->length; + sc = sn->c; + fs = &sn->fs; - rev = sn->c->read; - rev->ready = 1; + if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn) + != NGX_OK) + { + pool = sc->pool; - if (f->fin) { - rev->pending_eof = 1; - } - } + ngx_close_connection(sc); + ngx_destroy_pool(pool); - sn->c->listening->handler(sn->c); + return NGX_ERROR; } - if (f->offset == 0) { - return NGX_OK; - } + sc->listening->handler(sc); - /* out-of-order stream: proceed to buffering */ + return NGX_OK; } fs = &sn->fs; @@ -3026,8 +2970,6 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) f = &frame->u.stream; id = f->stream_id; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic existing stream"); - b = sn->b; if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { @@ -3132,13 +3074,25 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unknown stream id:0x%xL", f->id); - return NGX_ERROR; - } + sn = ngx_quic_create_client_stream(c, f->id); - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + b = sn->b; + n = b->end - b->last; + + sn->c->listening->handler(sn->c); + + } else { + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + } frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { @@ -3182,14 +3136,23 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "unknown stream id:0x%xL", f->id); + sn = ngx_quic_create_client_stream(c, f->id); - if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + if (sn == NULL) { + return NGX_ERROR; } - return NGX_ERROR; + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit > sn->send_max_data) { + sn->send_max_data = f->limit; + } + + sn->c->listening->handler(sn->c); + + return NGX_OK; } if (f->limit <= sn->send_max_data) { @@ -3886,6 +3849,96 @@ ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) } +static ngx_quic_stream_t * +ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) +{ + size_t n; + uint64_t min_id; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id 0x%xL is new", id); + + qc = c->quic; + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + n = qc->tp.initial_max_stream_data_uni; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_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; + n = qc->tp.initial_max_stream_data_bidi_remote; + } + + if (n < NGX_QUIC_STREAM_BUFSIZE) { + n = NGX_QUIC_STREAM_BUFSIZE; + } + + /* + * 2.1. Stream Types and Identifiers + * + * Within each type, streams are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being + * opened. + */ + + for ( /* void */ ; min_id < id; min_id += 0x04) { + + sn = ngx_quic_create_stream(c, min_id, n); + if (sn == NULL) { + return NULL; + } + + sn->c->listening->handler(sn->c); + } + + return ngx_quic_create_stream(c, id, n); +} + + static ngx_quic_stream_t * ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) { -- cgit v1.2.3 From cb0e3a2658d8b0be313bddd07ebb2599253f9856 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 3 Aug 2020 13:31:48 +0300 Subject: QUIC: handle client RESET_STREAM and STOP_SENDING. For RESET_STREAM the c->read->error flag is set. For STOP_SENDING the c->write->error flag is set. --- src/event/ngx_event_quic.c | 75 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 8650103b1..65b949387 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3180,16 +3180,52 @@ static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic frame handler not implemented"); + ngx_event_t *rev; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = c->quic; if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) { - c->quic->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; return NGX_ERROR; } + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + rev = sc->read; + rev->error = 1; + rev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + rev = sn->c->read; + rev->error = 1; + rev->ready = 1; + + if (rev->active) { + rev->handler(rev); + } + return NGX_OK; } @@ -3198,12 +3234,11 @@ static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) { + ngx_event_t *wev; + ngx_connection_t *sc; ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic frame handler not implemented"); - qc = c->quic; if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) @@ -3216,13 +3251,33 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, sn = ngx_quic_find_stream(&qc->streams.tree, f->id); if (sn == NULL) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL is new", f->id); + sn = ngx_quic_create_client_stream(c, f->id); - if (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + if (sn == NULL) { return NGX_ERROR; } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + wev = sc->write; + wev->error = 1; + wev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + wev = sn->c->write; + wev->error = 1; + wev->ready = 1; + + if (wev->active) { + wev->handler(wev); } return NGX_OK; -- cgit v1.2.3 From f1b0afde65c3f697bc8f77f50705b06e805402ee Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 14 Aug 2020 16:53:56 +0300 Subject: QUIC: fixed leak of bytes_in_flight attributed to lost packets. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 65b949387..a3189e264 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3764,6 +3764,7 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) q = ngx_queue_next(q); ngx_queue_remove(&f->queue); + qc->congestion.in_flight -= f->len; ngx_queue_insert_tail(&range, &f->queue); } while (q != ngx_queue_sentinel(&ctx->sent)); -- cgit v1.2.3 From 81e9a5d77c95ef0df62326a7f1761b990d074aa7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 14 Aug 2020 16:54:06 +0300 Subject: QUIC: fixed leak of bytes_in_flight on keys discard. This applies to discarding Initial and Handshake keys. --- src/event/ngx_event_quic.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a3189e264..c37b2b482 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1735,6 +1735,8 @@ static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_int_t rc; + ngx_queue_t *q; + ngx_quic_frame_t *f; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1782,7 +1784,15 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) * that no more Initial packets need to be exchanged */ ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial); - ngx_quic_free_frames(c, &ctx->sent); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } qc->validated = 1; qc->pto_count = 0; @@ -2801,6 +2811,7 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { int n, sslerr; + ngx_queue_t *q; ngx_ssl_conn_t *ssl_conn; ngx_quic_send_ctx_t *ctx; ngx_quic_crypto_frame_t *f; @@ -2879,7 +2890,15 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) * when the TLS handshake is confirmed */ ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_handshake); - ngx_quic_free_frames(c, &ctx->sent); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, frame); + ngx_quic_free_frame(c, frame); + } c->quic->pto_count = 0; } -- cgit v1.2.3 From 6e17937db4f7419427333e9afd66c9819e4f9aaf Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 14 Aug 2020 16:54:13 +0300 Subject: QUIC: packet based bytes_in_flight accounting. A packet size is kept in one of the frames belonging to the packet. --- src/event/ngx_event_quic.c | 38 +++++++++++++++++++++++++++--------- src/event/ngx_event_quic_transport.h | 1 + 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c37b2b482..f9bd5bdf0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -288,7 +288,8 @@ static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *frame); -static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_msec_t sent); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); static SSL_QUIC_METHOD quic_method = { @@ -3627,9 +3628,11 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_del_timer(&qc->pto); } ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); + + start->plen = len; } - qc->congestion.in_flight += out.len; + qc->congestion.in_flight += len; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion send if:%uz", @@ -3783,12 +3786,11 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) q = ngx_queue_next(q); ngx_queue_remove(&f->queue); - qc->congestion.in_flight -= f->len; ngx_queue_insert_tail(&range, &f->queue); } while (q != ngx_queue_sentinel(&ctx->sent)); - ngx_quic_congestion_lost(c, start->last); + ngx_quic_congestion_lost(c, start); if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { return NGX_ERROR; @@ -4535,26 +4537,34 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; + if (f->plen == 0) { + return; + } + qc = c->quic; cg = &qc->congestion; - cg->in_flight -= f->len; + cg->in_flight -= f->plen; timer = f->last - cg->recovery_start; if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz, ss:%uz, if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + return; } if (cg->window < cg->ssthresh) { - cg->window += f->len; + cg->window += f->plen; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion slow start win:%uz, ss:%uz, if:%uz", cg->window, cg->ssthresh, cg->in_flight); } else { - cg->window += qc->tp.max_udp_payload_size * f->len / cg->window; + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion avoidance win:%uz, ss:%uz, if:%uz", @@ -4572,18 +4582,28 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) static void -ngx_quic_congestion_lost(ngx_connection_t *c, ngx_msec_t sent) +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) { ngx_msec_t timer; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; + if (f->plen == 0) { + return; + } + qc = c->quic; cg = &qc->congestion; - timer = sent - cg->recovery_start; + cg->in_flight -= f->plen; + + timer = f->last - cg->recovery_start; if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz, ss:%uz, if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + return; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index f1a278769..b1dd5aef5 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -248,6 +248,7 @@ struct ngx_quic_frame_s { enum ssl_encryption_level_t level; ngx_queue_t queue; uint64_t pnum; + size_t plen; ngx_msec_t first; ngx_msec_t last; ssize_t len; -- cgit v1.2.3 From ff1941d6ddb014da8b085c7ca9ba1098b4ec35a5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 18 Aug 2020 12:28:33 +0300 Subject: QUIC: coalesce neighbouring stream send buffers. Previously a single STREAM frame was created for each buffer in stream output chain which is wasteful with respect to memory. The following changes were made in the stream send code: - ngx_quic_stream_send_chain() no longer calls ngx_quic_stream_send() and got a separate implementation that coalesces neighbouring buffers into a single frame - the new ngx_quic_stream_send_chain() respects the limit argument, which fixes sendfile_max_chunk and limit_rate - ngx_quic_stream_send() is reimplemented to call ngx_quic_stream_send_chain() - stream frame size limit is moved out to a separate function ngx_quic_max_stream_frame() - flow control is moved out to a separate function ngx_quic_max_stream_flow() - ngx_quic_stream_send_chain() is relocated next to ngx_quic_stream_send() --- src/event/ngx_event_quic.c | 276 +++++++++++++++++++++++++++------------------ 1 file changed, 166 insertions(+), 110 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f9bd5bdf0..42650ab1a 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -280,9 +280,11 @@ 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 void ngx_quic_stream_cleanup_handler(void *data); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); +static size_t ngx_quic_max_stream_frame(ngx_quic_connection_t *qc); +static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); +static void ngx_quic_stream_cleanup_handler(void *data); static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c, size_t size); static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); @@ -4228,10 +4230,44 @@ 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) { - u_char *p, *end; - size_t fsize, limit, n, len; - uint64_t sent, unacked; + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + u_char *p; + size_t n, max, max_frame, max_flow, max_limit, len; +#if (NGX_DEBUG) + size_t sent; +#endif + ngx_buf_t *b; +#if (NGX_DEBUG) + ngx_uint_t nframes; +#endif ngx_event_t *wev; + ngx_chain_t *cl; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -4243,69 +4279,49 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) wev = c->write; if (wev->error) { - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL send: %uz", qs->id, size); - - /* - * we need to fit at least 1 frame into a packet, thus account head/tail; - * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type - */ - limit = qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_SHORT_HEADER - 25 - - EVP_GCM_TLS_TAG_LEN; - - len = size; - sent = c->sent; - unacked = sent - qs->acked; - - if (qc->streams.send_max_data == 0) { - qc->streams.send_max_data = qc->ctp.initial_max_data; + return NGX_CHAIN_ERROR; } - if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send hit buffer size"); - len = 0; + max_frame = ngx_quic_max_stream_frame(qc); + max_flow = ngx_quic_max_stream_flow(c); + max_limit = limit; - } else if (unacked + len > NGX_QUIC_STREAM_BUFSIZE) { - len = NGX_QUIC_STREAM_BUFSIZE - unacked; - } +#if (NGX_DEBUG) + sent = 0; + nframes = 0; +#endif - if (qc->streams.sent >= qc->streams.send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send hit MAX_DATA"); - len = 0; + for ( ;; ) { + max = ngx_min(max_frame, max_flow); - } else if (qc->streams.sent + len > qc->streams.send_max_data) { - len = qc->streams.send_max_data - qc->streams.sent; - } + if (limit) { + max = ngx_min(max, max_limit); + } - if (sent >= qs->send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send hit MAX_STREAM_DATA"); - len = 0; + for (cl = in, n = 0; in; in = in->next) { - } else if (sent + len > qs->send_max_data) { - len = qs->send_max_data - sent; - } + if (!ngx_buf_in_memory(in->buf)) { + continue; + } - p = (u_char *) buf; - end = (u_char *) buf + len; - n = 0; + n += ngx_buf_size(in->buf); - while (p < end) { + if (n > max) { + n = max; + break; + } + } - fsize = ngx_min(limit, (size_t) (end - p)); + if (n == 0) { + wev->ready = (max_flow ? 1 : 0); + break; + } - frame = ngx_quic_alloc_frame(pc, fsize); + frame = ngx_quic_alloc_frame(pc, n); if (frame == NULL) { - return 0; + return NGX_CHAIN_ERROR; } - ngx_memcpy(frame->data, p, fsize); - frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ frame->u.stream.off = 1; @@ -4315,33 +4331,115 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) frame->u.stream.type = frame->type; frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; - frame->u.stream.length = fsize; + frame->u.stream.length = n; frame->u.stream.data = frame->data; - c->sent += fsize; - qc->streams.sent += fsize; - p += fsize; - n += fsize; + ngx_sprintf(frame->info, "STREAM id:0x%xL len:%uz level:%d", + qs->id, n, frame->level); + + c->sent += n; + qc->streams.sent += n; + max_flow -= n; + + if (limit) { + max_limit -= n; + } - ngx_sprintf(frame->info, "stream 0x%xL len=%ui level=%d", - qs->id, fsize, frame->level); +#if (NGX_DEBUG) + sent += n; + nframes++; +#endif + + for (p = frame->data; n > 0; cl = cl->next) { + b = cl->buf; + + if (!ngx_buf_in_memory(b)) { + continue; + } + + len = ngx_min(n, (size_t) (b->last - b->pos)); + p = ngx_cpymem(p, b->pos, len); + + b->pos += len; + n -= len; + } ngx_quic_queue_frame(qc, frame); } - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send %uz of %uz, sent:%O, unacked:%uL", - n, size, c->sent, (uint64_t) c->sent - qs->acked); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uz, frames:%ui", sent, nframes); + + return in; +} + + +static size_t +ngx_quic_max_stream_frame(ngx_quic_connection_t *qc) +{ + /* + * we need to fit at least 1 frame into a packet, thus account head/tail; + * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type + */ + + return qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_SHORT_HEADER - 25 + - EVP_GCM_TLS_TAG_LEN; +} + + +static size_t +ngx_quic_max_stream_flow(ngx_connection_t *c) +{ + size_t size; + uint64_t sent, unacked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->qs; + qc = qs->parent->quic; + + size = NGX_QUIC_STREAM_BUFSIZE; + sent = c->sent; + unacked = sent - qs->acked; + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } - if (n != size) { - c->write->ready = 0; + if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit buffer size"); + return 0; } - if (n == 0) { - return NGX_AGAIN; + if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { + size = NGX_QUIC_STREAM_BUFSIZE - unacked; } - return n; + if (qc->streams.sent >= qc->streams.send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_DATA"); + return 0; + } + + if (qc->streams.sent + size > qc->streams.send_max_data) { + size = qc->streams.send_max_data - qc->streams.sent; + } + + if (sent >= qs->send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_STREAM_DATA"); + return 0; + } + + if (sent + size > qs->send_max_data) { + size = qs->send_max_data - sent; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow: %uz", size); + + return size; } @@ -4431,48 +4529,6 @@ ngx_quic_stream_cleanup_handler(void *data) } -static ngx_chain_t * -ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, - off_t limit) -{ - size_t len; - ssize_t n; - ngx_buf_t *b; - - for ( /* void */; in; in = in->next) { - b = in->buf; - - if (!ngx_buf_in_memory(b)) { - continue; - } - - if (ngx_buf_size(b) == 0) { - continue; - } - - len = b->last - b->pos; - - n = ngx_quic_stream_send(c, b->pos, len); - - if (n == NGX_ERROR) { - return NGX_CHAIN_ERROR; - } - - if (n == NGX_AGAIN) { - return in; - } - - b->pos += n; - - if (n != (ssize_t) len) { - return in; - } - } - - return NULL; -} - - static ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) { -- cgit v1.2.3 From 9a0fb643bfcea20f8a3ede222f9a1d8ec234a2cd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 18 Aug 2020 17:11:32 +0300 Subject: HTTP/3: fixed context storage in request body parser. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a0259be11..24ad771d6 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -392,7 +392,7 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, goto failed; } - r->h3_parse = st; + ctx->h3_parse = st; } if (ctx->size) { -- cgit v1.2.3 From 160242dd2e8544f21056056ce1b36b209bfaea63 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 18 Aug 2020 23:33:40 +0300 Subject: QUIC: changed ctx->largest_ack initial value to type maximum. In particular, this prevents declaring packet number 0 as lost if there aren't yet any acknowledgements in this packet number space. For example, only Initial packets were acknowledged in handshake. --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 42650ab1a..94097c4d9 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -690,6 +690,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_queue_init(&qc->send_ctx[i].frames); ngx_queue_init(&qc->send_ctx[i].sent); qc->send_ctx[i].largest_pn = (uint64_t) -1; + qc->send_ctx[i].largest_ack = (uint64_t) -1; } for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { @@ -2346,7 +2347,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ctx->largest_ack < max) { + if (ctx->largest_ack < max || ctx->largest_ack == (uint64_t) -1) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic updated largest received ack: %uL", max); -- cgit v1.2.3 From fa82dccd2aab6dcb090b992e199448c850972dff Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 13:24:23 +0300 Subject: QUIC: sending probe packets on PTO timer expiration. The PTO handler is split into separate PTO and loss detection handlers that operate interchangeably depending on which timer should be set. The present ngx_quic_lost_handler is now only used for packet loss detection. It replaces ngx_quic_pto_handler if there are packets preceeding largest_ack. Once there is no more such packets, ngx_quic_pto_handler is installed again. Probes carry unacknowledged data previously sent in the oldest packet number, one per each packet number space. That is, it could be up to two probes. PTO backoff is now increased before scheduling next probes. --- src/event/ngx_event_quic.c | 168 +++++++++++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 59 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 94097c4d9..51d32eb83 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -265,7 +265,10 @@ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); static void ngx_quic_pto_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack); +static void ngx_quic_lost_handler(ngx_event_t *ev); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); +static ngx_int_t ngx_quic_resend_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, ngx_quic_frame_t *start); static void ngx_quic_push_handler(ngx_event_t *ev); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, @@ -2401,7 +2404,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } - return ngx_quic_detect_lost(c, 1); + return ngx_quic_detect_lost(c); } @@ -3682,18 +3685,46 @@ ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) static void ngx_quic_pto_handler(ngx_event_t *ev) { - ngx_connection_t *c; + ngx_uint_t i; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_quic_frame_t *start; + 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 = c->quic; - if (ngx_quic_detect_lost(c, 0) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } + qc->pto_count++; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum <= ctx->largest_ack + && ctx->largest_ack != (uint64_t) -1) + { + continue; + } - c->quic->pto_count++; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto pnum:%ui pto_count:%ui level:%d", + start->pnum, c->quic->pto_count, start->level); + + if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + } } @@ -3713,14 +3744,28 @@ ngx_quic_push_handler(ngx_event_t *ev) } +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } +} + + static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) +ngx_quic_detect_lost(ngx_connection_t *c) { - uint64_t pn; ngx_uint_t i; ngx_msec_t now, wait, min_wait, thr; - ngx_queue_t *q, range; - ngx_quic_frame_t *f, *start; + ngx_queue_t *q; + ngx_quic_frame_t *start; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -3729,88 +3774,93 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_uint_t ack) min_wait = 0; -#if (NGX_SUPPRESS_WARN) - thr = 0; -#endif - - if (ack) { - thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); - thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); - } + thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); + thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - if (!ack) { - thr = ngx_quic_pto(c, ctx); - } - - q = ngx_queue_head(&ctx->sent); + while (!ngx_queue_empty(&ctx->sent)) { - do { + q = ngx_queue_head(&ctx->sent); start = ngx_queue_data(q, ngx_quic_frame_t, queue); wait = start->last + thr - now; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%ui thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + if ((ngx_msec_int_t) wait >= 0) { if (min_wait == 0 || wait < min_wait) { min_wait = wait; } - if (!ack) { - break; - } - if ((start->pnum > ctx->largest_ack) + || ctx->largest_ack == (uint64_t) -1 || ((ctx->largest_ack - start->pnum) < NGX_QUIC_PKT_THR)) { break; } } - pn = start->pnum; + if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { + return NGX_ERROR; + } + } + } - ngx_queue_init(&range); + /* no more preceeding packets */ - /* send frames with same packet number to the wire */ - do { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); + if (min_wait == 0) { + qc->pto.handler = ngx_quic_pto_handler; + return NGX_OK; + } - if (f->pnum != pn) { - break; - } + qc->pto.handler = ngx_quic_lost_handler; - q = ngx_queue_next(q); + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } - ngx_queue_remove(&f->queue); - ngx_queue_insert_tail(&range, &f->queue); + ngx_add_timer(&qc->pto, min_wait); - } while (q != ngx_queue_sentinel(&ctx->sent)); + return NGX_OK; +} - ngx_quic_congestion_lost(c, start); - if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { - return NGX_ERROR; - } +static ngx_int_t +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_frame_t *start) +{ + ngx_queue_t *q, range; + ngx_quic_frame_t *f; - } while (q != ngx_queue_sentinel(&ctx->sent)); - } + ngx_queue_init(&range); - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } + /* send frames with same packet number to the wire */ - if (min_wait > 0) { - ngx_add_timer(&qc->pto, min_wait); - } + q = ngx_queue_head(&ctx->sent); - return NGX_OK; + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != start->pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + ngx_queue_insert_tail(&range, &f->queue); + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + ngx_quic_congestion_lost(c, start); + + return ngx_quic_send_frames(c, ctx, &range); } -- cgit v1.2.3 From 3bf7b02e6eccb6e51161693d887fccac18afaf38 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 13:24:30 +0300 Subject: QUIC: handling packets with send time equal to lost send time. Previously, such packets weren't handled as the resulting zero remaining time prevented setting the loss detection timer, which, instead, could be disarmed. For implementation details, see quic-recovery draft 29, appendix A.10. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 51d32eb83..f0f07bf38 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3792,7 +3792,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) "quic detect_lost pnum:%ui thr:%M wait:%i level:%d", start->pnum, thr, (ngx_int_t) wait, start->level); - if ((ngx_msec_int_t) wait >= 0) { + if ((ngx_msec_int_t) wait > 0) { if (min_wait == 0 || wait < min_wait) { min_wait = wait; -- cgit v1.2.3 From 391abc00c9ead945635f8b9b6a35e50eeb4dc3e2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 13:24:47 +0300 Subject: QUIC: do not arm loss detection timer for succeeding packets. --- src/event/ngx_event_quic.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f0f07bf38..0781bcfb0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3781,11 +3781,19 @@ ngx_quic_detect_lost(ngx_connection_t *c) ctx = &qc->send_ctx[i]; + if (ctx->largest_ack == (uint64_t) -1) { + continue; + } + while (!ngx_queue_empty(&ctx->sent)) { q = ngx_queue_head(&ctx->sent); start = ngx_queue_data(q, ngx_quic_frame_t, queue); + if (start->pnum > ctx->largest_ack) { + break; + } + wait = start->last + thr - now; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3798,10 +3806,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) min_wait = wait; } - if ((start->pnum > ctx->largest_ack) - || ctx->largest_ack == (uint64_t) -1 - || ((ctx->largest_ack - start->pnum) < NGX_QUIC_PKT_THR)) - { + if (ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) { break; } } -- cgit v1.2.3 From 3b83a140ff2710911f6f3251e0d3a7f1ec3f1ac0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 13:24:53 +0300 Subject: QUIC: do not arm loss detection timer on packet threshold. --- src/event/ngx_event_quic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0781bcfb0..ef57f6cad 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3800,15 +3800,15 @@ ngx_quic_detect_lost(ngx_connection_t *c) "quic detect_lost pnum:%ui thr:%M wait:%i level:%d", start->pnum, thr, (ngx_int_t) wait, start->level); - if ((ngx_msec_int_t) wait > 0) { + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { if (min_wait == 0 || wait < min_wait) { min_wait = wait; } - if (ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) { - break; - } + break; } if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { -- cgit v1.2.3 From f760147d9de72bf162d354807dc8b16522d9433c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 13:24:54 +0300 Subject: QUIC: do not artificially delay sending queued frames. This interacts badly with retransmissions of lost packets and can provoke spurious client retransmits. --- src/event/ngx_event_quic.c | 16 +--------------- src/event/ngx_event_quic.h | 4 ---- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ef57f6cad..5a049ed3f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -84,8 +84,6 @@ typedef struct { ngx_queue_t frames; ngx_queue_t sent; - - size_t frames_len; } ngx_quic_send_ctx_t; @@ -3351,22 +3349,11 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) frame->len = ngx_quic_create_frame(NULL, frame); /* always succeeds */ - ctx->frames_len += frame->len; - if (qc->closing) { return; } - /* TODO: TCP_NODELAY analogue ? TCP_CORK and others... */ - - if (ctx->frames_len < NGX_QUIC_MIN_DATA_NODELAY) { - if (!qc->push.timer_set) { - ngx_add_timer(&qc->push, qc->tp.max_ack_delay); - } - - } else { - ngx_post_event(&qc->push, &ngx_posted_events); - } + ngx_post_event(&qc->push, &ngx_posted_events); } @@ -3463,7 +3450,6 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_queue_remove(&f->queue); ngx_queue_insert_tail(&range, &f->queue); - ctx->frames_len -= f->len; len += f->len; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 2d43defef..9646b03ac 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -49,10 +49,6 @@ #define NGX_QUIC_MIN_INITIAL_SIZE 1200 -/* if we have so much data, send immediately */ -/* TODO: configurable ? */ -#define NGX_QUIC_MIN_DATA_NODELAY 512 /* bytes */ - #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 -- cgit v1.2.3 From a4e06606c5fdfa846b21de5aa0b5916c55367a27 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 15:58:03 +0300 Subject: QUIC: changed c->quic->pto_count type to ngx_uint_t. This field is served as a simple counter for PTO backoff. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5a049ed3f..0cb573130 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -117,7 +117,7 @@ struct ngx_quic_connection_s { ngx_msec_t min_rtt; ngx_msec_t rttvar; - ngx_msec_t pto_count; + ngx_uint_t pto_count; #if (NGX_DEBUG) ngx_uint_t nframes; -- cgit v1.2.3 From 338c4015340f0eab37892c5a0449f86173b530a6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 19 Aug 2020 16:00:12 +0300 Subject: QUIC: fixed format specifiers. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0cb573130..53054ab5f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3703,7 +3703,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pto pnum:%ui pto_count:%ui level:%d", + "quic pto pnum:%uL pto_count:%ui level:%d", start->pnum, c->quic->pto_count, start->level); if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { @@ -3783,7 +3783,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) wait = start->last + thr - now; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic detect_lost pnum:%ui thr:%M wait:%i level:%d", + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", start->pnum, thr, (ngx_int_t) wait, start->level); if ((ngx_msec_int_t) wait > 0 -- cgit v1.2.3 From 7ce1a68aadec80a2c4a3b27f3fd3d7342043299b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 18 Aug 2020 17:23:16 +0300 Subject: HTTP/3: request more client body bytes. Previously the request body DATA frame header was read by one byte because filters were called only when the requested number of bytes were read. Now, after 08ff2e10ae92 (1.19.2), filters are called after each read. More bytes can be read at once, which simplifies and optimizes the code. This also reduces diff with the default branch. --- src/http/ngx_http_request_body.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index 204253ca2..d0fcb00f4 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -1029,12 +1029,6 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) r->headers_in.content_length_n = 0; rb->rest = cscf->large_client_header_buffers.size; - -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - rb->rest = 1; - } -#endif } out = NULL; -- cgit v1.2.3 From 5a4aaa6aed4d4b97d09548035298a90bf984a997 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 20 Aug 2020 12:33:00 +0300 Subject: HTTP/3: special handling of client errors in the upstream module. The function ngx_http_upstream_check_broken_connection() terminates the HTTP/1 request if client sends eof. For QUIC (including HTTP/3) the c->write->error flag is now checked instead. This flag is set when the entire QUIC connection is closed or STOP_SENDING was received from client. --- src/http/ngx_http_upstream.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 47f98ccb2..88e7b6fad 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -1345,6 +1345,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_QUIC) + + if (c->qs) { + if (c->write->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { -- cgit v1.2.3 From 4ecea6cbedb723d2edbed9a3178bcbc603c6b108 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 20 Aug 2020 17:11:04 +0300 Subject: QUIC: added version negotiation support. If a client attemtps to start a new connection with unsupported version, a version negotiation packet is sent that contains a list of supported versions (currently this is a single version, selected at compile time). --- src/event/ngx_event_quic.c | 59 ++++++++++++++++++++++++++++++++++++ src/event/ngx_event_quic_transport.c | 49 +++++++++++++++++++++++++----- src/event/ngx_event_quic_transport.h | 2 ++ 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 53054ab5f..9930a909d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -169,6 +169,8 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, + ngx_quic_header_t *inpkt); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); static ngx_int_t ngx_quic_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); @@ -659,6 +661,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return rc; } + if (pkt->version != NGX_QUIC_VERSION) { + return ngx_quic_negotiate_version(c, pkt); + } + if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid initial packet: 0x%xd", pkt->flags); @@ -823,6 +829,35 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, } +static ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + + /* buffer size is calculated assuming a single supported version */ + static u_char buf[NGX_QUIC_MAX_LONG_HEADER + sizeof(uint32_t)]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(c->log, "quic vnego packet to send", buf, len); +#endif + + (void) c->send(c, buf, len); + + return NGX_ERROR; +} + + static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) { @@ -1628,6 +1663,12 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } + if (pkt->version != NGX_QUIC_VERSION) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_ERROR; + } + if (ngx_quic_pkt_zrtt(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic discard inflight 0-RTT packet"); @@ -1715,6 +1756,12 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } + if (pkt->version != NGX_QUIC_VERSION) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_ERROR; + } + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { return NGX_ERROR; } @@ -1765,6 +1812,12 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } + if (pkt->version != NGX_QUIC_VERSION) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_ERROR; + } + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { return NGX_ERROR; } @@ -1825,6 +1878,12 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } + if (pkt->version != NGX_QUIC_VERSION) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_ERROR; + } + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index dd50ba390..58a4c8374 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -88,6 +88,15 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ngx_quic_tp_t *dst); +/* currently only single version (selected at compile-time) is supported */ +uint32_t ngx_quic_versions[] = { + NGX_QUIC_VERSION +}; + +#define NGX_QUIC_NVERSIONS \ + (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) + + /* literal errors indexed by corresponding value */ static char *ngx_quic_errors[] = { "NO_ERROR", @@ -232,8 +241,8 @@ ngx_quic_error_text(uint64_t error_code) ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt) { - u_char *p, *end; - uint8_t idlen; + u_char *p, *end; + uint8_t idlen; p = pkt->data; end = pkt->data + pkt->len; @@ -270,12 +279,6 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) return NGX_DECLINED; } - if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_ERROR; - } - p = ngx_quic_read_uint8(p, end, &idlen); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -326,6 +329,36 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } +size_t +ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) +{ + u_char *p, *start; + ngx_uint_t i; + + p = start = out; + + *p++ = pkt->flags; + + /* + * The Version field of a Version Negotiation packet + * MUST be set to 0x00000000 + */ + p = ngx_quic_write_uint32(p, 0); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); + } + + return p - start; +} + + size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp) diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index b1dd5aef5..c13f1b7ce 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -317,6 +317,8 @@ typedef struct { u_char *ngx_quic_error_text(uint64_t error_code); +size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); + ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); -- cgit v1.2.3 From 51b4d208d6a53f588cfab9dd916c229abc1da784 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 20 Aug 2020 16:45:48 +0300 Subject: QUIC: removed outdated TODOs. The logical quic connection state is tested by handler functions that process corresponding types of packets (initial/handshake/application). The packet is declined if state is incorrect. No timeout is required for the input queue. --- src/event/ngx_event_quic.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9930a909d..b410cb0e0 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1578,7 +1578,6 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) return ngx_quic_retry_input(c, &pkt); } - /* TODO: check current state */ if (ngx_quic_long_pkt(pkt.flags)) { if (ngx_quic_pkt_in(pkt.flags)) { @@ -2830,8 +2829,6 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, "quic ordered frame with unexpected offset:" " buffered, total %ui", fs->total); - /* TODO: do we need some timeout for this queue ? */ - if (ngx_queue_empty(&fs->frames)) { ngx_queue_insert_after(&fs->frames, &dst->queue); return NGX_OK; -- cgit v1.2.3 From fb21151ff8561415b757408d8a51bc2af688175d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 21 Aug 2020 10:00:25 +0300 Subject: QUIC: dead code removed. This case was already handled in c70446e3d771. --- src/event/ngx_event_quic_transport.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 58a4c8374..610f7c6ea 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -930,14 +930,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; - case NGX_QUIC_FT_NEW_TOKEN: - /* TODO: implement */ - - ngx_log_error(NGX_LOG_ALERT, pkt->log, 0, - "quic unimplemented frame type 0x%xi in packet", f->type); - - break; - case NGX_QUIC_FT_MAX_STREAMS: case NGX_QUIC_FT_MAX_STREAMS2: -- cgit v1.2.3 From 6c089cda295fa05c9f3801aa7c69f717d8940788 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 21 Aug 2020 14:41:41 +0300 Subject: QUIC: stripped down debug traces that have served its purpose. The most observable remainers are incoming packet and stream payload that could still be useful to debug various QUIC and HTTP/3 frames. --- src/event/ngx_event_quic.c | 4 ---- src/event/ngx_event_quic_protection.c | 44 +---------------------------------- src/event/ngx_event_quic_transport.c | 19 +++------------ 3 files changed, 4 insertions(+), 63 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b410cb0e0..7923c5529 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3651,10 +3651,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return NGX_ERROR; } -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len); -#endif - len = c->send(c, res.data, res.len); if (len == NGX_ERROR || (size_t) len != res.len) { return NGX_ERROR; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 721944b97..ae6ae27e7 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -277,9 +277,7 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic ngx_quic_hkdf_expand %V keys", label); - ngx_quic_hexdump(pool->log, "quic info", info, info_len); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pool->log, 0, "quic expand %V", label); ngx_quic_hexdump(pool->log, "quic key", out->data, out->len); #endif @@ -779,8 +777,6 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ngx_quic_create_long_packet"); ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif @@ -791,11 +787,6 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); -#endif - if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) @@ -810,11 +801,6 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); - ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); -#endif - /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x0f; @@ -846,8 +832,6 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ngx_quic_create_short_packet"); ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif @@ -855,19 +839,9 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ngx_quic_create_short_packet: number %L," - " encoded %d:0x%xD", pkt->number, (int) pkt->num_len, - pkt->trunc); - ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic server_iv", pkt->secret->iv.data, 12); - ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); -#endif - if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) @@ -882,11 +856,6 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); - ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); -#endif - /* quic-tls: 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & 0x1f; @@ -1052,12 +1021,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, sample = p + 4; -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ngx_quic_decrypt()"); - ngx_quic_hexdump(pkt->log, "quic sample", sample, 16); -#endif - /* header protection */ if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) @@ -1086,10 +1049,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->pn = pn; pkt->flags = clearflags; -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic mask", mask, 5); -#endif - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic clear flags: %xd", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, @@ -1122,7 +1081,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic nonce", nonce, 12); ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 610f7c6ea..2fe73733d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -247,10 +247,6 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) p = pkt->data; end = pkt->data + pkt->len; -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(pkt->log, "quic long packet in", pkt->data, pkt->len); -#endif - p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -474,10 +470,6 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) p = pkt->data; end = pkt->data + pkt->len; -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(pkt->log, "quic short packet in", pkt->data, pkt->len); -#endif - p = ngx_quic_read_uint8(p, end, &pkt->flags); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -655,15 +647,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: CRYPTO length: %uL off:%uL pp:%p", - f->u.crypto.length, f->u.crypto.offset, - f->u.crypto.data); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic frame in: CRYPTO length: %uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); -#ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump(pkt->log, "quic CRYPTO frame", - f->u.crypto.data, f->u.crypto.length); -#endif break; case NGX_QUIC_FT_PADDING: -- cgit v1.2.3 From e153f4993c62e02f14da07fcf68d8c24ba189d3b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 21 Aug 2020 14:41:42 +0300 Subject: QUIC: disabled bidirectional SSL shutdown after 09fb2135a589. On QUIC connections, SSL_shutdown() is used to call the send_alert callback to send a CONNECTION_CLOSE frame. The reverse side is handled by other means. At least BoringSSL doesn't differentiate whether this is a QUIC SSL method, so waiting for the peer's close_notify alert should be explicitly disabled. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7923c5529..918e8f716 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2918,6 +2918,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) "quic handshake completed successfully"); c->ssl->handshaked = 1; + c->ssl->no_wait_shutdown = 1; frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { -- cgit v1.2.3 From 2cd05e1909f8ffa8e73f45beeb4c9f362be5a26c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 21 Aug 2020 14:55:32 +0300 Subject: QUIC: updated README. - version negotiation is implemented - quic recovery implementation is greatly improved --- README | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index a95b87838..97b2fe4cc 100644 --- a/README +++ b/README @@ -52,14 +52,14 @@ Experimental QUIC support for nginx + An H3 transaction succeeded + One or both endpoints insert entries into dynamic table and subsequently reference them from header blocks + + Version Negotiation packet is sent to client with unknown version + + Lost packets are detected and retransmitted properly Not (yet) supported features: - - Version negotiation - - ECN, Congestion control and friends as specified in quic-recovery [5] + - Explicit Congestion Notification (ECN) as specified in quic-recovery [5] - A connection with the spin bit succeeds and the bit is spinning - Structured Logging - - QUIC recovery (proper congestion and flow control) - NAT Rebinding - Address Mobility - HTTP/3 trailers -- cgit v1.2.3 From 4e4d0938b9190713fbb4526d06ee731b6a1800a5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Aug 2020 14:07:26 +0300 Subject: QUIC: send STOP_SENDING on stream closure. The frame is sent for a read-enabled stream which has not received a FIN or RESET_STREAM. --- src/event/ngx_event_quic.c | 21 +++++++++++++++++++++ src/event/ngx_event_quic_transport.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 918e8f716..44360a1bc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4564,6 +4564,27 @@ ngx_quic_stream_cleanup_handler(void *data) return; } + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 + || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + if (!c->read->eof && !c->read->error) { + frame = ngx_quic_alloc_frame(pc, 0); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ + + ngx_sprintf(frame->info, "STOP_SENDING id:0x%xL err:0x%xL level:%d", + qs->id, frame->u.stop_sending.error_code, frame->level); + + ngx_quic_queue_frame(qc, frame); + } + } + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 2fe73733d..84712fbae 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -70,6 +70,8 @@ static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); +static size_t ngx_quic_create_stop_sending(u_char *p, + ngx_quic_stop_sending_frame_t *ss); static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto); static size_t ngx_quic_create_hs_done(u_char *p); @@ -1165,6 +1167,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) f->need_ack = 0; return ngx_quic_create_ack(p, &f->u.ack); + case NGX_QUIC_FT_STOP_SENDING: + return ngx_quic_create_stop_sending(p, &f->u.stop_sending); + case NGX_QUIC_FT_CRYPTO: return ngx_quic_create_crypto(p, &f->u.crypto); @@ -1235,6 +1240,29 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) } +static size_t +ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); + len += ngx_quic_varint_len(ss->id); + len += ngx_quic_varint_len(ss->error_code); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); + ngx_quic_build_int(&p, ss->id); + ngx_quic_build_int(&p, ss->error_code); + + return p - start; +} + + static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) { -- cgit v1.2.3 From 693e55a4b232afc6f7b4ba8e25f07decd86baa21 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Aug 2020 12:45:21 +0300 Subject: HTTP/3: drop the unwanted remainder of the request. As per HTTP/3 draft 29, section 4.1: When the server does not need to receive the remainder of the request, it MAY abort reading the request stream, send a complete response, and cleanly close the sending part of the stream. --- src/http/ngx_http_request.c | 7 +++++++ src/http/ngx_http_request_body.c | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7dbbcceb2..e322fae4b 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2819,6 +2819,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_QUIC) + if (r->connection->qs) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index d0fcb00f4..7a9bbdd5d 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -579,6 +579,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_QUIC) + if (r->connection->qs) { + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } -- cgit v1.2.3 From d69471b81b3206b75fd9738b464b34929cdaf763 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Aug 2020 17:22:57 +0300 Subject: QUIC: enforce flow control on incoming STREAM and CRYPTO frames. --- src/event/ngx_event_quic.c | 54 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 44360a1bc..d9410023f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2802,13 +2802,6 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* frame start offset is in the future, buffer it */ - /* check limit on total size used by all buffered frames, not actual data */ - if (NGX_QUIC_MAX_BUFFERED - fs->total < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic ordered input buffer limit exceeded"); - return NGX_ERROR; - } - dst = ngx_quic_alloc_frame(c, f->length); if (dst == NULL) { return NGX_ERROR; @@ -2857,11 +2850,22 @@ static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { + uint64_t last; ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; ngx_quic_frames_stream_t *fs; qc = c->quic; fs = &qc->crypto[pkt->level]; + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { + c->quic->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, NULL); @@ -2978,6 +2982,9 @@ static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { + size_t window; + uint64_t last; + ngx_buf_t *b; ngx_pool_t *pool; ngx_connection_t *sc; ngx_quic_stream_t *sn; @@ -2995,6 +3002,9 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); if (sn == NULL) { @@ -3010,17 +3020,19 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sc = sn->c; fs = &sn->fs; + b = sn->b; + window = b->end - b->last; + + if (last > window) { + c->quic->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + goto cleanup; + } if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, sn) != NGX_OK) { - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; + goto cleanup; } sc->listening->handler(sc); @@ -3029,9 +3041,25 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } fs = &sn->fs; + b = sn->b; + window = (b->pos - b->start) + (b->end - b->last); + + if (last > fs->received && last - fs->received > window) { + c->quic->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, sn); + +cleanup: + + pool = sc->pool; + + ngx_close_connection(sc); + ngx_destroy_pool(pool); + + return NGX_ERROR; } -- cgit v1.2.3 From 6f0e1bc14ff0324c3b3680aa4731ce224e517295 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 28 Aug 2020 12:01:35 +0300 Subject: QUIC: handle PATH_CHALLENGE frame. A PATH_RESPONSE frame with the same data is sent in response. --- src/event/ngx_event_quic.c | 38 ++++++++++++++++++++++++++++- src/event/ngx_event_quic_transport.c | 46 +++++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d9410023f..00d5f5178 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -251,6 +251,8 @@ static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); static 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); +static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -2202,9 +2204,19 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_NEW_CONNECTION_ID: case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: /* TODO: handle */ @@ -3422,6 +3434,30 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + ngx_quic_frame_t *frame; + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_PATH_RESPONSE; + frame->u.path_response = *f; + + ngx_sprintf(frame->info, "PATH_RESPONSE data:0x%xL level:%d", + *(uint64_t *) &f->data, frame->level); + + ngx_quic_queue_frame(c->quic, frame); + + return NGX_OK; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 84712fbae..69d17a623 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -84,6 +84,8 @@ static size_t ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms); static size_t ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_response(u_char *p, + ngx_quic_path_challenge_frame_t *pc); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, @@ -1004,13 +1006,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: PATH_CHALLENGE"); - -#ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump(pkt->log, "quic PATH_CHALLENGE frame data", - f->u.path_challenge.data, 8); -#endif + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic frame in: PATH_CHALLENGE data:0x%xL", + *(uint64_t *) &f->u.path_challenge.data); break; case NGX_QUIC_FT_PATH_RESPONSE: @@ -1020,13 +1018,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: PATH_RESPONSE"); - -#ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump(pkt->log, "quic PATH_RESPONSE frame data", - f->u.path_response.data, 8); -#endif + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic frame in: PATH_RESPONSE data:0x%xL", + *(uint64_t *) &f->u.path_response.data); break; default: @@ -1203,6 +1197,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_MAX_DATA: return ngx_quic_create_max_data(p, &f->u.max_data); + case NGX_QUIC_FT_PATH_RESPONSE: + return ngx_quic_create_path_response(p, &f->u.path_response); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -1661,6 +1658,27 @@ ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) } +static size_t +ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, size_t *clen) -- cgit v1.2.3 From 208735967547b989c243b70370061ba422c229d7 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 1 Sep 2020 15:21:49 +0300 Subject: QUIC: do not update largest packet number from a bad packet. The removal of QUIC packet protection depends on the largest packet number received. When a garbage packet was received, the decoder still updated the largest packet number from that packet. This could affect removing protection from subsequent QUIC packets. --- src/event/ngx_event_quic_protection.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index ae6ae27e7..8e7fcc1e8 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -998,7 +998,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, { u_char clearflags, *p, *sample; uint8_t badflags; - uint64_t pn; + uint64_t pn, lpn; ngx_int_t pnl, rc, key_phase; ngx_str_t in, ad; ngx_quic_secret_t *secret; @@ -1043,8 +1043,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } } + lpn = *largest_pn; + pnl = (clearflags & 0x03) + 1; - pn = ngx_quic_parse_pn(&p, pnl, &mask[1], largest_pn); + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); pkt->pn = pn; pkt->flags = clearflags; @@ -1118,6 +1120,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, return NGX_ERROR; } + *largest_pn = lpn; + return NGX_OK; } -- cgit v1.2.3 From d73a289c432b280913cfff4abffc5d40aa34318f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 1 Sep 2020 17:20:42 +0300 Subject: QUIC: discard incorrect packets instead of closing the connection. quic-transport 5.2: Packets that are matched to an existing connection are discarded if the packets are inconsistent with the state of that connection. 5.2.2: Servers MUST drop incoming packets under all other circumstances. --- src/event/ngx_event_quic.c | 60 ++++++++++++++++-------------------- src/event/ngx_event_quic_transport.c | 4 +-- 2 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 00d5f5178..913ffc56d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -658,9 +658,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - rc = ngx_quic_parse_long_header(pkt); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; } if (pkt->version != NGX_QUIC_VERSION) { @@ -1645,7 +1644,6 @@ ngx_quic_skip_zero_padding(ngx_buf_t *b) static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1659,15 +1657,14 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_OK; } - rc = ngx_quic_parse_long_header(pkt); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; } if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unsupported version: 0x%xD", pkt->version); - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_pkt_zrtt(pkt->flags)) { @@ -1679,11 +1676,11 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (!ngx_quic_pkt_in(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid initial packet: 0x%xd", pkt->flags); - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { @@ -1742,7 +1739,6 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_int_t rc; ngx_ssl_conn_t *ssl_conn; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; @@ -1752,19 +1748,22 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ssl_conn = c->ssl->connection; - rc = ngx_quic_parse_long_header(pkt); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; } if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unsupported version: 0x%xD", pkt->version); - return NGX_ERROR; + return NGX_DECLINED; + } + + if (ngx_quic_check_peer(c->quic, pkt) != NGX_OK) { + return NGX_DECLINED; } if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } keys = &c->quic->keys[ssl_encryption_initial]; @@ -1787,7 +1786,6 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_int_t rc; ngx_queue_t *q; ngx_quic_frame_t *f; ngx_quic_secrets_t *keys; @@ -1808,23 +1806,22 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) } /* extract cleartext data into pkt */ - rc = ngx_quic_parse_long_header(pkt); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; } if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unsupported version: 0x%xD", pkt->version); - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } pkt->secret = &keys->client; @@ -1863,7 +1860,6 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1874,23 +1870,22 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = c->quic; /* extract cleartext data into pkt */ - rc = ngx_quic_parse_long_header(pkt); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; } if (pkt->version != NGX_QUIC_VERSION) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unsupported version: 0x%xD", pkt->version); - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { - return NGX_ERROR; + return NGX_DECLINED; } keys = &c->quic->keys[ssl_encryption_early_data]; @@ -1970,9 +1965,8 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } - rc = ngx_quic_parse_short_header(pkt, &qc->dcid); - if (rc != NGX_OK) { - return rc; + if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { + return NGX_DECLINED; } pkt->secret = &keys->client; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 69d17a623..6e0fb8753 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -276,7 +276,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); - return NGX_DECLINED; + return NGX_ERROR; } p = ngx_quic_read_uint8(p, end, &idlen); @@ -491,7 +491,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); - return NGX_DECLINED; + return NGX_ERROR; } if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { -- cgit v1.2.3 From e443b1244f60448805a873bf488a9d9f89bf7488 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 31 Aug 2020 18:42:26 +0300 Subject: HTTP/3: do not set the never-indexed literal bit by default. The "Literal Header Field Never Indexed" header field representation is not used in HTTP/2, and it makes little sense to make a distinction in HTTP/3. --- src/http/v3/ngx_http_v3_encode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c index a80310faf..b7bc3dd7c 100644 --- a/src/http/v3/ngx_http_v3_encode.c +++ b/src/http/v3/ngx_http_v3_encode.c @@ -145,7 +145,7 @@ ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + len; } - *p = dynamic ? 0x60 : 0x70; + *p = dynamic ? 0x40 : 0x50; p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); *p = 0; @@ -171,7 +171,7 @@ ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) + value->len; } - *p = 0x30; + *p = 0x20; p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); ngx_strlow(p, name->data, name->len); @@ -213,7 +213,7 @@ ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, + len; } - *p = 0x08; + *p = 0; p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); *p = 0; -- cgit v1.2.3 From ff4cfa80e51a015d538a7880da5b5a41e1ac7289 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 2 Sep 2020 09:54:15 +0300 Subject: QUIC: discard unrecognized long packes. While there, updated comment about discarded packets. --- src/event/ngx_event_quic.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 913ffc56d..fbc6455a7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1593,7 +1593,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) } else { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic unknown long packet type"); - return NGX_ERROR; + rc = NGX_DECLINED; } } else { @@ -1618,6 +1618,9 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) * not available or any other reason), the receiver MAY either * discard or buffer the packet for later processing and MUST * attempt to process the remaining packets. + * + * We also skip packets that don't match connection state + * or cannot be parsed properly. */ /* b->pos is at header end, adjust by actual packet length */ -- cgit v1.2.3 From fb54f2acd964211cda7ec9423f9fa0e142b2645b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 2 Sep 2020 22:34:15 +0300 Subject: QUIC: pass return code from ngx_quic_decrypt() to the caller. It is required to distinguish internal errors from corrupted packets and perform actions accordingly: drop the packet or close the connection. While there, made processing of ngx_quic_decrypt() erorrs similar and removed couple of protocol violation errors. --- src/event/ngx_event_quic.c | 61 ++++++++++++++++++++++------------- src/event/ngx_event_quic_protection.c | 2 -- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index fbc6455a7..cb89990ab 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -610,6 +610,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) { + ngx_int_t rc; ngx_buf_t *b; ngx_quic_header_t pkt; @@ -626,8 +627,9 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) pkt.data = b->start; pkt.len = b->last - b->start; - if (ngx_quic_new_connection(c, ssl, conf, &pkt) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); + rc = ngx_quic_new_connection(c, ssl, conf, &pkt); + if (rc != NGX_OK) { + ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); return; } @@ -806,11 +808,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { + rc = ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn); + if (rc != NGX_OK) { qc->error = pkt->error; qc->error_reason = "failed to decrypt packet"; - - return NGX_ERROR; + return rc; } if (ngx_quic_init_connection(c) != NGX_OK) { @@ -1647,6 +1649,7 @@ ngx_quic_skip_zero_padding(ngx_buf_t *b) static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1717,9 +1720,11 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn) != NGX_OK) { + rc = ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn); + if (rc != NGX_OK) { qc->error = pkt->error; - return NGX_ERROR; + qc->error_reason = "failed to decrypt packet"; + return rc; } if (ngx_quic_init_connection(c) != NGX_OK) { @@ -1742,10 +1747,12 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_ssl_conn_t *ssl_conn; - ngx_quic_secrets_t *keys; - ngx_quic_send_ctx_t *ctx; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + ngx_int_t rc; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_secrets_t *keys; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "processing initial quic packet"; @@ -1761,7 +1768,9 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } - if (ngx_quic_check_peer(c->quic, pkt) != NGX_OK) { + qc = c->quic; + + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { return NGX_DECLINED; } @@ -1769,17 +1778,19 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } - keys = &c->quic->keys[ssl_encryption_initial]; + keys = &qc->keys[ssl_encryption_initial]; pkt->secret = &keys->client; pkt->level = ssl_encryption_initial; pkt->plaintext = buf; - ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn) != NGX_OK) { - c->quic->error = pkt->error; - return NGX_ERROR; + rc = ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn); + if (rc != NGX_OK) { + qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return rc; } return ngx_quic_payload_handler(c, pkt); @@ -1789,6 +1800,7 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_queue_t *q; ngx_quic_frame_t *f; ngx_quic_secrets_t *keys; @@ -1833,9 +1845,11 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); + if (rc != NGX_OK) { qc->error = pkt->error; - return NGX_ERROR; + qc->error_reason = "failed to decrypt packet"; + return rc; } /* @@ -1863,6 +1877,7 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) { + ngx_int_t rc; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1906,9 +1921,11 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); - if (ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn) != NGX_OK) { + rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); + if (rc != NGX_OK) { qc->error = pkt->error; - return NGX_ERROR; + qc->error_reason = "failed to decrypt packet"; + return rc; } return ngx_quic_payload_handler(c, pkt); @@ -1981,9 +1998,9 @@ ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); - if (rc != NGX_OK) { qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; return rc; } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 8e7fcc1e8..fd11e591e 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1026,7 +1026,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { - pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; return NGX_DECLINED; } @@ -1103,7 +1102,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, #endif if (rc != NGX_OK) { - pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; return NGX_DECLINED; } -- cgit v1.2.3 From c36c54f5001599a65add4f41461d7d8bc0b8394a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 27 Aug 2020 10:15:37 +0300 Subject: QUIC: style. Moved processing of RETIRE_CONNECTION_ID right after the NEW_CONNECTION_ID. --- src/event/ngx_event_quic_transport.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 6e0fb8753..ff741242d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -751,6 +751,19 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + goto error; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic frame in: RETIRE_CONNECTION_ID" + " sequence_number:%uL", + f->u.retire_cid.sequence_number); + break; + case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE_APP: @@ -986,19 +999,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.stream_data_blocked.limit); break; - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - - p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); - if (p == NULL) { - goto error; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: RETIRE_CONNECTION_ID" - " sequence_number:%uL", - f->u.retire_cid.sequence_number); - break; - case NGX_QUIC_FT_PATH_CHALLENGE: p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); -- cgit v1.2.3 From d6065b2791ad41b6d0b1730ee085dcc8a4dde736 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 3 Sep 2020 13:11:27 +0300 Subject: QUIC: added support for multiple connection IDs. The peer may issue additional connection IDs up to the limit defined by transport parameter "active_connection_id_limit", using NEW_CONNECTION_ID frames, and retire such IDs using RETIRE_CONNECTION_ID frame. --- src/event/ngx_event_quic.c | 274 +++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic_transport.c | 37 ++++- src/event/ngx_event_quic_transport.h | 4 +- 3 files changed, 301 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index cb89990ab..f158b0fc7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -88,11 +88,17 @@ typedef struct { struct ngx_quic_connection_s { - ngx_str_t scid; - ngx_str_t dcid; - ngx_str_t odcid; + ngx_str_t scid; /* initial client ID */ + ngx_str_t dcid; /* server (our own) ID */ + ngx_str_t odcid; /* original server ID */ ngx_str_t token; + ngx_queue_t client_ids; + ngx_queue_t free_client_ids; + ngx_uint_t nclient_ids; + uint64_t max_retired_seqnum; + uint64_t curr_seqnum; + ngx_uint_t client_tp_done; ngx_quic_tp_t tp; ngx_quic_tp_t ctp; @@ -143,6 +149,15 @@ struct ngx_quic_connection_s { }; +typedef struct { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SRT_LEN]; +} ngx_quic_client_id_t; + + typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); @@ -253,6 +268,11 @@ static 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); static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); +static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum); +static ngx_quic_client_id_t *ngx_quic_alloc_connection_id(ngx_connection_t *c); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -651,6 +671,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_tp_t *ctp; ngx_quic_secrets_t *keys; ngx_quic_send_ctx_t *ctx; + ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; @@ -708,6 +729,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, } ngx_queue_init(&qc->free_frames); + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->free_client_ids); qc->avg_rtt = NGX_QUIC_INITIAL_RTT; qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; @@ -715,6 +738,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, /* * qc->latest_rtt = 0 + * qc->nclient_ids = 0 + * qc->max_retired_seqnum = 0 */ qc->received = pkt->raw->last - pkt->raw->start; @@ -766,6 +791,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, } ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); + cid = ngx_quic_alloc_connection_id(c); + if (cid == NULL) { + return NGX_ERROR; + } + + cid->seqnum = 0; + cid->len = pkt->scid.len; + ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + qc->curr_seqnum = 0; + keys = &c->quic->keys[ssl_encryption_initial]; if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, @@ -1935,7 +1973,9 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { - ngx_str_t *dcid; + ngx_str_t *dcid; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; dcid = ngx_quic_pkt_zrtt(pkt->flags) ? &qc->odcid : &qc->dcid; @@ -1949,17 +1989,22 @@ ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) return NGX_ERROR; } - if (pkt->scid.len != qc->scid.len) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scidl"); - return NGX_ERROR; - } + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - if (ngx_memcmp(pkt->scid.data, qc->scid.data, qc->scid.len) != 0) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); - return NGX_ERROR; + if (pkt->scid.len == cid->len + && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) + { + return NGX_OK; + } } - return NGX_OK; + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; } @@ -2230,6 +2275,15 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_NEW_CONNECTION_ID: + + if (ngx_quic_handle_new_connection_id_frame(c, pkt, &frame.u.ncid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: case NGX_QUIC_FT_PATH_RESPONSE: @@ -3472,6 +3526,202 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { + return NGX_ERROR; + } + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN) != 0) + { + /* + * ..a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + cid = ngx_quic_alloc_connection_id(c); + if (cid == NULL) { + return NGX_ERROR; + } + + cid->seqnum = f->seqnum; + cid->len = f->len; + ngx_memcpy(cid->id, f->cid, f->len); + + ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + /* always use latest available connection id */ + if (f->seqnum > qc->curr_seqnum) { + qc->scid.len = cid->len; + qc->scid.data = cid->id; + qc->curr_seqnum = f->seqnum; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + /* this connection id must be retired */ + + if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + qc->nclient_ids--; + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum) +{ + ngx_quic_frame_t *frame; + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = seqnum; + + ngx_sprintf(frame->info, "RETIRE_CONNECTION_ID seqnum=%uL level=%d", + seqnum, frame->level); + + ngx_quic_queue_frame(c->quic, frame); + + return NGX_OK; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_connection_id(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = c->quic; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index ff741242d..36e987f92 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -86,6 +86,8 @@ static size_t ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md); static size_t ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, @@ -731,17 +733,25 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } + if (f->u.ncid.retire > f->u.ncid.seqnum) { + goto error; + } + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); if (p == NULL) { goto error; } + if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { + goto error; + } + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); if (p == NULL) { goto error; } - p = ngx_quic_copy_bytes(p, end, 16, f->u.ncid.srt); + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SRT_LEN, f->u.ncid.srt); if (p == NULL) { goto error; } @@ -1200,6 +1210,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_PATH_RESPONSE: return ngx_quic_create_path_response(p, &f->u.path_response); + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); + default: /* BUG: unsupported frame type generated */ return NGX_ERROR; @@ -1679,6 +1692,28 @@ ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) } +static size_t +ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); + len += ngx_quic_varint_len(rcid->sequence_number); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); + ngx_quic_build_int(&p, rcid->sequence_number); + + return p - start; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, size_t *clen) diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index c13f1b7ce..63c6a2191 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -120,6 +120,8 @@ #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 +#define NGX_QUIC_SRT_LEN 16 + typedef struct { uint64_t largest; @@ -139,7 +141,7 @@ typedef struct { uint64_t retire; uint8_t len; u_char cid[NGX_QUIC_CID_LEN_MAX]; - u_char srt[16]; + u_char srt[NGX_QUIC_SRT_LEN]; } ngx_quic_new_conn_id_frame_t; -- cgit v1.2.3 From 6983bc0a37bdec605b471643c2e6a0b0f6d6140f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 6 Sep 2020 14:51:23 +0300 Subject: QUIC: do not send STOP_SENDING after STREAM fin. Previously STOP_SENDING was sent to client upon stream closure if rev->eof and rev->error were not set. This was an indirect indication that no RESET_STREAM or STREAM fin has arrived. But it is indeed possible that rev->eof is not set, but STREAM fin has already been received, just not read out by the application. In this case sending STOP_SENDING does not make sense and can be misleading for some clients. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f158b0fc7..2364caebf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4895,7 +4895,7 @@ ngx_quic_stream_cleanup_handler(void *data) if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - if (!c->read->eof && !c->read->error) { + if (!c->read->pending_eof && !c->read->error) { frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { return; -- cgit v1.2.3 From 6f78befe992ac5dc667ce9f040ad4fc012d8340b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 4 Sep 2020 15:48:53 +0300 Subject: QUIC: refactored ngx_quic_retry_input(). The function now returns NGX_DECLINED for packets that need to be ignored and integrates nicely into ngx_quic_input(). --- src/event/ngx_event_quic.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2364caebf..99a0550f1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1098,10 +1098,6 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_connection_t *qc; u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - if (pkt->token.len == 0) { - return NGX_ERROR; - } - qc = c->quic; /* Retry token */ @@ -1616,10 +1612,9 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.flags = p[0]; if (c->quic->in_retry) { - return ngx_quic_retry_input(c, &pkt); - } + rc = ngx_quic_retry_input(c, &pkt); - if (ngx_quic_long_pkt(pkt.flags)) { + } else if (ngx_quic_long_pkt(pkt.flags)) { if (ngx_quic_pkt_in(pkt.flags)) { rc = ngx_quic_initial_input(c, &pkt); @@ -1698,7 +1693,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic UDP datagram is too small for initial packet"); - return NGX_OK; + return NGX_DECLINED; } if (ngx_quic_parse_long_header(pkt) != NGX_OK) { @@ -1714,7 +1709,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) if (ngx_quic_pkt_zrtt(pkt->flags)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic discard inflight 0-RTT packet"); - return NGX_OK; + return NGX_DECLINED; } if (!ngx_quic_pkt_in(pkt->flags)) { @@ -1727,6 +1722,10 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } + if (!pkt->token.len) { + return NGX_DECLINED; + } + if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { return NGX_ERROR; } @@ -1773,12 +1772,7 @@ ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } - /* pos is at header end, adjust by actual packet length */ - pkt->raw->pos += pkt->len; - - (void) ngx_quic_skip_zero_padding(pkt->raw); - - return ngx_quic_input(c, pkt->raw); + return NGX_OK; } -- cgit v1.2.3 From e43ef3dda9efaa1488c36951b089d77f65c75a79 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 7 Sep 2020 20:55:36 +0300 Subject: QUIC: added logging output stream frame offset. --- src/event/ngx_event_quic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 99a0550f1..66a4e7501 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4751,8 +4751,8 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) frame->u.stream.length = n; frame->u.stream.data = frame->data; - ngx_sprintf(frame->info, "STREAM id:0x%xL len:%uz level:%d", - qs->id, n, frame->level); + ngx_sprintf(frame->info, "STREAM id:0x%xL offset:%O len:%uz level:%d", + qs->id, c->sent, n, frame->level); c->sent += n; qc->streams.sent += n; @@ -4958,8 +4958,8 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stream.length = 0; frame->u.stream.data = NULL; - ngx_sprintf(frame->info, "stream 0x%xL fin=1 level=%d", - qs->id, frame->level); + ngx_sprintf(frame->info, "STREAM id:0x%xL offset:%O fin:1 level:%d", + qs->id, c->sent, frame->level); ngx_quic_queue_frame(qc, frame); -- cgit v1.2.3 From 952c6f19898b770906aefeb52dd0eb8a578dd808 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 8 Sep 2020 13:27:39 +0300 Subject: QUIC: update packet length for short packets too. During long packet header parsing, pkt->len is updated with the Length field value that is used to find next coalesced packets in a datagram. For short packets it still contained the whole QUIC packet size. This change uniforms packet length handling to always contain the total length of the packet number and protected packet payload in pkt->len. --- src/event/ngx_event_quic_protection.c | 3 +-- src/event/ngx_event_quic_transport.c | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index fd11e591e..261f02d7f 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1058,13 +1058,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, /* packet protection */ in.data = p; + in.len = pkt->len - pnl; if (ngx_quic_long_pkt(pkt->flags)) { - in.len = pkt->len - pnl; badflags = clearflags & NGX_QUIC_PKT_LONG_RESERVED_BIT; } else { - in.len = pkt->data + pkt->len - p; badflags = clearflags & NGX_QUIC_PKT_SHORT_RESERVED_BIT; } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 36e987f92..1d270a2f2 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -511,6 +511,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) } pkt->raw->pos = p; + pkt->len = end - p; return NGX_OK; } -- cgit v1.2.3 From d8360f912ac2eeb0103c2781d450b7735d7894ba Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 8 Sep 2020 13:28:56 +0300 Subject: QUIC: check that the packet length is of at least sample size. From quic-tls draft, section 5.4.2: An endpoint MUST discard packets that are not long enough to contain a complete sample. The check includes the Packet Number field assumed to be 4 bytes long. --- src/event/ngx_event_quic_protection.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 261f02d7f..7a4ebdaa7 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1019,6 +1019,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, * AES-Based and ChaCha20-Based header protections sample 16 bytes */ + if (pkt->len < EVP_GCM_TLS_TAG_LEN + 4) { + return NGX_DECLINED; + } + sample = p + 4; /* header protection */ -- cgit v1.2.3 From 786a74e34ec89d0e78b95f2524dff68bf6235923 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 8 Sep 2020 13:35:50 +0300 Subject: QUIC: removed check for packet size beyond MAX_UDP_PAYLOAD_SIZE. The check tested the total size of a packet header and unprotected packet payload, which doesn't include the packet number length and expansion of the packet protection AEAD. If the packet was corrupted, it could cause false triggering of the condition due to unsigned type underflow leading to a connection error. Existing checks for the QUIC header and protected packet payload lengths should be enough. --- src/event/ngx_event_quic_protection.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 7a4ebdaa7..0d205a160 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1089,11 +1089,6 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, #endif pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; - - if (NGX_QUIC_MAX_UDP_PAYLOAD_SIZE - ad.len < pkt->payload.len) { - return NGX_ERROR; - } - pkt->payload.data = pkt->plaintext + ad.len; rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, -- cgit v1.2.3 From f3bed9cd67ee6333b7d0db121f96451c0bcf4686 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 8 Sep 2020 15:54:02 +0300 Subject: QUIC: eliminated idle timeout restart for dropped packets. --- src/event/ngx_event_quic.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 66a4e7501..336888787 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -866,7 +866,15 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, (void) ngx_quic_skip_zero_padding(pkt->raw); - return ngx_quic_input(c, pkt->raw); + rc = ngx_quic_input(c, pkt->raw); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + /* rc == NGX_OK || rc == NGX_DECLINED */ + + return NGX_OK; } @@ -1290,6 +1298,7 @@ static void ngx_quic_input_handler(ngx_event_t *rev) { ssize_t n; + ngx_int_t rc; ngx_buf_t b; ngx_connection_t *c; ngx_quic_connection_t *qc; @@ -1337,11 +1346,19 @@ ngx_quic_input_handler(ngx_event_t *rev) b.last += n; qc->received += n; - if (ngx_quic_input(c, &b) != NGX_OK) { + rc = ngx_quic_input(c, &b); + + if (rc == NGX_ERROR) { ngx_quic_close_connection(c, NGX_ERROR); return; } + if (rc == NGX_DECLINED) { + return; + } + + /* rc == NGX_OK */ + qc->send_timer_set = 0; ngx_add_timer(rev, qc->tp.max_idle_timeout); } @@ -1597,8 +1614,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) { u_char *p; ngx_int_t rc; + ngx_uint_t good; ngx_quic_header_t pkt; + good = 0; + p = b->pos; while (p < b->last) { @@ -1639,6 +1659,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) return NGX_ERROR; } + if (rc == NGX_OK) { + good = 1; + } + /* NGX_OK || NGX_DECLINED */ /* @@ -1663,7 +1687,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) p = ngx_quic_skip_zero_padding(b); } - return NGX_OK; + return good ? NGX_OK : NGX_DECLINED; } -- cgit v1.2.3 From 2e24e3811bafe5949ebe4d286f67c534c009c08a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 9 Sep 2020 16:35:29 +0300 Subject: QUIC: allowed old DCID for initial packets until first ACK. If a packet sent in response to an initial client packet was lost, then successive client initial packets were dropped by nginx with the unexpected dcid message logged. This was because the new DCID generated by the server was not available to the client. --- src/event/ngx_event_quic.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 336888787..9301aa9a6 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1991,22 +1991,35 @@ ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { - ngx_str_t *dcid; ngx_queue_t *q; + ngx_quic_send_ctx_t *ctx; ngx_quic_client_id_t *cid; - dcid = ngx_quic_pkt_zrtt(pkt->flags) ? &qc->odcid : &qc->dcid; + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - if (pkt->dcid.len != dcid->len) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcidl"); - return NGX_ERROR; + if (ngx_quic_pkt_zrtt(pkt->flags) + || (ngx_quic_pkt_in(pkt->flags) && ctx->largest_ack == (uint64_t) -1)) + { + if (pkt->dcid.len == qc->odcid.len + && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0) + { + goto found; + } } - if (ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) != 0) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid"); - return NGX_ERROR; + if (!ngx_quic_pkt_zrtt(pkt->flags)) { + if (pkt->dcid.len == qc->dcid.len + && ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) == 0) + { + goto found; + } } + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid"); + return NGX_ERROR; + +found: + for (q = ngx_queue_head(&qc->client_ids); q != ngx_queue_sentinel(&qc->client_ids); q = ngx_queue_next(q)) -- cgit v1.2.3 From 0bc772d1fbf28ce0a705a827861158660d504035 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 11 Sep 2020 10:56:05 +0300 Subject: QUIC: switched to draft 29 by default. --- README | 14 +++++++------- src/event/ngx_event_quic.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README b/README index 97b2fe4cc..146917a64 100644 --- a/README +++ b/README @@ -190,7 +190,7 @@ Example configuration: + to enable QUIC in Chrome, enable it on command line and force it on your site: - $ ./chrome --enable-quic --quic-version=h3-27 \ + $ ./chrome --enable-quic --quic-version=h3-29 \ --origin-to-force-quic-on=example.com:8443 * Console clients @@ -202,7 +202,7 @@ Example configuration: $ ./neqo-client https://127.0.0.1:8443/ $ chromium-build/out/my_build/quic_client http://example.com:8443 \ - --quic_version=h3-27 \ + --quic_version=h3-29 \ --allow_unknown_root_cert \ --disable_certificate_verification @@ -210,7 +210,7 @@ Example configuration: If you've got it right, in the access log you should see something like: 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" - "nghttp3/ngtcp2 client" "quic" "h3-27" + "nghttp3/ngtcp2 client" "quic" "h3-29" 5. Troubleshooting @@ -218,7 +218,7 @@ Example configuration: Here are some tips that may help you to identify problems: + Ensure you are building with proper SSL library that - implements draft 27 + implements draft 29 + Ensure you are using the proper SSL library in runtime (`nginx -V` will show you what you are using) @@ -251,10 +251,10 @@ Example configuration: 7. Links - [1] https://tools.ietf.org/html/draft-ietf-quic-transport-27 - [2] https://tools.ietf.org/html/draft-ietf-quic-http-27 + [1] https://tools.ietf.org/html/draft-ietf-quic-transport-29 + [2] https://tools.ietf.org/html/draft-ietf-quic-http-29 [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel [4] https://boringssl.googlesource.com/boringssl/ - [5] https://tools.ietf.org/html/draft-ietf-quic-recovery-27 + [5] https://tools.ietf.org/html/draft-ietf-quic-recovery-29 [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 9646b03ac..2399860af 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -12,9 +12,9 @@ #include -/* Supported drafts: 27, 28 */ +/* Supported drafts: 27, 28, 29 */ #ifndef NGX_QUIC_DRAFT_VERSION -#define NGX_QUIC_DRAFT_VERSION 27 +#define NGX_QUIC_DRAFT_VERSION 29 #endif #define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) -- cgit v1.2.3 From 46173bd4b40023fd5e35bfe77b9ac2205e0c6bb0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 16 Sep 2020 18:59:25 +0100 Subject: HTTP/3: fixed handling request body eof. While for HTTP/1 unexpected eof always means an error, for HTTP/3 an eof right after a DATA frame end means the end of the request body. For this reason, since adding HTTP/3 support, eof no longer produced an error right after recv() but was passed to filters which would make a decision. This decision was made in ngx_http_parse_chunked() and ngx_http_v3_parse_request_body() based on the b->last_buf flag. Now that since 0f7f1a509113 (1.19.2) rb->chunked->length is a lower threshold for the expected number of bytes, it can be set to zero to indicate that more bytes may or may not follow. Now it's possible to move the check for eof from parser functions to ngx_http_request_body_chunked_filter() and clean up the parsing code. Also, in the default branch, in case of eof, the following three things happened, which were replaced with returning NGX_ERROR while implementing HTTP/3: - "client prematurely closed connection" message was logged - c->error flag was set - NGX_HTTP_BAD_REQUEST was returned The change brings back this behavior for HTTP/1 as well as HTTP/3. --- src/http/ngx_http_parse.c | 5 ----- src/http/ngx_http_request_body.c | 30 +++++++++++++++++++++++------- src/http/v3/ngx_http_v3_request.c | 24 ++++++++++++------------ 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index a351f2b27..2015f56b2 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -2377,11 +2377,6 @@ ngx_http_parse_chunked(ngx_http_request_t *r, ngx_buf_t *b, } } - if (b->last_buf) { - /* XXX client prematurely closed connection */ - return NGX_ERROR; - } - data: ctx->state = state; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index 7a9bbdd5d..a6dbcf502 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -960,6 +960,15 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) break; } + size = cl->buf->last - cl->buf->pos; + + if (cl->buf->last_buf && (off_t) size < rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed connection"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + tl = ngx_chain_get_free_buf(r->pool, &rb->free); if (tl == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -977,8 +986,6 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) b->end = cl->buf->end; b->flush = r->request_body_no_buffering; - size = cl->buf->last - cl->buf->pos; - if ((off_t) size < rb->rest) { cl->buf->pos = cl->buf->last; rb->rest -= size; @@ -990,11 +997,6 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) b->last_buf = 1; } - if (cl->buf->last_buf && rb->rest > 0) { - /* XXX client prematurely closed connection */ - return NGX_ERROR; - } - *ll = tl; ll = &tl->next; } @@ -1148,6 +1150,20 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) continue; } + if (rc == NGX_AGAIN && cl->buf->last_buf) { + + /* last body buffer */ + + if (rb->chunked->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed connection"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rc = NGX_DONE; + } + if (rc == NGX_DONE) { /* a whole response has been parsed successfully */ diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 24ad771d6..fe3c79bf0 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -379,6 +379,10 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_int_t rc; ngx_connection_t *c; ngx_http_v3_parse_data_t *st; + enum { + sw_start = 0, + sw_skip + }; c = r->connection; st = ctx->h3_parse; @@ -395,12 +399,8 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ctx->h3_parse = st; } - if (ctx->size) { - ctx->length = ctx->size + 1; - return (b->pos == b->last) ? NGX_AGAIN : NGX_OK; - } + while (b->pos < b->last && ctx->size == 0) { - while (b->pos < b->last) { rc = ngx_http_v3_parse_data(c, st, *b->pos++); if (rc > 0) { @@ -414,27 +414,27 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, } if (rc == NGX_AGAIN) { + ctx->state = sw_skip; continue; } /* rc == NGX_DONE */ ctx->size = st->length; - return NGX_OK; + ctx->state = sw_start; } - if (!b->last_buf) { + if (ctx->state == sw_skip) { ctx->length = 1; return NGX_AGAIN; } - if (st->state) { - goto failed; + if (b->pos == b->last) { + ctx->length = ctx->size; + return NGX_AGAIN; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); - - return NGX_DONE; + return NGX_OK; failed: -- cgit v1.2.3 From d294369915461ba764426c709301b6c66ed33681 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 24 Aug 2020 09:56:36 +0300 Subject: HTTP/3: skip unknown frames on request stream. As per HTTP/3 draft 29, section 4.1: Frames of unknown types (Section 9), including reserved frames (Section 7.2.8) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section. Also, trailers frame is now used as an indication of the request body end. --- src/http/v3/ngx_http_v3_parse.c | 63 ++++++++++++++++++++++++++++++++------- src/http/v3/ngx_http_v3_parse.h | 2 ++ src/http/v3/ngx_http_v3_request.c | 6 +++- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 1a7aa17f8..8f47b4d99 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -155,7 +155,9 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_int_t rc; enum { sw_start = 0, + sw_type, sw_length, + sw_skip, sw_prefix, sw_verify, sw_header_rep, @@ -168,10 +170,18 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers"); - if (ch != NGX_HTTP_V3_FRAME_HEADERS) { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + if (rc != NGX_DONE) { + return rc; } + st->type = st->vlint.value; st->state = sw_length; break; @@ -184,12 +194,26 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, st->length = st->vlint.value; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse headers len:%ui", st->length); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { + st->state = st->length > 0 ? sw_skip : sw_type; + break; + } st->state = sw_prefix; break; + case sw_skip: + + if (--st->length == 0) { + st->state = sw_type; + } + + break; + case sw_prefix: if (st->length-- == 0) { @@ -1529,7 +1553,8 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, enum { sw_start = 0, sw_type, - sw_length + sw_length, + sw_skip }; switch (st->state) { @@ -1549,8 +1574,11 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, return rc; } - if (st->vlint.value != NGX_HTTP_V3_FRAME_DATA) { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + st->type = st->vlint.value; + + if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { + /* trailers */ + goto done; } st->state = sw_length; @@ -1565,10 +1593,25 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, st->length = st->vlint.value; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse data frame len:%ui", st->length); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data type:%ui, len:%ui", + st->type, st->length); - goto done; + if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { + st->state = sw_skip; + break; + } + + st->state = sw_type; + return NGX_OK; + + case sw_skip: + + if (--st->length == 0) { + st->state = sw_type; + } + + break; } return NGX_AGAIN; diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index 0c0af33b7..856f021bd 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -76,6 +76,7 @@ typedef struct { typedef struct { ngx_uint_t state; + ngx_uint_t type; ngx_uint_t length; ngx_http_v3_parse_varlen_int_t vlint; ngx_http_v3_parse_header_block_prefix_t prefix; @@ -107,6 +108,7 @@ typedef struct { typedef struct { ngx_uint_t state; + ngx_uint_t type; ngx_uint_t length; ngx_http_v3_parse_varlen_int_t vlint; } ngx_http_v3_parse_data_t; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fe3c79bf0..d9f4c9d55 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -418,7 +418,11 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, continue; } - /* rc == NGX_DONE */ + if (rc == NGX_DONE) { + return NGX_DONE; + } + + /* rc == NGX_OK */ ctx->size = st->length; ctx->state = sw_start; -- cgit v1.2.3 From 9fff3b7516936ef663926f33c30b15b08ecfd7ae Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 16 Sep 2020 12:27:23 +0100 Subject: HTTP/3: reject HTTP/2 frames. As per HTTP/3 draft 30, section 7.2.8: Frame types that were used in HTTP/2 where there is no corresponding HTTP/3 frame have also been reserved (Section 11.2.1). These frame types MUST NOT be sent, and their receipt MUST be treated as a connection error of type H3_FRAME_UNEXPECTED. --- src/http/v3/ngx_http_v3_parse.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 8f47b4d99..96f87b0b6 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -10,6 +10,10 @@ #include +#define ngx_http_v3_is_v2_frame(type) \ + ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) + + 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); @@ -182,6 +186,11 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, } st->type = st->vlint.value; + + if (ngx_http_v3_is_v2_frame(st->type)) { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + st->state = sw_length; break; @@ -986,6 +995,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse frame type:%ui", st->type); + if (ngx_http_v3_is_v2_frame(st->type)) { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + if (st->state == sw_first_type && st->type != NGX_HTTP_V3_FRAME_SETTINGS) { @@ -1581,6 +1594,10 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, goto done; } + if (ngx_http_v3_is_v2_frame(st->type)) { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + st->state = sw_length; break; -- cgit v1.2.3 From ebbcc329cb5f9878d2eb27f53e74e951bdaa449d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 16 Sep 2020 19:48:33 +0100 Subject: HTTP/3: removed HTTP/3 parser call from discard body filter. Request body discard is disabled for QUIC streams anyway. --- src/http/ngx_http_request_body.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index a6dbcf502..760a3cd05 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -804,16 +804,7 @@ ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) for ( ;; ) { - switch (r->http_version) { -#if (NGX_HTTP_V3) - case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_request_body(r, b, rb->chunked); - break; -#endif - - default: /* HTTP/1.x */ - rc = ngx_http_parse_chunked(r, b, rb->chunked); - } + rc = ngx_http_parse_chunked(r, b, rb->chunked); if (rc == NGX_OK) { -- cgit v1.2.3 From be719bbec8a9130223e25c4c22b52a73c44eb1ec Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 16 Sep 2020 20:21:03 +0100 Subject: HTTP/3: rearranged length check when parsing header. The new code looks simpler and is similar to other checks. --- src/http/v3/ngx_http_v3_parse.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 96f87b0b6..6ce5f10a7 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -212,6 +212,10 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, break; } + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + st->state = sw_prefix; break; @@ -225,7 +229,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, case sw_prefix: - if (st->length-- == 0) { + if (--st->length == 0) { return NGX_HTTP_V3_ERR_FRAME_ERROR; } @@ -234,10 +238,6 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return rc; } - if (st->length == 0) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - st->state = sw_verify; break; -- cgit v1.2.3 From 766fc16f55301bc37bf5403c9ef48661234b0108 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 21 Sep 2020 13:58:17 +0300 Subject: QUIC: prevented posted push event while in the draining state. If the push event was posted before ngx_quic_close_connection(), it could send data in the draining state. --- src/event/ngx_event_quic.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9301aa9a6..a5f85ee08 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1492,8 +1492,16 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) return NGX_AGAIN; } - if (qc->close.timer_set) { - return NGX_AGAIN; + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); } for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { @@ -1505,16 +1513,8 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } - if (qc->push.timer_set) { - ngx_del_timer(&qc->push); - } - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - if (qc->push.posted) { - ngx_delete_posted_event(&qc->push); + if (qc->close.timer_set) { + return NGX_AGAIN; } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From 0824d61fc9d28898e7d771825eca2880bc08df8b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 23 Sep 2020 13:13:04 +0100 Subject: QUIC: unbreak client certificate verification after 0d2b2664b41c. Initially, client certificate verification didn't work due to the missing hc->ssl on a QUIC stream, which is started to be set in 7738:7f0981be07c4. Then it was lost in 7999:0d2b2664b41c introducing "quic" listen parameter. This change re-adds hc->ssl back for all QUIC connections, similar to SSL. --- src/http/ngx_http_request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index e322fae4b..f1c6fa45c 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -308,6 +308,8 @@ ngx_http_init_connection(ngx_connection_t *c) ngx_quic_conf_t *qcf; ngx_http_ssl_srv_conf_t *sscf; + hc->ssl = 1; + #if (NGX_HTTP_V3) if (hc->addr_conf->http3) { -- cgit v1.2.3 From 1f90fccd972d3395239a7f6dea733dfe3627abc5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 29 Sep 2020 22:09:09 +0100 Subject: QUIC: switch stream context to a server selected by SNI. Previously the default server configuration context was used until the :authority or host header was parsed. This led to using the configuration parameters like client_header_buffer_size or request_pool_size from the default server rather than from the server selected by SNI. Also, the switch to the right server log is implemented. This issue manifested itself as QUIC stream being logged to the default server log until :authority or host is parsed. --- src/http/ngx_http_request.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f1c6fa45c..2a8a22564 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -305,8 +305,10 @@ ngx_http_init_connection(ngx_connection_t *c) #if (NGX_HTTP_QUIC) if (hc->addr_conf->quic) { - ngx_quic_conf_t *qcf; - ngx_http_ssl_srv_conf_t *sscf; + ngx_quic_conf_t *qcf; + ngx_http_connection_t *phc; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_loc_conf_t *clcf; hc->ssl = 1; @@ -340,6 +342,17 @@ ngx_http_init_connection(ngx_connection_t *c) ngx_quic_run(c, &sscf->ssl, qcf); return; } + + phc = c->qs->parent->data; + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; + hc->conf_ctx = phc->conf_ctx; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, + ngx_http_core_module); + ngx_set_connection_log(c, clcf->error_log); + } } #endif -- cgit v1.2.3 From 469f69bf2cfe0119835062f2de1e3a1ab76d799c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 30 Sep 2020 20:23:16 +0100 Subject: QUIC: resend frames by moving them to output queue. Previously, when a packet was declared lost, another packet was sent with the same frames. Now lost frames are moved to the output frame queue and push event is posted. This has the advantage of forming packets with more frames than before. Also, the start argument is removed from the ngx_quic_resend_frames() function as excess information. --- src/event/ngx_event_quic.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a5f85ee08..d0e8f383d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -289,8 +289,8 @@ static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, static void ngx_quic_pto_handler(ngx_event_t *ev); static void ngx_quic_lost_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); -static ngx_int_t ngx_quic_resend_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, ngx_quic_frame_t *start); +static void ngx_quic_resend_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); static void ngx_quic_push_handler(ngx_event_t *ev); static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, @@ -4118,10 +4118,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) "quic pto pnum:%uL pto_count:%ui level:%d", start->pnum, c->quic->pto_count, start->level); - if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } + ngx_quic_resend_frames(c, ctx); } } @@ -4209,9 +4206,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) break; } - if (ngx_quic_resend_frames(c, ctx, start) != NGX_OK) { - return NGX_ERROR; - } + ngx_quic_resend_frames(c, ctx); } } @@ -4234,18 +4229,19 @@ ngx_quic_detect_lost(ngx_connection_t *c) } -static ngx_int_t -ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_quic_frame_t *start) +static void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - ngx_queue_t *q, range; - ngx_quic_frame_t *f; - - ngx_queue_init(&range); - - /* send frames with same packet number to the wire */ + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_connection_t *qc; + qc = c->quic; q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); do { f = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -4257,13 +4253,17 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, q = ngx_queue_next(q); ngx_queue_remove(&f->queue); - ngx_queue_insert_tail(&range, &f->queue); + ngx_queue_insert_tail(&ctx->frames, &f->queue); } while (q != ngx_queue_sentinel(&ctx->sent)); ngx_quic_congestion_lost(c, start); - return ngx_quic_send_frames(c, ctx, &range); + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); } -- cgit v1.2.3 From 6c0be4b4cf63d730c556b5265a080b3a331b35ae Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 18 Sep 2020 15:53:37 +0300 Subject: QUIC: switched to using fixed-length server connection IDs. --- src/event/ngx_event_quic.c | 13 +++---------- src/event/ngx_event_quic.h | 2 ++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index d0e8f383d..f8e449517 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -910,24 +910,17 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) { - uint8_t len; ngx_quic_connection_t *qc; qc = c->quic; - if (RAND_bytes(&len, sizeof(len)) != 1) { - return NGX_ERROR; - } - - len = len % 10 + 10; - - qc->dcid.len = len; - qc->dcid.data = ngx_pnalloc(c->pool, len); + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, NGX_QUIC_SERVER_CID_LEN); if (qc->dcid.data == NULL) { return NGX_ERROR; } - if (RAND_bytes(qc->dcid.data, len) != 1) { + if (RAND_bytes(qc->dcid.data, NGX_QUIC_SERVER_CID_LEN) != 1) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 2399860af..ef3a15a73 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,6 +54,8 @@ #define NGX_QUIC_STREAM_BUFSIZE 65536 +#define NGX_QUIC_SERVER_CID_LEN 20 + typedef struct { /* configurable */ -- cgit v1.2.3 From 80958b29a2ed69237b463f848951e63480339b07 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 25 Sep 2020 21:46:55 +0300 Subject: QUIC: keep the entire packet size in pkt->len. Previously pkt->len kept the length of the packet remainder starting from pkt->raw->pos. --- src/event/ngx_event_quic.c | 2 +- src/event/ngx_event_quic_protection.c | 6 ++++-- src/event/ngx_event_quic_transport.c | 5 ++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f8e449517..f79ec408f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1676,7 +1676,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) */ /* b->pos is at header end, adjust by actual packet length */ - b->pos += pkt.len; + b->pos = pkt.data + pkt.len; p = ngx_quic_skip_zero_padding(b); } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 0d205a160..2f686cd7e 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -997,6 +997,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, uint64_t *largest_pn) { u_char clearflags, *p, *sample; + size_t len; uint8_t badflags; uint64_t pn, lpn; ngx_int_t pnl, rc, key_phase; @@ -1012,6 +1013,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, secret = pkt->secret; p = pkt->raw->pos; + len = pkt->data + pkt->len - p; /* draft-ietf-quic-tls-23#section-5.4.2: * the Packet Number field is assumed to be 4 bytes long @@ -1019,7 +1021,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, * AES-Based and ChaCha20-Based header protections sample 16 bytes */ - if (pkt->len < EVP_GCM_TLS_TAG_LEN + 4) { + if (len < EVP_GCM_TLS_TAG_LEN + 4) { return NGX_DECLINED; } @@ -1062,7 +1064,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, /* packet protection */ in.data = p; - in.len = pkt->len - pnl; + in.len = len - pnl; if (ngx_quic_long_pkt(pkt->flags)) { badflags = clearflags & NGX_QUIC_PKT_LONG_RESERVED_BIT; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1d270a2f2..be0aed78d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -511,7 +511,6 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) } pkt->raw->pos = p; - pkt->len = end - p; return NGX_OK; } @@ -561,7 +560,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } pkt->raw->pos = p; - pkt->len = varint; + pkt->len = p + varint - pkt->data; #ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len); @@ -600,7 +599,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } pkt->raw->pos = p; - pkt->len = plen; + pkt->len = p + plen - pkt->data; return NGX_OK; } -- cgit v1.2.3 From fe626bda8426fe7f0a9a9e4930eba30eb2b2f109 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 25 Sep 2020 21:47:28 +0300 Subject: QUIC: simplified packet header parsing. Now flags are processed in ngx_quic_input(), and raw->pos points to the first byte after the flags. Redundant checks from ngx_quic_parse_short_header() and ngx_quic_parse_long_header() are removed. --- src/event/ngx_event_quic.c | 1 + src/event/ngx_event_quic_transport.c | 28 ++-------------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f79ec408f..04fe56deb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1623,6 +1623,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.len = b->last - p; pkt.log = c->log; pkt.flags = p[0]; + pkt.raw->pos++; if (c->quic->in_retry) { rc = ngx_quic_retry_input(c, &pkt); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index be0aed78d..182b93f61 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -250,21 +250,9 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) u_char *p, *end; uint8_t idlen; - p = pkt->data; + p = pkt->raw->pos; end = pkt->data + pkt->len; - p = ngx_quic_read_uint8(p, end, &pkt->flags); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read flags"); - return NGX_ERROR; - } - - if (!ngx_quic_long_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a long packet"); - return NGX_ERROR; - } - p = ngx_quic_read_uint32(p, end, &pkt->version); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -473,21 +461,9 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) { u_char *p, *end; - p = pkt->data; + p = pkt->raw->pos; end = pkt->data + pkt->len; - p = ngx_quic_read_uint8(p, end, &pkt->flags); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read flags"); - return NGX_ERROR; - } - - if (!ngx_quic_short_pkt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic not a short packet"); - return NGX_ERROR; - } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic short packet flags:%xd", pkt->flags); -- cgit v1.2.3 From 99d4f2399dc466c52bcb30433c6ecc1613130ab8 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 30 Sep 2020 15:14:09 +0300 Subject: QUIC: packet processing refactoring. All packet header parsing is now performed by ngx_quic_parse_packet() function, located in the ngx_quic_transport.c file. The packet processing is centralized in the ngx_quic_process_packet() function which decides if the packet should be accepted, ignored or connection should be closed, depending on the connection state. As a result of refactoring, behavior has changed in some places: - minimal size of Initial packet is now always tested - connection IDs are always tested in existing connections - old keys are discarded on encryption level switch --- src/event/ngx_event_quic.c | 637 ++++++++++------------------------- src/event/ngx_event_quic_transport.c | 69 +++- src/event/ngx_event_quic_transport.h | 8 +- 3 files changed, 239 insertions(+), 475 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 04fe56deb..184afd8e1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -187,7 +187,7 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); -static ngx_int_t ngx_quic_retry(ngx_connection_t *c); +static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt); @@ -201,20 +201,14 @@ static void ngx_quic_close_timer_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc); -static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b); -static ngx_inline u_char *ngx_quic_skip_zero_padding(ngx_buf_t *b); -static ngx_int_t ngx_quic_retry_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_initial_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_handshake_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_early_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, + ngx_ssl_t *ssl, ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static void ngx_quic_discard_ctx(ngx_connection_t *c, + enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_app_input(ngx_connection_t *c, - ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); @@ -630,24 +624,13 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) { - ngx_int_t rc; - ngx_buf_t *b; - ngx_quic_header_t pkt; + ngx_int_t rc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); c->log->action = "QUIC initialization"; - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - b = c->buffer; - - pkt.log = c->log; - pkt.raw = b; - pkt.data = b->start; - pkt.len = b->last - b->start; - - rc = ngx_quic_new_connection(c, ssl, conf, &pkt); + rc = ngx_quic_input(c, c->buffer, ssl, conf); if (rc != NGX_OK) { ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); return; @@ -666,46 +649,10 @@ static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { - ngx_int_t rc; ngx_uint_t i; ngx_quic_tp_t *ctp; - ngx_quic_secrets_t *keys; - ngx_quic_send_ctx_t *ctx; ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic UDP datagram is too small for initial packet"); - return NGX_ERROR; - } - - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - if (pkt->version != NGX_QUIC_VERSION) { - return ngx_quic_negotiate_version(c, pkt); - } - - if (!ngx_quic_pkt_in(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid initial packet: 0x%xd", pkt->flags); - return NGX_ERROR; - } - - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_ERROR; - } - - if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { - /* 7.2. Negotiating Connection IDs */ - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic too short dcid in initial packet: length %i", - pkt->dcid.len); - return NGX_ERROR; - } c->log->action = "creating new quic connection"; @@ -804,15 +751,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->nclient_ids++; qc->curr_seqnum = 0; - keys = &c->quic->keys[ssl_encryption_initial]; - - if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, - &qc->odcid) - != NGX_OK) - { - return NGX_ERROR; - } - qc->initialized = 1; if (ngx_terminate || ngx_exiting) { @@ -820,60 +758,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - if (pkt->token.len) { - rc = ngx_quic_validate_token(c, pkt); - - if (rc == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); - return ngx_quic_retry(c); - } - - /* NGX_OK */ - qc->validated = 1; - - } else if (conf->retry) { - return ngx_quic_retry(c); - } - - pkt->secret = &keys->client; - pkt->level = ssl_encryption_initial; - pkt->plaintext = buf; - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - rc = ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn); - if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; - return rc; - } - - if (ngx_quic_init_connection(c) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_payload_handler(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - - /* pos is at header end, adjust by actual packet length */ - pkt->raw->pos += pkt->len; - - (void) ngx_quic_skip_zero_padding(pkt->raw); - - rc = ngx_quic_input(c, pkt->raw); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - /* rc == NGX_OK || rc == NGX_DECLINED */ - return NGX_OK; } @@ -939,7 +823,7 @@ ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) static ngx_int_t -ngx_quic_retry(ngx_connection_t *c) +ngx_quic_send_retry(ngx_connection_t *c) { ssize_t len; ngx_str_t res, token; @@ -1194,6 +1078,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_memcpy(&msec, tdec + len, sizeof(msec)); if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); return NGX_DECLINED; } @@ -1201,6 +1086,8 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) bad_token: + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; qc->error_reason = "invalid_token"; @@ -1339,7 +1226,7 @@ ngx_quic_input_handler(ngx_event_t *rev) b.last += n; qc->received += n; - rc = ngx_quic_input(c, &b); + rc = ngx_quic_input(c, &b, NULL, NULL); if (rc == NGX_ERROR) { ngx_quic_close_connection(c, NGX_ERROR); @@ -1603,7 +1490,8 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) static ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) +ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_ssl_t *ssl, + ngx_quic_conf_t *conf) { u_char *p; ngx_int_t rc; @@ -1625,29 +1513,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) pkt.flags = p[0]; pkt.raw->pos++; - if (c->quic->in_retry) { - rc = ngx_quic_retry_input(c, &pkt); - - } else if (ngx_quic_long_pkt(pkt.flags)) { - - if (ngx_quic_pkt_in(pkt.flags)) { - rc = ngx_quic_initial_input(c, &pkt); - - } else if (ngx_quic_pkt_hs(pkt.flags)) { - rc = ngx_quic_handshake_input(c, &pkt); - - } else if (ngx_quic_pkt_zrtt(pkt.flags)) { - rc = ngx_quic_early_input(c, &pkt); - - } else { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unknown long packet type"); - rc = NGX_DECLINED; - } - - } else { - rc = ngx_quic_app_input(c, &pkt); - } + rc = ngx_quic_process_packet(c, ssl, conf, &pkt); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1678,164 +1544,156 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b) /* b->pos is at header end, adjust by actual packet length */ b->pos = pkt.data + pkt.len; - p = ngx_quic_skip_zero_padding(b); - } - - return good ? NGX_OK : NGX_DECLINED; -} + /* firefox workaround: skip zero padding at the end of quic packet */ + while (b->pos < b->last && *(b->pos) == 0) { + b->pos++; + } -/* firefox workaround: skip zero padding at the end of quic packet */ -static ngx_inline u_char * -ngx_quic_skip_zero_padding(ngx_buf_t *b) -{ - while (b->pos < b->last && *(b->pos) == 0) { - b->pos++; + p = b->pos; } - return b->pos; + return good ? NGX_OK : NGX_DECLINED; } static ngx_int_t -ngx_quic_retry_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; - ngx_quic_secrets_t *keys; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_secrets_t *keys, *next, tmp; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - c->log->action = "retrying quic connection"; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - if (ngx_buf_size(pkt->raw) < NGX_QUIC_MIN_INITIAL_SIZE) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic UDP datagram is too small for initial packet"); - return NGX_DECLINED; - } + rc = ngx_quic_parse_packet(pkt); - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; + if (rc == NGX_DECLINED || rc == NGX_ERROR) { + return rc; } - if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_DECLINED; - } + qc = c->quic; - if (ngx_quic_pkt_zrtt(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic discard inflight 0-RTT packet"); - return NGX_DECLINED; - } + if (qc) { - if (!ngx_quic_pkt_in(pkt->flags)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid initial packet: 0x%xd", pkt->flags); - return NGX_DECLINED; - } + if (rc == NGX_ABORT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_DECLINED; + } - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } - if (!pkt->token.len) { - return NGX_DECLINED; - } + if (qc->in_retry) { - if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { - return NGX_ERROR; - } + c->log->action = "retrying quic connection"; - qc = c->quic; - qc->tp.initial_scid = c->quic->dcid; + if (pkt->level != ssl_encryption_initial) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic discard late retry packet"); + return NGX_DECLINED; + } - keys = &c->quic->keys[ssl_encryption_initial]; + if (!pkt->token.len) { + return NGX_DECLINED; + } - if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, - &qc->odcid) - != NGX_OK) - { - return NGX_ERROR; - } + if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { + return NGX_ERROR; + } - c->quic->in_retry = 0; + qc->tp.initial_scid = qc->dcid; + qc->in_retry = 0; - if (ngx_quic_validate_token(c, pkt) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - return NGX_ERROR; - } + keys = &qc->keys[ssl_encryption_initial]; - qc->validated = 1; + if (ngx_quic_set_initial_secret(c->pool, &keys->client, + &keys->server, &qc->odcid) + != NGX_OK) + { + return NGX_ERROR; + } - pkt->secret = &keys->client; - pkt->level = ssl_encryption_initial; - pkt->plaintext = buf; + if (ngx_quic_validate_token(c, pkt) != NGX_OK) { + return NGX_ERROR; + } - ctx = ngx_quic_get_send_ctx(qc, pkt->level); + qc->validated = 1; + } - rc = ngx_quic_decrypt(pkt, NULL, &ctx->largest_pn); - if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; - return rc; - } + } else { - if (ngx_quic_init_connection(c) != NGX_OK) { - return NGX_ERROR; - } + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } - if (ngx_quic_payload_handler(c, pkt) != NGX_OK) { - return NGX_ERROR; - } + if (pkt->level == ssl_encryption_initial) { - return NGX_OK; -} + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: length %i", pkt->dcid.len); + return NGX_ERROR; + } + rc = ngx_quic_new_connection(c, ssl, conf, pkt); + if (rc != NGX_OK) { + return rc; + } -static ngx_int_t -ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - ngx_int_t rc; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_secrets_t *keys; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + if (pkt->token.len) { + if (ngx_quic_validate_token(c, pkt) != NGX_OK) { + return NGX_ERROR; + } - c->log->action = "processing initial quic packet"; + } else if (conf->retry) { + return ngx_quic_send_retry(c); + } - ssl_conn = c->ssl->connection; + qc = c->quic; - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } + keys = &qc->keys[ssl_encryption_initial]; - if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_DECLINED; - } + if (ngx_quic_set_initial_secret(c->pool, &keys->client, + &keys->server, &qc->odcid) + != NGX_OK) + { + return NGX_ERROR; + } - qc = c->quic; + } else if (pkt->level == ssl_encryption_application) { + return NGX_DECLINED; - if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - return NGX_DECLINED; + } else { + return NGX_ERROR; + } } - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { + keys = &qc->keys[pkt->level]; + + if (keys->client.key.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no level %d keys yet, ignoring packet", pkt->level); return NGX_DECLINED; } - keys = &qc->keys[ssl_encryption_initial]; + next = &qc->next_key; pkt->secret = &keys->client; - pkt->level = ssl_encryption_initial; + pkt->next = &next->client; + pkt->key_phase = qc->key_phase; pkt->plaintext = buf; ctx = ngx_quic_get_send_ctx(qc, pkt->level); + ssl_conn = c->ssl ? c->ssl->connection : NULL; + rc = ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn); if (rc != NGX_OK) { qc->error = pkt->error; @@ -1843,167 +1701,115 @@ ngx_quic_initial_input(ngx_connection_t *c, ngx_quic_header_t *pkt) return rc; } - return ngx_quic_payload_handler(c, pkt); -} - - -static ngx_int_t -ngx_quic_handshake_input(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - ngx_int_t rc; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_secrets_t *keys; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "processing handshake quic packet"; - - qc = c->quic; - - keys = &c->quic->keys[ssl_encryption_handshake]; - - if (keys->client.key.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no read keys yet, packet ignored"); - return NGX_DECLINED; + if (c->ssl == NULL) { + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } } - /* extract cleartext data into pkt */ - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; + if (pkt->level == ssl_encryption_handshake) { + /* + * 4.10.1. The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ngx_quic_discard_ctx(c, ssl_encryption_initial); + qc->validated = 1; } - if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_DECLINED; + if (pkt->level != ssl_encryption_application) { + return ngx_quic_payload_handler(c, pkt); } - if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - return NGX_DECLINED; - } + ngx_gettimeofday(&pkt->received); - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } + /* switch keys on Key Phase change */ - pkt->secret = &keys->client; - pkt->level = ssl_encryption_handshake; - pkt->plaintext = buf; + if (pkt->key_update) { + qc->key_phase ^= 1; - ctx = ngx_quic_get_send_ctx(qc, pkt->level); + tmp = *keys; + *keys = *next; + *next = tmp; + } - rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); + rc = ngx_quic_payload_handler(c, pkt); if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; return rc; } - /* - * 4.10.1. The successful use of Handshake packets indicates - * that no more Initial packets need to be exchanged - */ - ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_initial); - - while (!ngx_queue_empty(&ctx->sent)) { - q = ngx_queue_head(&ctx->sent); - ngx_queue_remove(q); + /* generate next keys */ - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_ack(c, f); - ngx_quic_free_frame(c, f); + if (pkt->key_update) { + if (ngx_quic_key_update(c, keys, next) != NGX_OK) { + return NGX_ERROR; + } } - qc->validated = 1; - qc->pto_count = 0; - - return ngx_quic_payload_handler(c, pkt); + return NGX_OK; } -static ngx_int_t -ngx_quic_early_input(ngx_connection_t *c, ngx_quic_header_t *pkt) +static void +ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) { - ngx_int_t rc; - ngx_quic_secrets_t *keys; + ngx_queue_t *q; + ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "processing early data quic packet"; qc = c->quic; - /* extract cleartext data into pkt */ - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } - - if (pkt->version != NGX_QUIC_VERSION) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_DECLINED; - } - - if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - return NGX_DECLINED; - } - - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } - - keys = &c->quic->keys[ssl_encryption_early_data]; - - if (keys->client.key.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no 0-RTT keys yet, packet ignored"); - return NGX_DECLINED; + if (qc->keys[level].client.key.len == 0) { + return; } + qc->keys[level].client.key.len = 0; + qc->pto_count = 0; - pkt->secret = &keys->client; - pkt->level = ssl_encryption_early_data; - pkt->plaintext = buf; + ctx = ngx_quic_get_send_ctx(qc, level); - ctx = ngx_quic_get_send_ctx(qc, pkt->level); + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); - rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); - if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; - return rc; + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); } - - return ngx_quic_payload_handler(c, pkt); } static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { + ngx_str_t *dcid; ngx_queue_t *q; ngx_quic_send_ctx_t *ctx; ngx_quic_client_id_t *cid; - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + dcid = (pkt->level == ssl_encryption_early_data) ? &qc->odcid : &qc->dcid; - if (ngx_quic_pkt_zrtt(pkt->flags) - || (ngx_quic_pkt_in(pkt->flags) && ctx->largest_ack == (uint64_t) -1)) + if (pkt->dcid.len == dcid->len + && ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) == 0) { - if (pkt->dcid.len == qc->odcid.len - && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0) - { - goto found; + if (pkt->level == ssl_encryption_application) { + return NGX_OK; } + + goto found; } - if (!ngx_quic_pkt_zrtt(pkt->flags)) { - if (pkt->dcid.len == qc->dcid.len - && ngx_memcmp(pkt->dcid.data, qc->dcid.data, qc->dcid.len) == 0) + /* + * a packet sent in response to an initial client packet might be lost, + * thus check also for old dcid + */ + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + if (pkt->level == ssl_encryption_initial + && ctx->largest_ack == (uint64_t) -1) + { + if (pkt->dcid.len == qc->odcid.len + && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0) { goto found; } @@ -2027,80 +1833,8 @@ found: } } - - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - ngx_int_t rc; - ngx_quic_secrets_t *keys, *next, tmp; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "processing application data quic packet"; - - qc = c->quic; - - keys = &c->quic->keys[ssl_encryption_application]; - next = &c->quic->next_key; - - if (keys->client.key.len == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no read keys yet, packet ignored"); - return NGX_DECLINED; - } - - if (ngx_quic_parse_short_header(pkt, &qc->dcid) != NGX_OK) { - return NGX_DECLINED; - } - - pkt->secret = &keys->client; - pkt->next = &next->client; - pkt->key_phase = c->quic->key_phase; - pkt->level = ssl_encryption_application; - pkt->plaintext = buf; - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - rc = ngx_quic_decrypt(pkt, c->ssl->connection, &ctx->largest_pn); - if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; - return rc; - } - - ngx_gettimeofday(&pkt->received); - - /* switch keys on Key Phase change */ - - if (pkt->key_update) { - c->quic->key_phase ^= 1; - - tmp = *keys; - *keys = *next; - *next = tmp; - } - - rc = ngx_quic_payload_handler(c, pkt); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - /* generate next keys */ - - if (pkt->key_update) { - if (ngx_quic_key_update(c, keys, next) != NGX_OK) { - return NGX_ERROR; - } - } - - return rc; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; } @@ -2981,9 +2715,7 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { int n, sslerr; - ngx_queue_t *q; ngx_ssl_conn_t *ssl_conn; - ngx_quic_send_ctx_t *ctx; ngx_quic_crypto_frame_t *f; f = &frame->u.crypto; @@ -3060,18 +2792,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) * 4.10.2 An endpoint MUST discard its handshake keys * when the TLS handshake is confirmed */ - ctx = ngx_quic_get_send_ctx(c->quic, ssl_encryption_handshake); - - while (!ngx_queue_empty(&ctx->sent)) { - q = ngx_queue_head(&ctx->sent); - ngx_queue_remove(q); - - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_ack(c, frame); - ngx_quic_free_frame(c, frame); - } - - c->quic->pto_count = 0; + ngx_quic_discard_ctx(c, ssl_encryption_handshake); } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 182b93f61..0dd56abd9 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -67,6 +67,12 @@ static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + size_t dcid_len); +static ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); + static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); @@ -245,6 +251,52 @@ ngx_quic_error_text(uint64_t error_code) ngx_int_t +ngx_quic_parse_packet(ngx_quic_header_t *pkt) +{ + if (!ngx_quic_long_pkt(pkt->flags)) { + pkt->level = ssl_encryption_application; + + return ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN); + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; + } + + if (pkt->version != NGX_QUIC_VERSION) { + return NGX_ABORT; + } + + if (ngx_quic_pkt_in(pkt->flags)) { + + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } + + pkt->level = ssl_encryption_initial; + + return ngx_quic_parse_initial_header(pkt); + } + + if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; + + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; + + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown long packet type"); + return NGX_DECLINED; + } + + return ngx_quic_parse_handshake_header(pkt); +} + + +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt) { u_char *p, *end; @@ -456,8 +508,8 @@ ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, } -ngx_int_t -ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) { u_char *p, *end; @@ -472,14 +524,9 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) return NGX_ERROR; } - if (ngx_memcmp(p, dcid->data, dcid->len) != 0) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid"); - return NGX_ERROR; - } - - pkt->dcid.len = dcid->len; + pkt->dcid.len = dcid_len; - p = ngx_quic_read_bytes(p, end, dcid->len, &pkt->dcid.data); + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic packet is too small to read dcid"); @@ -492,7 +539,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid) } -ngx_int_t +static ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) { u_char *p, *end; @@ -548,7 +595,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } -ngx_int_t +static ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) { u_char *p, *end; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 63c6a2191..5417c664b 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -319,23 +319,19 @@ typedef struct { u_char *ngx_quic_error_text(uint64_t error_code); +ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); + size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); -ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); -ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, - ngx_str_t *dcid); size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, u_char **start); -ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); -ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); - ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); -- cgit v1.2.3 From 2c3ada57224a34403948e36772bb6dc65e80d353 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 30 Sep 2020 21:27:52 +0300 Subject: QUIC: refined the "c->quic->initialized" flag usage. The flag is tied to the initial secret creation. The presence of c->quic pointer is sufficient to enable execution of ngx_quic_close_quic(). The ngx_quic_new_connection() function now returns the allocated quic connection object and the c->quic pointer is set by the caller. If an early error occurs before secrets initialization (i.e. in cases of invalid retry token or nginx exiting), it is still possible to generate an error response by trying to initialize secrets directly in the ngx_quic_send_cc() function. Before the change such early errors failed to send proper connection close message and logged an error. An auxilliary ngx_quic_init_secrets() function is introduced to avoid verbose call to ngx_quic_set_initial_secret() requiring local variable. --- src/event/ngx_event_quic.c | 119 +++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 184afd8e1..a4bf5687b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -182,11 +182,12 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); -static ngx_int_t ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, + ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); -static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid); +static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_str_t *odcid); static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, @@ -205,6 +206,7 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_ssl_t *ssl, ngx_quic_conf_t *conf); static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, @@ -266,7 +268,8 @@ static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum); -static ngx_quic_client_id_t *ngx_quic_alloc_connection_id(ngx_connection_t *c); +static ngx_quic_client_id_t *ngx_quic_alloc_connection_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -645,7 +648,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) } -static ngx_int_t +static ngx_quic_connection_t * ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { @@ -658,7 +661,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); if (qc == NULL) { - return NGX_ERROR; + return NULL; } ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, @@ -701,7 +704,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->push.handler = ngx_quic_push_handler; qc->push.cancelable = 1; - c->quic = qc; qc->ssl = ssl; qc->conf = conf; qc->tp = conf->tp; @@ -722,25 +724,25 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; qc->congestion.recovery_start = ngx_current_msec; - if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { - return NGX_ERROR; + if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) { + return NULL; } #if (NGX_QUIC_DRAFT_VERSION >= 28) - qc->tp.original_dcid = c->quic->odcid; + qc->tp.original_dcid = qc->odcid; #endif - qc->tp.initial_scid = c->quic->dcid; + qc->tp.initial_scid = qc->dcid; qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); if (qc->scid.data == NULL) { - return NGX_ERROR; + return NULL; } ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - cid = ngx_quic_alloc_connection_id(c); + cid = ngx_quic_alloc_connection_id(c, qc); if (cid == NULL) { - return NGX_ERROR; + return NULL; } cid->seqnum = 0; @@ -751,14 +753,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->nclient_ids++; qc->curr_seqnum = 0; - qc->initialized = 1; - - if (ngx_terminate || ngx_exiting) { - qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; - return NGX_ERROR; - } - - return NGX_OK; + return qc; } @@ -792,12 +787,9 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) static ngx_int_t -ngx_quic_new_dcid(ngx_connection_t *c, ngx_str_t *odcid) +ngx_quic_new_dcid(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_str_t *odcid) { - ngx_quic_connection_t *qc; - - qc = c->quic; - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; qc->dcid.data = ngx_pnalloc(c->pool, NGX_QUIC_SERVER_CID_LEN); if (qc->dcid.data == NULL) { @@ -1252,7 +1244,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_close_connection, rc: %i", rc); - if (!c->quic || !c->quic->initialized) { + if (!c->quic) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close connection early error"); @@ -1603,19 +1595,14 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_DECLINED; } - if (ngx_quic_new_dcid(c, &pkt->dcid) != NGX_OK) { + if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) { return NGX_ERROR; } qc->tp.initial_scid = qc->dcid; qc->in_retry = 0; - keys = &qc->keys[ssl_encryption_initial]; - - if (ngx_quic_set_initial_secret(c->pool, &keys->client, - &keys->server, &qc->odcid) - != NGX_OK) - { + if (ngx_quic_init_secrets(c) != NGX_OK) { return NGX_ERROR; } @@ -1642,9 +1629,16 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - rc = ngx_quic_new_connection(c, ssl, conf, pkt); - if (rc != NGX_OK) { - return rc; + qc = ngx_quic_new_connection(c, ssl, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; + } + + c->quic = qc; + + if (ngx_terminate || ngx_exiting) { + qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; + return NGX_ERROR; } if (pkt->token.len) { @@ -1656,14 +1650,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, return ngx_quic_send_retry(c); } - qc = c->quic; - - keys = &qc->keys[ssl_encryption_initial]; - - if (ngx_quic_set_initial_secret(c->pool, &keys->client, - &keys->server, &qc->odcid) - != NGX_OK) - { + if (ngx_quic_init_secrets(c) != NGX_OK) { return NGX_ERROR; } @@ -1749,6 +1736,28 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, } +static ngx_int_t +ngx_quic_init_secrets(ngx_connection_t *c) +{ + ngx_quic_secrets_t *keys; + ngx_quic_connection_t *qc; + + qc =c->quic; + keys = &qc->keys[ssl_encryption_initial]; + + if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, + &qc->odcid) + != NGX_OK) + { + return NGX_ERROR; + } + + qc->initialized = 1; + + return NGX_OK; +} + + static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) { @@ -2137,6 +2146,13 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } + if (!qc->initialized) { + /* try to initialize secrets to send an early error */ + if (ngx_quic_init_secrets(c) != NGX_OK) { + return NGX_OK; + } + } + if (qc->closing && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) { @@ -3335,7 +3351,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, } else { - cid = ngx_quic_alloc_connection_id(c); + cid = ngx_quic_alloc_connection_id(c, qc); if (cid == NULL) { return NGX_ERROR; } @@ -3439,13 +3455,10 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, static ngx_quic_client_id_t * -ngx_quic_alloc_connection_id(ngx_connection_t *c) +ngx_quic_alloc_connection_id(ngx_connection_t *c, ngx_quic_connection_t *qc) { - ngx_queue_t *q; - ngx_quic_client_id_t *cid; - ngx_quic_connection_t *qc; - - qc = c->quic; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; if (!ngx_queue_empty(&qc->free_client_ids)) { -- cgit v1.2.3 From f797a8a5b5a2012b0cae9745f05386b628365cb7 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 30 Sep 2020 20:54:46 +0300 Subject: QUIC: added stateless reset support. The new "quic_stateless_reset_token_key" directive is added. It sets the endpoint key used to generate stateless reset tokens and enables feature. If the endpoint receives short-header packet that can't be matched to existing connection, a stateless reset packet is generated with a proper token. If a valid stateless reset token is found in the incoming packet, the connection is closed. Example configuration: http { quic_stateless_reset_token_key "foo"; ... } --- src/event/ngx_event_quic.c | 150 +++++++++++++++++++++++++++++++- src/event/ngx_event_quic.h | 6 +- src/event/ngx_event_quic_protection.c | 56 ++++++++++++ src/event/ngx_event_quic_protection.h | 2 + src/event/ngx_event_quic_transport.c | 16 +++- src/event/ngx_event_quic_transport.h | 7 +- src/http/modules/ngx_http_quic_module.c | 13 ++- 7 files changed, 237 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a4bf5687b..5b99e99b1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -36,6 +36,16 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +/* + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + typedef struct { ngx_rbtree_t tree; @@ -154,7 +164,7 @@ typedef struct { uint64_t seqnum; size_t len; u_char id[NGX_QUIC_CID_LEN_MAX]; - u_char sr_token[NGX_QUIC_SRT_LEN]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; } ngx_quic_client_id_t; @@ -184,6 +194,10 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, @@ -757,6 +771,105 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, } +static ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (conf->sr_token_key.len == 0) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) c->send(c, buf, len); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *tail, ch; + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = c->quic; + + /* A stateless reset uses an entire UDP datagram */ + if (pkt->raw->start != pkt->data) { + return NGX_DECLINED; + } + + tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (cid->seqnum == 0) { + /* no stateless reset token in initial connection id */ + continue; + } + + /* constant time comparison */ + + for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { + ch |= tail[i] ^ cid->sr_token[i]; + } + + if (ch == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) { @@ -1116,6 +1229,20 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif + if (qc->conf->sr_token_key.len) { + qc->tp.sr_enabled = 1; + + if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, + qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_hexdump(c->log, "quic stateless reset token", + qc->tp.sr_token, NGX_QUIC_SR_TOKEN_LEN); + } + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); /* always succeeds */ @@ -1578,6 +1705,21 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, } if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { + + if (pkt->level == ssl_encryption_application) { + if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); + + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + + return NGX_OK; + } + + return ngx_quic_send_stateless_reset(c, qc->conf, pkt); + } + return NGX_DECLINED; } @@ -1655,7 +1797,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, } } else if (pkt->level == ssl_encryption_application) { - return NGX_DECLINED; + return ngx_quic_send_stateless_reset(c, conf, pkt); } else { return NGX_ERROR; @@ -3337,7 +3479,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, if (cid->len != f->len || ngx_strncmp(cid->id, f->cid, f->len) != 0 - || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN) != 0) + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) { /* * ..a sequence number is used for different connection IDs, @@ -3360,7 +3502,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, cid->len = f->len; ngx_memcpy(cid->id, f->cid, f->len); - ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN); + ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); ngx_queue_insert_tail(&qc->client_ids, &cid->queue); qc->nclient_ids++; diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index ef3a15a73..1249a8b9e 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -56,6 +56,8 @@ #define NGX_QUIC_SERVER_CID_LEN 20 +#define NGX_QUIC_SR_TOKEN_LEN 16 + typedef struct { /* configurable */ @@ -75,9 +77,10 @@ typedef struct { ngx_str_t original_dcid; ngx_str_t initial_scid; ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + ngx_uint_t sr_enabled; /* TODO */ - u_char stateless_reset_token[16]; void *preferred_address; } ngx_quic_tp_t; @@ -87,6 +90,7 @@ typedef struct { ngx_flag_t retry; ngx_flag_t require_alpn; u_char token_key[32]; /* AES 256 */ + ngx_str_t sr_token_key; /* stateless reset token key */ } ngx_quic_conf_t; diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 2f686cd7e..b77d65971 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -923,6 +923,62 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, + u_char *token) +{ + uint8_t *p; + size_t is_len, key_len, info_len; + ngx_str_t label; + const EVP_MD *digest; + uint8_t info[20]; + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t key[SHA256_DIGEST_LENGTH]; + + /* 10.4.2. Calculating a Stateless Reset Token */ + + digest = EVP_sha256(); + ngx_str_set(&label, "sr_token_key"); + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + cid->data, cid->len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "ngx_hkdf_extract(%V) failed", &label); + return NGX_ERROR; + } + + key_len = SHA256_DIGEST_LENGTH; + + info_len = 2 + 1 + label.len + 1; + + info[0] = 0; + info[1] = key_len; + info[2] = label.len; + + p = ngx_cpymem(&info[3], label.data, label.len); + *p = '\0'; + + if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "ngx_hkdf_expand(%V) failed", &label); + return NGX_ERROR; + } + + ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); + +#if (NGX_DEBUG) + ngx_quic_hexdump(c->log, "quic stateless reset token", token, + NGX_QUIC_SR_TOKEN_LEN); +#endif + + return NGX_OK; +} + + static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, uint64_t *largest_pn) diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index 37d7c37b1..b7916a498 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -39,6 +39,8 @@ int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, ngx_int_t ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, ngx_quic_secrets_t *next); +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + ngx_str_t *key, u_char *token); ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 0dd56abd9..9ee18ca9b 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -774,7 +774,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SRT_LEN, f->u.ncid.srt); + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); if (p == NULL) { goto error; } @@ -1553,7 +1553,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, case NGX_QUIC_TP_ORIGINAL_DCID: case NGX_QUIC_TP_PREFERRED_ADDRESS: case NGX_QUIC_TP_RETRY_SCID: - case NGX_QUIC_TP_STATELESS_RESET_TOKEN: + case NGX_QUIC_TP_SR_TOKEN: ngx_log_error(NGX_LOG_INFO, log, 0, "quic client sent forbidden transport param" " id 0x%xL", id); @@ -1810,6 +1810,12 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, } #endif + if (tp->sr_enabled) { + 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; } @@ -1851,6 +1857,12 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, } #endif + if (tp->sr_enabled) { + 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/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 5417c664b..a62f4d586 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -101,7 +101,7 @@ /* Transport parameters */ #define NGX_QUIC_TP_ORIGINAL_DCID 0x00 #define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 -#define NGX_QUIC_TP_STATELESS_RESET_TOKEN 0x02 +#define NGX_QUIC_TP_SR_TOKEN 0x02 #define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 #define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 #define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 @@ -120,9 +120,6 @@ #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 -#define NGX_QUIC_SRT_LEN 16 - - typedef struct { uint64_t largest; uint64_t delay; @@ -141,7 +138,7 @@ typedef struct { uint64_t retire; uint8_t len; u_char cid[NGX_QUIC_CID_LEN_MAX]; - u_char srt[NGX_QUIC_SRT_LEN]; + u_char srt[NGX_QUIC_SR_TOKEN_LEN]; } ngx_quic_new_conn_id_frame_t; diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 9888e2eae..ec70c7286 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -125,6 +125,13 @@ static ngx_command_t ngx_http_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, + { ngx_string("quic_stateless_reset_token_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, sr_token_key), + NULL }, + ngx_null_command }; @@ -223,8 +230,10 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.original_dcid = { 0, NULL }; * conf->tp.initial_scid = { 0, NULL }; * conf->tp.retry_scid = { 0, NULL }; - * conf->tp.stateless_reset_token = { 0 } + * conf->tp.sr_token = { 0 } + * conf->tp.sr_enabled = 0 * conf->tp.preferred_address = NULL + * conf->sr_token_key = { 0, NULL } */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; @@ -304,6 +313,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + return NGX_CONF_OK; } -- cgit v1.2.3 From 0f843cfb74dd4dab7bff4d9a0f7e73b8b8cb61f0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 1 Oct 2020 10:04:35 +0300 Subject: QUIC: moved ssl configuration pointer to quic configuration. The ssl configuration is obtained at config time and saved for future use. --- src/event/ngx_event_quic.c | 32 ++++++++++++++------------------ src/event/ngx_event_quic.h | 3 ++- src/http/modules/ngx_http_quic_module.c | 5 +++++ src/http/ngx_http_request.c | 6 +----- src/stream/ngx_stream_handler.c | 8 ++------ src/stream/ngx_stream_quic_module.c | 7 ++++++- 6 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5b99e99b1..a9c092176 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -120,8 +120,6 @@ struct ngx_quic_connection_s { ngx_quic_conf_t *conf; - ngx_ssl_t *ssl; - ngx_event_t push; ngx_event_t pto; ngx_event_t close; @@ -193,7 +191,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, - ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, @@ -217,8 +215,8 @@ static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc); static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, - ngx_ssl_t *ssl, ngx_quic_conf_t *conf); -static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, + ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); static void ngx_quic_discard_ctx(ngx_connection_t *c, @@ -639,7 +637,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void -ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) +ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) { ngx_int_t rc; @@ -647,7 +645,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) c->log->action = "QUIC initialization"; - rc = ngx_quic_input(c, c->buffer, ssl, conf); + rc = ngx_quic_input(c, c->buffer, conf); if (rc != NGX_OK) { ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); return; @@ -663,8 +661,8 @@ ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf) static ngx_quic_connection_t * -ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) { ngx_uint_t i; ngx_quic_tp_t *ctp; @@ -718,7 +716,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, qc->push.handler = ngx_quic_push_handler; qc->push.cancelable = 1; - qc->ssl = ssl; qc->conf = conf; qc->tp = conf->tp; @@ -1211,7 +1208,7 @@ ngx_quic_init_connection(ngx_connection_t *c) qc = c->quic; - if (ngx_ssl_create_connection(qc->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { return NGX_ERROR; } @@ -1345,7 +1342,7 @@ ngx_quic_input_handler(ngx_event_t *rev) b.last += n; qc->received += n; - rc = ngx_quic_input(c, &b, NULL, NULL); + rc = ngx_quic_input(c, &b, NULL); if (rc == NGX_ERROR) { ngx_quic_close_connection(c, NGX_ERROR); @@ -1609,8 +1606,7 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) static ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf) +ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { u_char *p; ngx_int_t rc; @@ -1632,7 +1628,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_ssl_t *ssl, pkt.flags = p[0]; pkt.raw->pos++; - rc = ngx_quic_process_packet(c, ssl, conf, &pkt); + rc = ngx_quic_process_packet(c, conf, &pkt); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -1677,8 +1673,8 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_ssl_t *ssl, static ngx_int_t -ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) +ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) { ngx_int_t rc; ngx_ssl_conn_t *ssl_conn; @@ -1771,7 +1767,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_ssl_t *ssl, return NGX_ERROR; } - qc = ngx_quic_new_connection(c, ssl, conf, pkt); + qc = ngx_quic_new_connection(c, conf, pkt); if (qc == NULL) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 1249a8b9e..2dac905e7 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -86,6 +86,7 @@ typedef struct { typedef struct { + ngx_ssl_t *ssl; ngx_quic_tp_t tp; ngx_flag_t retry; ngx_flag_t require_alpn; @@ -114,7 +115,7 @@ struct ngx_quic_stream_s { }; -void ngx_quic_run(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_quic_conf_t *conf); +void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index ec70c7286..34898984a 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -262,6 +262,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_quic_conf_t *prev = parent; ngx_quic_conf_t *conf = child; + ngx_http_ssl_srv_conf_t *sscf; + ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, prev->tp.max_idle_timeout, 60000); @@ -315,6 +317,9 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + conf->ssl = &sscf->ssl; + return NGX_CONF_OK; } diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 2a8a22564..b3e27c62e 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -307,7 +307,6 @@ ngx_http_init_connection(ngx_connection_t *c) if (hc->addr_conf->quic) { ngx_quic_conf_t *qcf; ngx_http_connection_t *phc; - ngx_http_ssl_srv_conf_t *sscf; ngx_http_core_loc_conf_t *clcf; hc->ssl = 1; @@ -336,10 +335,7 @@ ngx_http_init_connection(ngx_connection_t *c) qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_quic_module); - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, - ngx_http_ssl_module); - - ngx_quic_run(c, &sscf->ssl, qcf); + ngx_quic_run(c, qcf); return; } diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 2b0848a67..33f7bc191 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -118,18 +118,14 @@ ngx_stream_init_connection(ngx_connection_t *c) #if (NGX_STREAM_QUIC) if (addr_conf->quic) { - ngx_quic_conf_t *qcf; - ngx_stream_ssl_conf_t *scf; + ngx_quic_conf_t *qcf; if (c->qs == NULL) { c->log->connection = c->number; qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, ngx_stream_quic_module); - scf = ngx_stream_get_module_srv_conf(addr_conf->ctx, - ngx_stream_ssl_module); - - ngx_quic_run(c, &scf->ssl, qcf); + ngx_quic_run(c, qcf); return; } } diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 362855f1a..ba601a030 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -28,7 +28,7 @@ static ngx_conf_post_t ngx_stream_quic_max_udp_payload_size_post = { ngx_stream_quic_max_udp_payload_size }; static ngx_conf_num_bounds_t ngx_stream_quic_ack_delay_exponent_bounds = { ngx_conf_check_num_bounds, 0, 20 }; -static ngx_conf_num_bounds_t +static ngx_conf_num_bounds_t ngx_stream_quic_active_connection_id_limit_bounds = { ngx_conf_check_num_bounds, 2, -1 }; @@ -251,6 +251,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_quic_conf_t *prev = parent; ngx_quic_conf_t *conf = child; + ngx_stream_ssl_conf_t *scf; + ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, prev->tp.max_idle_timeout, 60000); @@ -302,6 +304,9 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); + conf->ssl = &scf->ssl; + return NGX_CONF_OK; } -- cgit v1.2.3 From 154536a64fd7b6bc567e976bde0b6edd00c3cd65 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Oct 2020 12:00:12 +0100 Subject: QUIC: fixed build with OpenSSL after bed310672f39. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a9c092176..61e46a471 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1221,7 +1221,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #ifdef SSL_READ_EARLY_DATA_SUCCESS - if (SSL_CTX_get_max_early_data(qc->ssl->ctx)) { + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { SSL_set_quic_early_data_enabled(ssl_conn, 1); } #endif -- cgit v1.2.3 From b64446f6f9e1b91e6d805cc56a72c1ad8e42a049 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Oct 2020 12:09:47 +0100 Subject: QUIC: fixed clang-ast asserts. --- src/event/ngx_event_quic.c | 2 +- src/event/ngx_event_quic_protection.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 61e46a471..82bb8902d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1237,7 +1237,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } ngx_quic_hexdump(c->log, "quic stateless reset token", - qc->tp.sr_token, NGX_QUIC_SR_TOKEN_LEN); + qc->tp.sr_token, (size_t) NGX_QUIC_SR_TOKEN_LEN); } len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index b77d65971..0bb9e8f87 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -972,7 +972,7 @@ ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, #if (NGX_DEBUG) ngx_quic_hexdump(c->log, "quic stateless reset token", token, - NGX_QUIC_SR_TOKEN_LEN); + (size_t) NGX_QUIC_SR_TOKEN_LEN); #endif return NGX_OK; -- cgit v1.2.3 From 7bd3868715b57f339ea5d61372bbca449509102f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Oct 2020 12:10:22 +0100 Subject: QUIC: speeding up handshake completion. As per quic-recovery draft, section-6.2.3: resend CRYPTO frames when receiving an Initial packet containing duplicate CRYPTO data. --- src/event/ngx_event_quic.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 82bb8902d..f223f5d7f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2661,7 +2661,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, == NGX_DONE) { /* old/duplicate data range */ - return NGX_OK; + return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; } /* intersecting data range, frame modified */ @@ -2844,6 +2844,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { uint64_t last; + ngx_int_t rc; ngx_quic_connection_t *qc; ngx_quic_crypto_frame_t *f; ngx_quic_frames_stream_t *fs; @@ -2860,8 +2861,19 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, - NULL); + rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, + NULL); + if (rc != NGX_DECLINED) { + return rc; + } + + /* speeding up handshake completion */ + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_resend_frames(c, ngx_quic_get_send_ctx(qc, pkt->level)); + } + + return NGX_OK; } -- cgit v1.2.3 From ee4a6024cc0097d8ab29af8441172846de6b0f64 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Oct 2020 12:10:37 +0100 Subject: QUIC: a bandaid for calculating ack_delay with non-monotonic time. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f223f5d7f..ed7922909 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2265,6 +2265,7 @@ ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, ngx_gettimeofday(&tv); ack_delay = (tv.tv_sec - received->tv_sec) * 1000000 + tv.tv_usec - received->tv_usec; + ack_delay = ngx_max(ack_delay, 0); ack_delay >>= c->quic->ctp.ack_delay_exponent; } -- cgit v1.2.3 From a06a3f6aba43525e32cb033e0472d25a15f202a5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 1 Oct 2020 22:20:51 +0300 Subject: QUIC: fixed handling of incorrect packets. Instead of ignoring, connection was closed. This was broken in d0d3fc0697a0. --- src/event/ngx_event_quic_transport.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 9ee18ca9b..9a1704f10 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -256,7 +256,12 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) if (!ngx_quic_long_pkt(pkt->flags)) { pkt->level = ssl_encryption_application; - return ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN); + if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) + { + return NGX_DECLINED; + } + + return NGX_OK; } if (ngx_quic_parse_long_header(pkt) != NGX_OK) { @@ -277,7 +282,11 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) pkt->level = ssl_encryption_initial; - return ngx_quic_parse_initial_header(pkt); + if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { + return NGX_DECLINED; + } + + return NGX_OK; } if (ngx_quic_pkt_hs(pkt->flags)) { @@ -292,7 +301,11 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) return NGX_DECLINED; } - return ngx_quic_parse_handshake_header(pkt); + if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { + return NGX_DECLINED; + } + + return NGX_OK; } -- cgit v1.2.3 From b99a4a0b82e49bd73db430d21a9d3fa6cf18377e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 5 Oct 2020 10:03:01 +0300 Subject: QUIC: inline function instead of macro for hexdump. This prevents name clashes with local variables. --- src/event/ngx_event_quic.h | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 2dac905e7..ece86bda7 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -130,18 +130,20 @@ void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, #if (NGX_DEBUG) -#define ngx_quic_hexdump(log, label, data, len) \ -do { \ - ngx_int_t m; \ - u_char buf[2048]; \ - \ - if (log->log_level & NGX_LOG_DEBUG_EVENT) { \ - m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 1024)) - buf; \ - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, \ - label " len:%uz data:%*s%s", \ - len, m, buf, len < 2048 ? "" : "..."); \ - } \ -} while (0) +static ngx_inline +void ngx_quic_hexdump(ngx_log_t *log, const char *label, u_char *data, + size_t len) +{ + ngx_int_t m; + u_char buf[2048]; + + if (log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, data, (len > 1024) ? 1024 : len) - buf; + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, + "%s len:%uz data:%*s%s", + label, len, m, buf, len < 2048 ? "" : "..."); + } +} #else -- cgit v1.2.3 From 4ed768d3d12fd875af3801a0958aed514bb7b19d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 5 Oct 2020 14:36:17 +0300 Subject: QUIC: fixed build with clang and NGX_QUIC_DEBUG_CRYPTO enabled. The ngx_quic_hexdump() function is wrapped into macros to cast "data" argument to "* u_char". --- src/event/ngx_event_quic.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index ece86bda7..7ff12f6d5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -130,8 +130,11 @@ void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, #if (NGX_DEBUG) +#define ngx_quic_hexdump(log, fmt, data, len) \ + ngx_quic_hexdump_real(log, fmt, (u_char *) data, (size_t) len) + static ngx_inline -void ngx_quic_hexdump(ngx_log_t *log, const char *label, u_char *data, +void ngx_quic_hexdump_real(ngx_log_t *log, const char *label, u_char *data, size_t len) { ngx_int_t m; -- cgit v1.2.3 From 3309b1e8dfd88af0bcadc98b14ac65349d8aa61c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 5 Oct 2020 13:02:38 +0100 Subject: QUIC: zero out packet length in frames prior to send. It could be that a frame was previously sent and may have stale information. This was previously broken by merging frames on resend in b383120afca3. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ed7922909..0c32be355 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3840,6 +3840,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, p += len; f->pnum = ctx->pnum; f->last = now; + f->plen = 0; } out.len = p - out.data; -- cgit v1.2.3 From f09be89a52d717163a16d77a9cfaabe7f1456dfd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 5 Oct 2020 13:02:53 +0100 Subject: QUIC: do not resend empty queue when speeding up handshake. If client acknowledged an Initial packet with CRYPTO frame and then sent another Initial packet containing duplicate CRYPTO again, this could result in resending frames off the empty send queue. --- src/event/ngx_event_quic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0c32be355..f503c48ba 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2846,6 +2846,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, { uint64_t last; ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; ngx_quic_crypto_frame_t *f; ngx_quic_frames_stream_t *fs; @@ -2871,7 +2872,11 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, /* speeding up handshake completion */ if (pkt->level == ssl_encryption_initial) { - ngx_quic_resend_frames(c, ngx_quic_get_send_ctx(qc, pkt->level)); + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } } return NGX_OK; -- cgit v1.2.3 From 46a01acdc0ba98014bcd282628453f59954f9a3a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 6 Oct 2020 18:08:55 +0100 Subject: QUIC: fixed measuring ACK Delay against 0-RTT packets. --- src/event/ngx_event_quic.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f503c48ba..c6a249b42 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1841,12 +1841,16 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->validated = 1; } + if (pkt->level == ssl_encryption_early_data + || pkt->level == ssl_encryption_application) + { + ngx_gettimeofday(&pkt->received); + } + if (pkt->level != ssl_encryption_application) { return ngx_quic_payload_handler(c, pkt); } - ngx_gettimeofday(&pkt->received); - /* switch keys on Key Phase change */ if (pkt->key_update) { -- cgit v1.2.3 From 7250a7688da4ee9ad1a88fd1489bbb19ec2020ce Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 7 Oct 2020 10:14:02 +0300 Subject: QUIC: fixed memory leak in ngx_quic_send_frames(). The function did not free passed frames in case of error. --- src/event/ngx_event_quic.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c6a249b42..aff89b01d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3839,6 +3839,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, len = ngx_quic_create_frame(p, f); if (len == -1) { + ngx_quic_free_frames(c, frames); return NGX_ERROR; } @@ -3897,11 +3898,13 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.num_len, pkt.trunc); if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) { + ngx_quic_free_frames(c, frames); return NGX_ERROR; } len = c->send(c, res.data, res.len); if (len == NGX_ERROR || (size_t) len != res.len) { + ngx_quic_free_frames(c, frames); return NGX_ERROR; } -- cgit v1.2.3 From 7369bdc47c82329b03b63d6d764fe82678d093d9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 7 Oct 2020 13:38:17 +0300 Subject: QUIC: updated c->log->action strings to reflect proper state. --- src/event/ngx_event_quic.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index aff89b01d..7b253a563 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -643,8 +643,6 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); - c->log->action = "QUIC initialization"; - rc = ngx_quic_input(c, c->buffer, conf); if (rc != NGX_OK) { ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); @@ -669,8 +667,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; - c->log->action = "creating new quic connection"; - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); if (qc == NULL) { return NULL; @@ -1300,6 +1296,8 @@ ngx_quic_input_handler(ngx_event_t *rev) ngx_quic_connection_t *qc; static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + ngx_memzero(&b, sizeof(ngx_buf_t)); b.start = buf; b.end = buf + sizeof(buf); @@ -1309,7 +1307,7 @@ ngx_quic_input_handler(ngx_event_t *rev) c = rev->data; qc = c->quic; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + c->log->action = "handling quic input"; if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, @@ -1618,7 +1616,6 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) p = b->pos; while (p < b->last) { - c->log->action = "processing quic packet"; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); pkt.raw = b; @@ -1684,12 +1681,16 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + c->log->action = "parsing quic packet"; + rc = ngx_quic_parse_packet(pkt); if (rc == NGX_DECLINED || rc == NGX_ERROR) { return rc; } + c->log->action = "processing quic packet"; + qc = c->quic; if (qc) { @@ -1759,6 +1760,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, if (pkt->level == ssl_encryption_initial) { + c->log->action = "creating quic connection"; + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { /* 7.2. Negotiating Connection IDs */ ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -1800,6 +1803,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } } + c->log->action = "decrypting packet"; + keys = &qc->keys[pkt->level]; if (keys->client.key.len == 0) { @@ -1847,6 +1852,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_gettimeofday(&pkt->received); } + c->log->action = "handling payload"; + if (pkt->level != ssl_encryption_application) { return ngx_quic_payload_handler(c, pkt); } -- cgit v1.2.3 From 9f583efe3d77b719c142a3db9a9bfc2343e10a89 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 2 Oct 2020 12:56:34 +0300 Subject: QUIC: added connection id debug. --- src/event/ngx_event_quic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7b253a563..3aed7573c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -906,9 +906,7 @@ ngx_quic_new_dcid(ngx_connection_t *c, ngx_quic_connection_t *qc, return NGX_ERROR; } -#ifdef NGX_QUIC_DEBUG_PACKETS ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); -#endif qc->odcid.len = odcid->len; qc->odcid.data = ngx_pstrdup(c->pool, odcid); -- cgit v1.2.3 From d6003648872112947bdd11611ea6351aa9f5727d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 2 Oct 2020 12:40:49 +0300 Subject: QUIC: enabled more key-related debug by default. --- src/event/ngx_event_quic.c | 15 ++++++++++++--- src/event/ngx_event_quic_protection.c | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3aed7573c..99de10d16 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -353,9 +353,9 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); -#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); #endif @@ -377,9 +377,9 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); -#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif @@ -403,9 +403,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); -#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(c->log, "quic read", rsecret, secret_len); #endif @@ -1691,6 +1691,15 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc = c->quic; +#if (NGX_DEBUG) + ngx_quic_hexdump(c->log, "quic pkt dcid", pkt->dcid.data, pkt->dcid.len); + + if (pkt->level != ssl_encryption_application) { + ngx_quic_hexdump(c->log, "quic pkt scid", pkt->scid.data, + pkt->scid.len); + } +#endif + if (qc) { if (rc == NGX_ABORT) { diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 0bb9e8f87..450281aea 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -165,9 +165,9 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, .len = is_len }; -#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, "quic ngx_quic_set_initial_secret"); +#ifdef NGX_QUIC_DEBUG_CRYPTO ngx_quic_hexdump(pool->log, "quic salt", salt, sizeof(salt)); ngx_quic_hexdump(pool->log, "quic initial secret", is, is_len); #endif -- cgit v1.2.3 From 783df73ba0a8b33df7d2057814db8e42db230b8a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 7 Oct 2020 12:24:03 +0100 Subject: QUIC: set local_socklen in stream connections. Previously, this field was not set while creating a QUIC stream connection. As a result, calling ngx_connection_local_sockaddr() led to getsockname() bad descriptor error. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 99de10d16..71257d377 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4438,6 +4438,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) sn->c->listening = c->listening; sn->c->addr_text = c->addr_text; sn->c->local_sockaddr = c->local_sockaddr; + sn->c->local_socklen = c->local_socklen; sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); sn->c->recv = ngx_quic_stream_recv; -- cgit v1.2.3 From 5a07601a3c117cd67d3ede31727ebe4906bfbf4c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 2 Oct 2020 16:20:41 +0300 Subject: QUIC: added debug message with final packet processing status. --- src/event/ngx_event_quic.c | 28 ++++++++++++++++++++++++++++ src/event/ngx_event_quic_transport.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 71257d377..e1109ace1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -46,6 +46,11 @@ #define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ #define NGX_QUIC_MAX_SR_PACKET 1200 +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "application" \ + : (lvl == ssl_encryption_initial) ? "initial" \ + : (lvl == ssl_encryption_handshake) ? "handshake" : "early_data" + typedef struct { ngx_rbtree_t tree; @@ -1625,6 +1630,25 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) rc = ngx_quic_process_packet(c, conf, &pkt); +#if (NGX_DEBUG) + if (pkt.parsed) { + ngx_quic_connection_t *qc; + + qc = c->quic; + + ngx_log_debug8(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pkt done %s decr:%d pn:%L pe:%ui rc:%i" + " closing:%d err:%d %s", + ngx_quic_level_name(pkt.level), pkt.decrypted, + pkt.pn, pkt.error, rc, (qc && qc->closing) ? 1 : 0, + qc ? qc->error : 0, + (qc && qc->error_reason) ? qc->error_reason : ""); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pkt done parse failed rc:%i", rc); + } +#endif + if (rc == NGX_ERROR) { return NGX_ERROR; } @@ -1687,6 +1711,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return rc; } + pkt->parsed = 1; + c->log->action = "processing quic packet"; qc = c->quic; @@ -1838,6 +1864,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return rc; } + pkt->decrypted = 1; + if (c->ssl == NULL) { if (ngx_quic_init_connection(c) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index a62f4d586..54f903473 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -311,6 +311,8 @@ typedef struct { unsigned need_ack:1; unsigned key_phase:1; unsigned key_update:1; + unsigned parsed:1; + unsigned decrypted:1; } ngx_quic_header_t; -- cgit v1.2.3 From 017e3bd8a8fdd5a670f368fbe005d2b239a2d79f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 7 Oct 2020 15:29:23 +0300 Subject: QUIC: fixed format specifier in debug message. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e1109ace1..ccaeccd1e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1638,7 +1638,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) ngx_log_debug8(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic pkt done %s decr:%d pn:%L pe:%ui rc:%i" - " closing:%d err:%d %s", + " closing:%d err:%ui %s", ngx_quic_level_name(pkt.level), pkt.decrypted, pkt.pn, pkt.error, rc, (qc && qc->closing) ? 1 : 0, qc ? qc->error : 0, -- cgit v1.2.3 From c245c9ea2018f9f23afe9c8a6bd8f7b4a8a05830 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 7 Oct 2020 14:51:05 +0100 Subject: QUIC: fixed dead store assignment. Found by Clang Static Analyzer. --- src/event/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 9a1704f10..1bd470f26 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1482,7 +1482,7 @@ ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, case NGX_QUIC_TP_INITIAL_SCID: str.len = end - p; - p = ngx_quic_read_bytes(p, end, str.len, &str.data); + str.data = p; break; default: -- cgit v1.2.3 From bb64f2017a5e549cd5883ee3ebee23a6a82d5c2e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 9 Oct 2020 16:57:19 +0300 Subject: QUIC: reset error and error_reason prior to processing packet. --- src/event/ngx_event_quic.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ccaeccd1e..2e6d4b570 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1628,6 +1628,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) pkt.flags = p[0]; pkt.raw->pos++; + if (c->quic) { + c->quic->error = 0; + c->quic->error_reason = 0; + } + rc = ngx_quic_process_packet(c, conf, &pkt); #if (NGX_DEBUG) -- cgit v1.2.3 From 72b566cea5387644853def8230aa19e4c1e1990b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 12 Oct 2020 14:00:00 +0100 Subject: QUIC: fixed ngx_http_upstream_init() much like HTTP/2 connections. --- src/http/ngx_http_upstream.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 53c77105b..419d936b8 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -523,6 +523,13 @@ ngx_http_upstream_init(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_QUIC) + if (c->qs) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } -- cgit v1.2.3 From 26102d7ad77f4aefa12744027778e2f2c8412201 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 15 Oct 2020 11:37:01 +0300 Subject: QUIC: account packet header length in amplification limit. Header length calculation is adjusted to account real connection id lengths instead of worst case. --- src/event/ngx_event_quic.c | 3 ++- src/event/ngx_event_quic.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2e6d4b570..ed865c327 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3757,6 +3757,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; do { len = 0; @@ -3786,7 +3787,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) * send more than three times the data it receives; */ - if (((c->sent + len + f->len) / 3) > qc->received) { + if (((c->sent + hlen + len + f->len) / 3) > qc->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic hit amplification limit" " received %uz sent %O", diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 7ff12f6d5..cb9fbb35c 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,7 +54,8 @@ #define NGX_QUIC_STREAM_BUFSIZE 65536 -#define NGX_QUIC_SERVER_CID_LEN 20 +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN #define NGX_QUIC_SR_TOKEN_LEN 16 -- cgit v1.2.3 From e8277e42241a848b63d4af2a05ceec156642690c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 15 Sep 2020 22:44:46 +0300 Subject: SSL: added the "ssl_keys_file" directive. --- src/event/ngx_event_openssl.c | 46 ++++++++++++++++++++++++++++++++++ src/event/ngx_event_openssl.h | 3 +++ src/http/modules/ngx_http_ssl_module.c | 21 ++++++++++++++++ src/http/modules/ngx_http_ssl_module.h | 2 ++ 4 files changed, 72 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index ca94a68ff..f9a986128 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -255,6 +255,50 @@ ngx_ssl_init(ngx_log_t *log) } +void +ngx_ssl_keylogger(const ngx_ssl_conn_t *ssl_conn, const char *line) +{ + u_char *p; + size_t len; + ssize_t n; + ngx_connection_t *c; + ngx_ssl_connection_t *sc; + + if (line == NULL) { + return; + } + + len = ngx_strlen(line); + + if (len == 0) { + return; + } + + c = ngx_ssl_get_connection(ssl_conn); + sc = c->ssl; + + p = ngx_alloc(len + 1, c->log); + if (p == NULL) { + return; + } + + ngx_memcpy(p, line, len); + p[len] = '\n'; + + n = ngx_write_fd(sc->keylog->fd, p, len + 1); + if (n == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + ngx_write_fd_n " to \"%s\" failed", + sc->keylog->name.data); + + } else if ((size_t) n != len + 1) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", + sc->keylog->name.data, n, len + 1); + } +} + + ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) { @@ -1516,6 +1560,8 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) return NGX_ERROR; } + sc->keylog = ssl->keylog; + sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 8ed778748..1e9648978 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -78,6 +78,7 @@ struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + ngx_open_file_t *keylog; }; @@ -100,6 +101,7 @@ struct ngx_ssl_connection_s { ngx_ssl_ocsp_t *ocsp; u_char early_buf; + ngx_open_file_t *keylog; unsigned handshaked:1; unsigned renegotiation:1; @@ -296,6 +298,7 @@ ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c); void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...); void ngx_ssl_cleanup_ctx(void *data); +void ngx_ssl_keylogger(const ngx_ssl_conn_t *ssl, const char *line); extern int ngx_ssl_connection_index; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 409514821..99dbd8ec6 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -119,6 +119,13 @@ static ngx_command_t ngx_http_ssl_commands[] = { 0, NULL }, + { ngx_string("ssl_keys_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, keys_file), + NULL }, + { ngx_string("ssl_dhparam"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -605,6 +612,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * sscf->trusted_certificate = { 0, NULL }; * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; + * sscf->keys_file = { 0, NULL }; * sscf->shm_zone = NULL; * sscf->ocsp_responder = { 0, NULL }; * sscf->stapling_file = { 0, NULL }; @@ -676,6 +684,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); + ngx_conf_merge_str_value(conf->keys_file, prev->keys_file, ""); + ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, @@ -912,6 +922,17 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } + if (conf->keys_file.len) { + + conf->ssl.keylog = ngx_conf_open_file(cf->cycle, &conf->keys_file); + + if (conf->ssl.keylog == NULL) { + return NGX_CONF_ERROR; + } + + SSL_CTX_set_keylog_callback(conf->ssl.ctx, ngx_ssl_keylogger); + } + if (conf->stapling) { if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file, diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index 98aa1be40..aab185e05 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -36,6 +36,8 @@ typedef struct { ngx_array_t *certificates; ngx_array_t *certificate_keys; + ngx_str_t keys_file; + ngx_array_t *certificate_values; ngx_array_t *certificate_key_values; -- cgit v1.2.3 From 622a65edea2489d87c7e8284e5d6db1e7dfbe3bf Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 14 Oct 2020 23:21:36 +0300 Subject: QUIC: added ACK frame range support. The history of acknowledged packet is kept in send context as ranges. Up to NGX_QUIC_MAX_RANGES ranges is stored. As a result, instead of separate ack frames, single frame with ranges is sent. --- src/event/ngx_event_quic.c | 279 +++++++++++++++++++++++++++++++++-- src/event/ngx_event_quic_transport.c | 22 ++- src/event/ngx_event_quic_transport.h | 13 +- 3 files changed, 293 insertions(+), 21 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ed865c327..40084753c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -93,12 +93,21 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; + enum ssl_encryption_level_t level; + uint64_t pnum; /* to be sent */ uint64_t largest_ack; /* received from peer */ uint64_t largest_pn; /* received from peer */ ngx_queue_t frames; ngx_queue_t sent; + + uint64_t largest_range; + uint64_t first_range; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + struct timeval ack_received; + ngx_uint_t send_ack; /* unsigned send_ack:1 */ } ngx_quic_send_ctx_t; @@ -230,7 +239,12 @@ static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); @@ -685,8 +699,13 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_queue_init(&qc->send_ctx[i].sent); qc->send_ctx[i].largest_pn = (uint64_t) -1; qc->send_ctx[i].largest_ack = (uint64_t) -1; + qc->send_ctx[i].largest_range = (uint64_t) -1; } + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { ngx_queue_init(&qc->crypto[i].frames); } @@ -1974,6 +1993,8 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_congestion_ack(c, f); ngx_quic_free_frame(c, f); } + + ctx->send_ack = 0; } @@ -2109,7 +2130,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) /* got there with ack-eliciting packet */ if (!ack_sent) { - if (ngx_quic_send_ack(c, pkt) != NGX_OK) { + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { return NGX_ERROR; } @@ -2274,31 +2295,251 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_quic_frame_t *frame; + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + ngx_uint_t i, j, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + + c->log->action = "preparing ack"; + + ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_ack_packet pn %uL largest %uL nranges %ui", + pkt->pn, ctx->largest_range, ctx->nranges); + + if (!ctx->send_ack) { + ngx_post_event(&c->quic->push, &ngx_posted_events); + } + + ctx->send_ack = 1; + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == (uint64_t) -1) { + ctx->largest_range = pn; + ctx->ack_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + ctx->largest_range = pn; + ctx->ack_received = pkt->received; + + if (pn - base == 1) { + ctx->first_range++; + return NGX_OK; + + } else { + /* new gap in front of current largest */ + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + r->gap = pn - gs - 1; + gap = ge - pn - 1; + range = 0; + + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; - c->log->action = "generating acknowledgment"; + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + gap = smallest - 2 - pn; + range = 0; - /* every ACK-eliciting packet is acknowledged, TODO ACK Ranges */ +insert: + + nr = ctx->nranges - i; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* last range is dropped and reused for newer data */ + + for (j = i; j < ctx->nranges; j++) { + largest = smallest - ctx->ranges[j].gap - 2; + smallest = largest - ctx->ranges[j].range; + } + + if (ngx_quic_send_ack_range(c, ctx, smallest, largest) != NGX_OK) { + return NGX_ERROR; + } + + nr--; + + } else { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * nr); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } - frame->level = (pkt->level == ssl_encryption_early_data) - ? ssl_encryption_application - : pkt->level; + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_sprintf(frame->info, "ACK for PN=%uL..%uL 0 ranges level=%d", + largest, smallest, frame->level); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t ranges_len; + ngx_quic_frame_t *frame; + + ranges_len = sizeof(ngx_quic_ack_range_t) * ctx->nranges; + frame = ngx_quic_alloc_frame(c, ranges_len); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(frame->data, ctx->ranges, ranges_len); + + frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = pkt->pn; - frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level); + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ngx_quic_ack_delay(c, &ctx->ack_received, ctx->level); + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + frame->u.ack.ranges_start = frame->data; + frame->u.ack.ranges_end = frame->data + ranges_len; + + ngx_sprintf(frame->info, "ACK for PN=%uL %ui ranges level=%d", + ctx->largest_range, ctx->nranges, frame->level); - ngx_sprintf(frame->info, "ACK for PN=%uL from frame handler level=%d", - pkt->pn, frame->level); ngx_quic_queue_frame(c->quic, frame); + ctx->send_ack = 0; + return NGX_OK; } @@ -3712,6 +3953,7 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { ngx_uint_t i; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; c->log->action = "sending frames"; @@ -3719,7 +3961,16 @@ ngx_quic_output(ngx_connection_t *c) qc = c->quic; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ngx_quic_output_frames(c, &qc->send_ctx[i]) != NGX_OK) { + + ctx = &qc->send_ctx[i]; + + if (ctx->send_ack) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_output_frames(c, ctx) != NGX_OK) { return NGX_ERROR; } } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1bd470f26..f81be23c9 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1259,18 +1259,25 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_uint_t i; + ngx_quic_ack_range_t *ranges; - /* minimal ACK packet */ + ranges = (ngx_quic_ack_range_t *) ack->ranges_start; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); len += ngx_quic_varint_len(ack->largest); len += ngx_quic_varint_len(ack->delay); - len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->range_count); len += ngx_quic_varint_len(ack->first_range); + for (i = 0; i < ack->range_count; i++) { + len += ngx_quic_varint_len(ranges[i].gap); + len += ngx_quic_varint_len(ranges[i].range); + } + return len; } @@ -1279,9 +1286,14 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ngx_quic_build_int(&p, ack->largest); ngx_quic_build_int(&p, ack->delay); - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->range_count); ngx_quic_build_int(&p, ack->first_range); + for (i = 0; i < ack->range_count; i++) { + ngx_quic_build_int(&p, ranges[i].gap); + ngx_quic_build_int(&p, ranges[i].range); + } + return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 54f903473..12aad5aae 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -120,6 +120,15 @@ #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + typedef struct { uint64_t largest; uint64_t delay; @@ -128,8 +137,8 @@ typedef struct { uint64_t ect0; uint64_t ect1; uint64_t ce; - u_char *ranges_start; - u_char *ranges_end; + u_char *ranges_start; + u_char *ranges_end; } ngx_quic_ack_frame_t; -- cgit v1.2.3 From 2d65615b42d5e759c8bf701a76ccdad0eb387469 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 19 Oct 2020 10:10:21 +0300 Subject: try: --skiptests --- src/event/ngx_event_quic.c | 70 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 40084753c..685441188 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -243,6 +243,8 @@ static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, @@ -2509,6 +2511,53 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + base = ctx->largest_range; + + if (base == (uint64_t) -1) { + return; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = (uint64_t) - 1; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + largest = smallest - r->gap - 2; + smallest = largest - r->range; + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { @@ -2779,7 +2828,22 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (f->pnum >= min && f->pnum <= max) { ngx_quic_congestion_ack(c, f); - ngx_quic_handle_stream_ack(c, f); + switch (f->type) { + case NGX_QUIC_FT_ACK: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + ngx_quic_handle_stream_ack(c, f); + break; + } if (f->pnum > found_num || !found) { *send_time = f->last; @@ -2901,10 +2965,6 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - if (f->type < NGX_QUIC_FT_STREAM0 || f->type > NGX_QUIC_FT_STREAM7) { - return; - } - qc = c->quic; sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); -- cgit v1.2.3 From 743cc997811336b01109f83c659a67752015ffad Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 19 Oct 2020 10:32:53 +0300 Subject: QUIC: reverted previous 3 commits. Changes were intended for the test repository. --- src/event/ngx_event_openssl.c | 46 ----- src/event/ngx_event_openssl.h | 3 - src/event/ngx_event_quic.c | 352 ++------------------------------- src/event/ngx_event_quic.h | 3 +- src/event/ngx_event_quic_transport.c | 22 +-- src/event/ngx_event_quic_transport.h | 13 +- src/http/modules/ngx_http_ssl_module.c | 21 -- src/http/modules/ngx_http_ssl_module.h | 2 - 8 files changed, 28 insertions(+), 434 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index f9a986128..ca94a68ff 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -255,50 +255,6 @@ ngx_ssl_init(ngx_log_t *log) } -void -ngx_ssl_keylogger(const ngx_ssl_conn_t *ssl_conn, const char *line) -{ - u_char *p; - size_t len; - ssize_t n; - ngx_connection_t *c; - ngx_ssl_connection_t *sc; - - if (line == NULL) { - return; - } - - len = ngx_strlen(line); - - if (len == 0) { - return; - } - - c = ngx_ssl_get_connection(ssl_conn); - sc = c->ssl; - - p = ngx_alloc(len + 1, c->log); - if (p == NULL) { - return; - } - - ngx_memcpy(p, line, len); - p[len] = '\n'; - - n = ngx_write_fd(sc->keylog->fd, p, len + 1); - if (n == -1) { - ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, - ngx_write_fd_n " to \"%s\" failed", - sc->keylog->name.data); - - } else if ((size_t) n != len + 1) { - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - ngx_write_fd_n " to \"%s\" was incomplete: %z of %uz", - sc->keylog->name.data, n, len + 1); - } -} - - ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) { @@ -1560,8 +1516,6 @@ ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) return NGX_ERROR; } - sc->keylog = ssl->keylog; - sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); sc->buffer_size = ssl->buffer_size; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 1e9648978..8ed778748 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -78,7 +78,6 @@ struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; - ngx_open_file_t *keylog; }; @@ -101,7 +100,6 @@ struct ngx_ssl_connection_s { ngx_ssl_ocsp_t *ocsp; u_char early_buf; - ngx_open_file_t *keylog; unsigned handshaked:1; unsigned renegotiation:1; @@ -298,7 +296,6 @@ ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c); void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...); void ngx_ssl_cleanup_ctx(void *data); -void ngx_ssl_keylogger(const ngx_ssl_conn_t *ssl, const char *line); extern int ngx_ssl_connection_index; diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 685441188..2e6d4b570 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -93,21 +93,12 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; - enum ssl_encryption_level_t level; - uint64_t pnum; /* to be sent */ uint64_t largest_ack; /* received from peer */ uint64_t largest_pn; /* received from peer */ ngx_queue_t frames; ngx_queue_t sent; - - uint64_t largest_range; - uint64_t first_range; - ngx_uint_t nranges; - ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; - struct timeval ack_received; - ngx_uint_t send_ack; /* unsigned send_ack:1 */ } ngx_quic_send_ctx_t; @@ -239,14 +230,7 @@ static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); -static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t pn); -static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); +static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); @@ -701,13 +685,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_queue_init(&qc->send_ctx[i].sent); qc->send_ctx[i].largest_pn = (uint64_t) -1; qc->send_ctx[i].largest_ack = (uint64_t) -1; - qc->send_ctx[i].largest_range = (uint64_t) -1; } - qc->send_ctx[0].level = ssl_encryption_initial; - qc->send_ctx[1].level = ssl_encryption_handshake; - qc->send_ctx[2].level = ssl_encryption_application; - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { ngx_queue_init(&qc->crypto[i].frames); } @@ -1995,8 +1974,6 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_congestion_ack(c, f); ngx_quic_free_frame(c, f); } - - ctx->send_ack = 0; } @@ -2132,7 +2109,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) /* got there with ack-eliciting packet */ if (!ack_sent) { - if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + if (ngx_quic_send_ack(c, pkt) != NGX_OK) { return NGX_ERROR; } @@ -2297,298 +2274,31 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - uint64_t base, largest, smallest, gs, ge, gap, range, pn; - ngx_uint_t i, j, nr; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_range_t *r; - - c->log->action = "preparing ack"; - - ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_ack_packet pn %uL largest %uL nranges %ui", - pkt->pn, ctx->largest_range, ctx->nranges); - - if (!ctx->send_ack) { - ngx_post_event(&c->quic->push, &ngx_posted_events); - } - - ctx->send_ack = 1; - - base = ctx->largest_range; - pn = pkt->pn; - - if (base == (uint64_t) -1) { - ctx->largest_range = pn; - ctx->ack_received = pkt->received; - return NGX_OK; - } - - if (base == pn) { - return NGX_OK; - } - - largest = base; - smallest = largest - ctx->first_range; - - if (pn > base) { - ctx->largest_range = pn; - ctx->ack_received = pkt->received; - - if (pn - base == 1) { - ctx->first_range++; - return NGX_OK; - - } else { - /* new gap in front of current largest */ - gap = pn - base - 2; - range = ctx->first_range; - - ctx->first_range = 0; - i = 0; - - goto insert; - } - } - - /* pn < base, perform lookup in existing ranges */ - - if (pn >= smallest && pn <= largest) { - return NGX_OK; - } - -#if (NGX_SUPPRESS_WARN) - r = NULL; -#endif - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - - ge = smallest - 1; - gs = ge - r->gap; - - if (pn >= gs && pn <= ge) { - - if (gs == ge) { - /* gap size is exactly one packet, now filled */ - - /* data moves to previous range, current is removed */ - - if (i == 0) { - ctx->first_range += r->range + 2; - - } else { - ctx->ranges[i - 1].range += r->range + 2; - } - - nr = ctx->nranges - i - 1; - if (nr) { - ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], - sizeof(ngx_quic_ack_range_t) * nr); - } - - ctx->nranges--; - - } else if (pn == gs) { - /* current gap shrinks from tail (current range grows) */ - r->gap--; - r->range++; - - } else if (pn == ge) { - /* current gap shrinks from head (previous range grows) */ - r->gap--; - - if (i == 0) { - ctx->first_range++; - - } else { - ctx->ranges[i - 1].range++; - } - - } else { - /* current gap is split into two parts */ - - r->gap = pn - gs - 1; - gap = ge - pn - 1; - range = 0; - - goto insert; - } - - return NGX_OK; - } - - largest = smallest - r->gap - 2; - smallest = largest - r->range; - - if (pn >= smallest && pn <= largest) { - /* this packet number is already known */ - return NGX_OK; - } - - } - - if (pn == smallest - 1) { - /* extend first or last range */ - - if (i == 0) { - ctx->first_range++; - - } else { - r->range++; - } - - return NGX_OK; - } - - /* nothing found, add new range at the tail */ - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - /* packet is too old to keep it */ - return ngx_quic_send_ack_range(c, ctx, pn, pn); - } - - gap = smallest - 2 - pn; - range = 0; - -insert: - - nr = ctx->nranges - i; - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - /* last range is dropped and reused for newer data */ - - for (j = i; j < ctx->nranges; j++) { - largest = smallest - ctx->ranges[j].gap - 2; - smallest = largest - ctx->ranges[j].range; - } - - if (ngx_quic_send_ack_range(c, ctx, smallest, largest) != NGX_OK) { - return NGX_ERROR; - } - - nr--; - - } else { - ctx->nranges++; - } - - ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], - sizeof(ngx_quic_ack_range_t) * nr); - - ctx->ranges[i].gap = gap; - ctx->ranges[i].range = range; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t smallest, uint64_t largest) +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_quic_frame_t *frame; - frame = ngx_quic_alloc_frame(c, 0); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ctx->level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = largest; - frame->u.ack.delay = 0; - frame->u.ack.range_count = 0; - frame->u.ack.first_range = largest - smallest; + c->log->action = "generating acknowledgment"; - ngx_sprintf(frame->info, "ACK for PN=%uL..%uL 0 ranges level=%d", - largest, smallest, frame->level); - - return NGX_OK; -} - - -static void -ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t pn) -{ - uint64_t base; - ngx_uint_t i, smallest, largest; - ngx_quic_ack_range_t *r; - - base = ctx->largest_range; - - if (base == (uint64_t) -1) { - return; - } + /* every ACK-eliciting packet is acknowledged, TODO ACK Ranges */ - largest = base; - smallest = largest - ctx->first_range; - - if (pn >= largest) { - ctx->largest_range = (uint64_t) - 1; - ctx->first_range = 0; - ctx->nranges = 0; - return; - } - - if (pn >= smallest) { - ctx->first_range = largest - pn - 1; - ctx->nranges = 0; - return; - } - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - largest = smallest - r->gap - 2; - smallest = largest - r->range; - if (pn >= largest) { - ctx->nranges = i; - return; - } - if (pn >= smallest) { - r->range = largest - pn - 1; - ctx->nranges = i + 1; - return; - } - } -} - - -static ngx_int_t -ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t ranges_len; - ngx_quic_frame_t *frame; - - ranges_len = sizeof(ngx_quic_ack_range_t) * ctx->nranges; - - frame = ngx_quic_alloc_frame(c, ranges_len); + frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } - ngx_memcpy(frame->data, ctx->ranges, ranges_len); + frame->level = (pkt->level == ssl_encryption_early_data) + ? ssl_encryption_application + : pkt->level; - frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = ctx->largest_range; - frame->u.ack.delay = ngx_quic_ack_delay(c, &ctx->ack_received, ctx->level); - frame->u.ack.range_count = ctx->nranges; - frame->u.ack.first_range = ctx->first_range; - frame->u.ack.ranges_start = frame->data; - frame->u.ack.ranges_end = frame->data + ranges_len; - - ngx_sprintf(frame->info, "ACK for PN=%uL %ui ranges level=%d", - ctx->largest_range, ctx->nranges, frame->level); + frame->u.ack.largest = pkt->pn; + frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level); + ngx_sprintf(frame->info, "ACK for PN=%uL from frame handler level=%d", + pkt->pn, frame->level); ngx_quic_queue_frame(c->quic, frame); - ctx->send_ack = 0; - return NGX_OK; } @@ -2828,22 +2538,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (f->pnum >= min && f->pnum <= max) { ngx_quic_congestion_ack(c, f); - switch (f->type) { - case NGX_QUIC_FT_ACK: - ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - ngx_quic_handle_stream_ack(c, f); - break; - } + ngx_quic_handle_stream_ack(c, f); if (f->pnum > found_num || !found) { *send_time = f->last; @@ -2965,6 +2660,10 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; + if (f->type < NGX_QUIC_FT_STREAM0 || f->type > NGX_QUIC_FT_STREAM7) { + return; + } + qc = c->quic; sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); @@ -4013,7 +3712,6 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { ngx_uint_t i; - ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; c->log->action = "sending frames"; @@ -4021,16 +3719,7 @@ ngx_quic_output(ngx_connection_t *c) qc = c->quic; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ctx->send_ack) { - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - } - - if (ngx_quic_output_frames(c, ctx) != NGX_OK) { + if (ngx_quic_output_frames(c, &qc->send_ctx[i]) != NGX_OK) { return NGX_ERROR; } } @@ -4068,7 +3757,6 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; do { len = 0; @@ -4098,7 +3786,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) * send more than three times the data it receives; */ - if (((c->sent + hlen + len + f->len) / 3) > qc->received) { + if (((c->sent + len + f->len) / 3) > qc->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic hit amplification limit" " received %uz sent %O", diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index cb9fbb35c..7ff12f6d5 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,8 +54,7 @@ #define NGX_QUIC_STREAM_BUFSIZE 65536 -#define NGX_QUIC_MAX_CID_LEN 20 -#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN +#define NGX_QUIC_SERVER_CID_LEN 20 #define NGX_QUIC_SR_TOKEN_LEN 16 diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index f81be23c9..1bd470f26 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1259,25 +1259,18 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { - size_t len; - u_char *start; - ngx_uint_t i; - ngx_quic_ack_range_t *ranges; + size_t len; + u_char *start; - ranges = (ngx_quic_ack_range_t *) ack->ranges_start; + /* minimal ACK packet */ if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); len += ngx_quic_varint_len(ack->largest); len += ngx_quic_varint_len(ack->delay); - len += ngx_quic_varint_len(ack->range_count); + len += ngx_quic_varint_len(0); len += ngx_quic_varint_len(ack->first_range); - for (i = 0; i < ack->range_count; i++) { - len += ngx_quic_varint_len(ranges[i].gap); - len += ngx_quic_varint_len(ranges[i].range); - } - return len; } @@ -1286,14 +1279,9 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ngx_quic_build_int(&p, ack->largest); ngx_quic_build_int(&p, ack->delay); - ngx_quic_build_int(&p, ack->range_count); + ngx_quic_build_int(&p, 0); ngx_quic_build_int(&p, ack->first_range); - for (i = 0; i < ack->range_count; i++) { - ngx_quic_build_int(&p, ranges[i].gap); - ngx_quic_build_int(&p, ranges[i].range); - } - return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 12aad5aae..54f903473 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -120,15 +120,6 @@ #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 -#define NGX_QUIC_MAX_RANGES 10 - - -typedef struct { - uint64_t gap; - uint64_t range; -} ngx_quic_ack_range_t; - - typedef struct { uint64_t largest; uint64_t delay; @@ -137,8 +128,8 @@ typedef struct { uint64_t ect0; uint64_t ect1; uint64_t ce; - u_char *ranges_start; - u_char *ranges_end; + u_char *ranges_start; + u_char *ranges_end; } ngx_quic_ack_frame_t; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 99dbd8ec6..409514821 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -119,13 +119,6 @@ static ngx_command_t ngx_http_ssl_commands[] = { 0, NULL }, - { ngx_string("ssl_keys_file"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_str_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_ssl_srv_conf_t, keys_file), - NULL }, - { ngx_string("ssl_dhparam"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, @@ -612,7 +605,6 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * sscf->trusted_certificate = { 0, NULL }; * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; - * sscf->keys_file = { 0, NULL }; * sscf->shm_zone = NULL; * sscf->ocsp_responder = { 0, NULL }; * sscf->stapling_file = { 0, NULL }; @@ -684,8 +676,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL); - ngx_conf_merge_str_value(conf->keys_file, prev->keys_file, ""); - ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, @@ -922,17 +912,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; } - if (conf->keys_file.len) { - - conf->ssl.keylog = ngx_conf_open_file(cf->cycle, &conf->keys_file); - - if (conf->ssl.keylog == NULL) { - return NGX_CONF_ERROR; - } - - SSL_CTX_set_keylog_callback(conf->ssl.ctx, ngx_ssl_keylogger); - } - if (conf->stapling) { if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file, diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index aab185e05..98aa1be40 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -36,8 +36,6 @@ typedef struct { ngx_array_t *certificates; ngx_array_t *certificate_keys; - ngx_str_t keys_file; - ngx_array_t *certificate_values; ngx_array_t *certificate_key_values; -- cgit v1.2.3 From d54fd4ed3402e9eb546e139826491af0536a57bd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 19 Oct 2020 12:19:38 +0300 Subject: QUIC: account packet header length in amplification limit. This is the restoration of 02ee77f8d53d accidentally reverted by 93be5658a250. --- src/event/ngx_event_quic.c | 3 ++- src/event/ngx_event_quic.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2e6d4b570..ed865c327 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3757,6 +3757,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; do { len = 0; @@ -3786,7 +3787,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) * send more than three times the data it receives; */ - if (((c->sent + len + f->len) / 3) > qc->received) { + if (((c->sent + hlen + len + f->len) / 3) > qc->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic hit amplification limit" " received %uz sent %O", diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 7ff12f6d5..cb9fbb35c 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -54,7 +54,8 @@ #define NGX_QUIC_STREAM_BUFSIZE 65536 -#define NGX_QUIC_SERVER_CID_LEN 20 +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN #define NGX_QUIC_SR_TOKEN_LEN 16 -- cgit v1.2.3 From c405a364eb938910336a1636368e46604699a7a2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 21 Oct 2020 12:03:21 +0100 Subject: QUIC: sorted ngx_quic_send_frames() declarations. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ed865c327..f51d7c8eb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3847,10 +3847,10 @@ static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames) { - ssize_t len; u_char *p; - ngx_msec_t now; + ssize_t len; ngx_str_t out, res; + ngx_msec_t now; ngx_queue_t *q; ngx_ssl_conn_t *ssl_conn; ngx_quic_frame_t *f, *start; -- cgit v1.2.3 From a47a4400b8201a4c5813fb91f211533c967b6aa4 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 21 Oct 2020 12:03:22 +0100 Subject: QUIC: avoided excessive initialization in ngx_quic_send_frames(). A zero-length token was used to initialize a prezeroed packet header. --- src/event/ngx_event_quic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f51d7c8eb..628090ca8 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3857,7 +3857,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_header_t pkt; ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; - static ngx_str_t initial_token = ngx_null_string; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; @@ -3918,7 +3917,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (start->level == ssl_encryption_initial) { pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - pkt.token = initial_token; } else if (start->level == ssl_encryption_handshake) { pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; -- cgit v1.2.3 From fe2c392551ca4b88b8f1ec35f97994928e2303c9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 21 Oct 2020 12:03:22 +0100 Subject: QUIC: simplified ngx_quic_create_long_header(). As seen in the quic-transport draft, which this implementation follows: Initial packets sent by the server MUST set the Token Length field to zero. --- src/event/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1bd470f26..cea69ab7a 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -433,7 +433,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); if (pkt->level == ssl_encryption_initial) { - ngx_quic_build_int(&p, pkt->token.len); + ngx_quic_build_int(&p, 0); } ngx_quic_build_int(&p, pkt_len + pkt->num_len); -- cgit v1.2.3 From e1982a1abafda2e1e541dc6d6516d4087c0e5bd8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 21 Oct 2020 12:03:23 +0100 Subject: QUIC: teach how to compute only the length of created QUIC headers. It will be used for precise expansion of UDP datagram payload. --- src/event/ngx_event_quic_transport.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index cea69ab7a..93b3bae78 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -420,6 +420,12 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, { u_char *p, *start; + if (out == NULL) { + return 5 + 2 + pkt->dcid.len + pkt->scid.len + + ngx_quic_varint_len(pkt_len + pkt->num_len) + pkt->num_len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + } + p = start = out; *p++ = pkt->flags; @@ -465,6 +471,10 @@ ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, { u_char *p, *start; + if (out == NULL) { + return 1 + pkt->dcid.len + pkt->num_len; + } + p = start = out; *p++ = pkt->flags; -- cgit v1.2.3 From 74f6c92529deee16d34d76395f1deab70d6beb1f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 21 Oct 2020 12:46:23 +0100 Subject: QUIC: expand UDP datagrams with an ack-eliciting Initial packet. Per draft-ietf-quic-transport-32 on the topic: : Similarly, a server MUST expand the payload of all UDP datagrams carrying : ack-eliciting Initial packets to at least the smallest allowed maximum : datagram size of 1200 bytes. --- src/event/ngx_event_quic.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 628090ca8..f920e59a7 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3848,6 +3848,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames) { u_char *p; + size_t pad_len; ssize_t len; ngx_str_t out, res; ngx_msec_t now; @@ -3902,11 +3903,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, out.len = p - out.data; - while (out.len < 4) { - *p++ = NGX_QUIC_FT_PADDING; - out.len++; - } - qc = c->quic; keys = &c->quic->keys[start->level]; @@ -3933,6 +3929,21 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.level = start->level; pkt.dcid = qc->scid; pkt.scid = qc->dcid; + + if (start->level == ssl_encryption_initial && pkt.need_ack) { + pad_len = NGX_QUIC_MIN_INITIAL_SIZE - EVP_GCM_TLS_TAG_LEN + - ngx_quic_create_long_header(&pkt, NULL, out.len, NULL); + pad_len = ngx_min(pad_len, NGX_QUIC_MIN_INITIAL_SIZE); + + } else { + pad_len = 4; + } + + if (out.len < pad_len) { + ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); + out.len = pad_len; + } + pkt.payload = out; res.data = dst; -- cgit v1.2.3 From 6e412bf182bf47a9c8053d859ada515552c3aba1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 20 Oct 2020 18:53:00 +0300 Subject: QUIC: added ACK frame range support. The history of acknowledged packet is kept in send context as ranges. Up to NGX_QUIC_MAX_RANGES ranges is stored. As a result, instead of separate ack frames, single frame with ranges is sent. --- src/event/ngx_event_quic.c | 324 ++++++++++++++++++++++++++++++++--- src/event/ngx_event_quic_transport.c | 22 ++- src/event/ngx_event_quic_transport.h | 13 +- 3 files changed, 329 insertions(+), 30 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f920e59a7..e30d5f64f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -93,12 +93,22 @@ typedef struct { ngx_quic_secret_t client_secret; ngx_quic_secret_t server_secret; + enum ssl_encryption_level_t level; + uint64_t pnum; /* to be sent */ uint64_t largest_ack; /* received from peer */ uint64_t largest_pn; /* received from peer */ ngx_queue_t frames; ngx_queue_t sent; + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + struct timeval ack_received; + ngx_uint_t send_ack; /* unsigned send_ack:1 */ } ngx_quic_send_ctx_t; @@ -230,7 +240,12 @@ static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); @@ -685,8 +700,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_queue_init(&qc->send_ctx[i].sent); qc->send_ctx[i].largest_pn = (uint64_t) -1; qc->send_ctx[i].largest_ack = (uint64_t) -1; + qc->send_ctx[i].largest_range = (uint64_t) -1; + qc->send_ctx[i].pending_ack = (uint64_t) -1; } + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { ngx_queue_init(&qc->crypto[i].frames); } @@ -1974,6 +1995,8 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_congestion_ack(c, f); ngx_quic_free_frame(c, f); } + + ctx->send_ack = 0; } @@ -2041,7 +2064,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *end, *p; ssize_t len; - ngx_uint_t ack_sent, do_close; + ngx_uint_t do_close; ngx_quic_frame_t frame; ngx_quic_connection_t *qc; @@ -2069,7 +2092,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) p = pkt->payload.data; end = p + pkt->payload.len; - ack_sent = 0; do_close = 0; while (p < end) { @@ -2107,14 +2129,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) } /* got there with ack-eliciting packet */ - - if (!ack_sent) { - if (ngx_quic_send_ack(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - - ack_sent = 1; - } + pkt->need_ack = 1; switch (frame.type) { @@ -2269,34 +2284,286 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_close_connection(c, NGX_OK); } + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + return NGX_OK; } static ngx_int_t -ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_quic_frame_t *frame; + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + + c->log->action = "preparing ack"; - c->log->action = "generating acknowledgment"; + ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_ack_packet pn %uL largest %uL nranges %ui", + pkt->pn, ctx->largest_range, ctx->nranges); - /* every ACK-eliciting packet is acknowledged, TODO ACK Ranges */ + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&c->quic->push, &ngx_posted_events); + + ctx->send_ack = 1; + + if (ctx->pending_ack == (uint64_t) -1 + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == (uint64_t) -1) { + ctx->largest_range = pn; + ctx->ack_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->ack_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != (uint64_t) -1) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = (uint64_t) -1; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->ack_received = pkt->received; + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != (uint64_t) -1) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = (uint64_t) -1; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { return NGX_ERROR; } - frame->level = (pkt->level == ssl_encryption_early_data) - ? ssl_encryption_application - : pkt->level; + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_sprintf(frame->info, "ACK for PN=%uL..%uL 0 ranges level=%d", + largest, smallest, frame->level); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t ranges_len; + ngx_quic_frame_t *frame; + + ranges_len = sizeof(ngx_quic_ack_range_t) * ctx->nranges; + + frame = ngx_quic_alloc_frame(c, ranges_len); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(frame->data, ctx->ranges, ranges_len); + frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = pkt->pn; - frame->u.ack.delay = ngx_quic_ack_delay(c, &pkt->received, frame->level); + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ngx_quic_ack_delay(c, &ctx->ack_received, ctx->level); + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + frame->u.ack.ranges_start = frame->data; + frame->u.ack.ranges_end = frame->data + ranges_len; + + ngx_sprintf(frame->info, "ACK for PN=%uL %ui ranges level=%d", + ctx->largest_range, ctx->nranges, frame->level); - ngx_sprintf(frame->info, "ACK for PN=%uL from frame handler level=%d", - pkt->pn, frame->level); ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -3712,6 +3979,7 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { ngx_uint_t i; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; c->log->action = "sending frames"; @@ -3719,7 +3987,17 @@ ngx_quic_output(ngx_connection_t *c) qc = c->quic; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ngx_quic_output_frames(c, &qc->send_ctx[i]) != NGX_OK) { + + ctx = &qc->send_ctx[i]; + + if (ctx->send_ack) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + ctx->send_ack = 0; + } + + if (ngx_quic_output_frames(c, ctx) != NGX_OK) { return NGX_ERROR; } } diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 93b3bae78..aac260a0a 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1269,18 +1269,25 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_uint_t i; + ngx_quic_ack_range_t *ranges; - /* minimal ACK packet */ + ranges = (ngx_quic_ack_range_t *) ack->ranges_start; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); len += ngx_quic_varint_len(ack->largest); len += ngx_quic_varint_len(ack->delay); - len += ngx_quic_varint_len(0); + len += ngx_quic_varint_len(ack->range_count); len += ngx_quic_varint_len(ack->first_range); + for (i = 0; i < ack->range_count; i++) { + len += ngx_quic_varint_len(ranges[i].gap); + len += ngx_quic_varint_len(ranges[i].range); + } + return len; } @@ -1289,9 +1296,14 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); ngx_quic_build_int(&p, ack->largest); ngx_quic_build_int(&p, ack->delay); - ngx_quic_build_int(&p, 0); + ngx_quic_build_int(&p, ack->range_count); ngx_quic_build_int(&p, ack->first_range); + for (i = 0; i < ack->range_count; i++) { + ngx_quic_build_int(&p, ranges[i].gap); + ngx_quic_build_int(&p, ranges[i].range); + } + return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 54f903473..12aad5aae 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -120,6 +120,15 @@ #define NGX_QUIC_CID_LEN_MIN 8 #define NGX_QUIC_CID_LEN_MAX 20 +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + typedef struct { uint64_t largest; uint64_t delay; @@ -128,8 +137,8 @@ typedef struct { uint64_t ect0; uint64_t ect1; uint64_t ce; - u_char *ranges_start; - u_char *ranges_end; + u_char *ranges_start; + u_char *ranges_end; } ngx_quic_ack_frame_t; -- cgit v1.2.3 From cb3a1d7f490b73546055034a01dcb305e7a09b9f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 20 Oct 2020 18:53:25 +0300 Subject: QUIC: drop acknowledged ranges. 13.2.4. Limiting Ranges by Tracking ACK Frames When a packet containing an ACK frame is sent, the largest acknowledged in that frame may be saved. When a packet containing an ACK frame is acknowledged, the receiver can stop acknowledging packets less than or equal to the largest acknowledged in the sent ACK frame. --- src/event/ngx_event_quic.c | 76 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e30d5f64f..c941d21eb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -244,6 +244,8 @@ static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, @@ -2537,6 +2539,59 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + base = ctx->largest_range; + + if (base == (uint64_t) -1) { + return; + } + + if (ctx->pending_ack != (uint64_t) -1 && pn >= ctx->pending_ack) { + ctx->pending_ack = (uint64_t) -1; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = (uint64_t) - 1; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { @@ -2805,7 +2860,22 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (f->pnum >= min && f->pnum <= max) { ngx_quic_congestion_ack(c, f); - ngx_quic_handle_stream_ack(c, f); + switch (f->type) { + case NGX_QUIC_FT_ACK: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + ngx_quic_handle_stream_ack(c, f); + break; + } if (f->pnum > found_num || !found) { *send_time = f->last; @@ -2927,10 +2997,6 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - if (f->type < NGX_QUIC_FT_STREAM0 || f->type > NGX_QUIC_FT_STREAM7) { - return; - } - qc = c->quic; sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); -- cgit v1.2.3 From 1506c7b825939cc603a826a4a31b3db2af658338 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 21 Oct 2020 18:44:25 +0300 Subject: QUIC: added macro for unset packet number. --- src/event/ngx_event_quic.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c941d21eb..7face15cf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -36,6 +36,8 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + /* * Endpoints MUST discard packets that are too small to be valid QUIC * packets. With the set of AEAD functions defined in [QUIC-TLS], @@ -700,10 +702,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ngx_queue_init(&qc->send_ctx[i].frames); ngx_queue_init(&qc->send_ctx[i].sent); - qc->send_ctx[i].largest_pn = (uint64_t) -1; - qc->send_ctx[i].largest_ack = (uint64_t) -1; - qc->send_ctx[i].largest_range = (uint64_t) -1; - qc->send_ctx[i].pending_ack = (uint64_t) -1; + qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; } qc->send_ctx[0].level = ssl_encryption_initial; @@ -2029,7 +2031,7 @@ ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); if (pkt->level == ssl_encryption_initial - && ctx->largest_ack == (uint64_t) -1) + && ctx->largest_ack == NGX_QUIC_UNSET_PN) { if (pkt->dcid.len == qc->odcid.len && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0) @@ -2319,7 +2321,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx->send_ack = 1; - if (ctx->pending_ack == (uint64_t) -1 + if (ctx->pending_ack == NGX_QUIC_UNSET_PN || ctx->pending_ack < pkt->pn) { ctx->pending_ack = pkt->pn; @@ -2329,7 +2331,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) base = ctx->largest_range; pn = pkt->pn; - if (base == (uint64_t) -1) { + if (base == NGX_QUIC_UNSET_PN) { ctx->largest_range = pn; ctx->ack_received = pkt->received; return NGX_OK; @@ -2357,14 +2359,14 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) /* no place for new range, send current range as is */ if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - if (prev_pending != (uint64_t) -1) { + if (prev_pending != NGX_QUIC_UNSET_PN) { if (ngx_quic_send_ack(c, ctx) != NGX_OK) { return NGX_ERROR; } } if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = (uint64_t) -1; + ctx->pending_ack = NGX_QUIC_UNSET_PN; } } @@ -2442,14 +2444,14 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) range = 0; if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - if (prev_pending != (uint64_t) -1) { + if (prev_pending != NGX_QUIC_UNSET_PN) { if (ngx_quic_send_ack(c, ctx) != NGX_OK) { return NGX_ERROR; } } if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = (uint64_t) -1; + ctx->pending_ack = NGX_QUIC_UNSET_PN; } } @@ -2549,19 +2551,19 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, base = ctx->largest_range; - if (base == (uint64_t) -1) { + if (base == NGX_QUIC_UNSET_PN) { return; } - if (ctx->pending_ack != (uint64_t) -1 && pn >= ctx->pending_ack) { - ctx->pending_ack = (uint64_t) -1; + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; } largest = base; smallest = largest - ctx->first_range; if (pn >= largest) { - ctx->largest_range = (uint64_t) - 1; + ctx->largest_range = NGX_QUIC_UNSET_PN; ctx->first_range = 0; ctx->nranges = 0; return; @@ -2773,7 +2775,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ctx->largest_ack < max || ctx->largest_ack == (uint64_t) -1) { + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic updated largest received ack: %uL", max); @@ -4402,7 +4404,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) start = ngx_queue_data(q, ngx_quic_frame_t, queue); if (start->pnum <= ctx->largest_ack - && ctx->largest_ack != (uint64_t) -1) + && ctx->largest_ack != NGX_QUIC_UNSET_PN) { continue; } @@ -4469,7 +4471,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) ctx = &qc->send_ctx[i]; - if (ctx->largest_ack == (uint64_t) -1) { + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { continue; } -- cgit v1.2.3 From ff26faaf77d9fa555ee7476a11a574cb3530228c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 21 Oct 2020 20:39:25 +0300 Subject: QUIC: fixed dropping output ack ranges on input ack. While there, additional debug messages were added. --- src/event/ngx_event_quic.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7face15cf..7848a77ac 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2309,9 +2309,10 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_ack_packet pn %uL largest %uL nranges %ui", - pkt->pn, ctx->largest_range, ctx->nranges); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_ack_packet pn %uL largest %uL" + " first %uL nranges %ui", pkt->pn, ctx->largest_range, + ctx->first_range, ctx->nranges); prev_pending = ctx->pending_ack; @@ -2549,6 +2550,11 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_uint_t i, smallest, largest; ngx_quic_ack_range_t *r; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ngx_quic_drop_ack_ranges pn %uL largest %uL" + " first %uL nranges %ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + base = ctx->largest_range; if (base == NGX_QUIC_UNSET_PN) { @@ -2793,6 +2799,8 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } + ngx_quic_drop_ack_ranges(c, ctx, ack->largest); + pos = ack->ranges_start; end = ack->ranges_end; @@ -2863,9 +2871,6 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_congestion_ack(c, f); switch (f->type) { - case NGX_QUIC_FT_ACK: - ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); - break; case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: -- cgit v1.2.3 From fec3d792c98315cbbdd58d2a5e3605e485e780e8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 22 Oct 2020 11:05:50 +0100 Subject: QUIC: restored proper usage of ngx_quic_drop_ack_ranges(). ACK Ranges are again managed based on the remembered Largest Acknowledged sent in the packet being acknowledged, which partially reverts c01964fd7b8b. --- src/event/ngx_event_quic.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7848a77ac..70fb09eeb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2799,8 +2799,6 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } - ngx_quic_drop_ack_ranges(c, ctx, ack->largest); - pos = ack->ranges_start; end = ack->ranges_end; @@ -2871,6 +2869,10 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_congestion_ack(c, f); switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; case NGX_QUIC_FT_STREAM0: case NGX_QUIC_FT_STREAM1: -- cgit v1.2.3 From 8e1ec8a5c275dc9990efd0721480766794c9b4ba Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 22 Oct 2020 12:55:15 +0100 Subject: QUIC: updated README. - ACK ranges are implemented - up to draft-32 is now supported - removed mentions of early alpha quality and further cleanup --- README | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/README b/README index 146917a64..ce3c54b84 100644 --- a/README +++ b/README @@ -15,12 +15,12 @@ Experimental QUIC support for nginx The code is developed in a separate "quic" branch available at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.19.x. We are planning to merge new nginx - releases into this branch regularly. + on nginx mainline 1.19.x. We merge new nginx releases into + this branch regularly. The project code base is under the same BSD license as nginx. - The code is at an early alpha level of quality and should not + The code is currently at a beta level of quality and should not be used in production. We are working on improving HTTP/3 support with the goal of @@ -34,13 +34,13 @@ Experimental QUIC support for nginx What works now: - Currently we support IETF-QUIC draft-27, draft-28, draft-29. + Currently we support IETF-QUIC draft-27 through draft-32. Earlier drafts are NOT supported as they have incompatible wire format. You may look at src/event/ngx_event_quic.h for alternative values of the NGX_QUIC_DRAFT_VERSION macro used to select IETF draft version number. - nginx should be able to respond to simple HTTP/3 requests over QUIC and + nginx should be able to respond to HTTP/3 requests over QUIC and it should be possible to upload and download big files without errors. + The handshake completes successfully @@ -67,9 +67,6 @@ Experimental QUIC support for nginx Since the code is experimental and still under development, a lot of things may not work as expected, for example: - - ACK handling is basic: every received ack-eliciting packet - is acknowledged, no ack ranges are used - - Flow control mechanism is basic and intended to avoid CPU hog and make simple interactions possible @@ -217,8 +214,7 @@ Example configuration: Here are some tips that may help you to identify problems: - + Ensure you are building with proper SSL library that - implements draft 29 + + Ensure you are building with proper SSL library that supports QUIC + Ensure you are using the proper SSL library in runtime (`nginx -V` will show you what you are using) @@ -251,10 +247,10 @@ Example configuration: 7. Links - [1] https://tools.ietf.org/html/draft-ietf-quic-transport-29 - [2] https://tools.ietf.org/html/draft-ietf-quic-http-29 + [1] https://tools.ietf.org/html/draft-ietf-quic-transport + [2] https://tools.ietf.org/html/draft-ietf-quic-http [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel [4] https://boringssl.googlesource.com/boringssl/ - [5] https://tools.ietf.org/html/draft-ietf-quic-recovery-29 + [5] https://tools.ietf.org/html/draft-ietf-quic-recovery [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html -- cgit v1.2.3 From b92e59691869120c55f56d721710d7de28836c05 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 23 Oct 2020 18:22:01 +0300 Subject: QUIC: added missing "quic" prefix in debug messages. --- src/event/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 70fb09eeb..5e61f9f4d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2310,7 +2310,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_ack_packet pn %uL largest %uL" + "quic ngx_quic_ack_packet pn %uL largest %uL" " first %uL nranges %ui", pkt->pn, ctx->largest_range, ctx->first_range, ctx->nranges); @@ -2551,7 +2551,7 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_ack_range_t *r; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ngx_quic_drop_ack_ranges pn %uL largest %uL" + "quic ngx_quic_drop_ack_ranges pn %uL largest %uL" " first %uL nranges %ui", pn, ctx->largest_range, ctx->first_range, ctx->nranges); -- cgit v1.2.3 From 83d7a949e8af4b89bc8b41e89b50dcfe819837dd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 23 Oct 2020 17:08:50 +0300 Subject: QUIC: optimized acknowledgement generation. For application level packets, only every second packet is now acknowledged, respecting max ack delay. 13.2.1 Sending ACK Frames In order to assist loss detection at the sender, an endpoint SHOULD generate and send an ACK frame without delay when it receives an ack- eliciting packet either: * when the received packet has a packet number less than another ack-eliciting packet that has been received, or * when the packet has a packet number larger than the highest- numbered ack-eliciting packet that has been received and there are missing packets between that packet and this packet. 13.2.2. Acknowledgement Frequency A receiver SHOULD send an ACK frame after receiving at least two ack-eliciting packets. --- src/event/ngx_event_quic.c | 89 ++++++++++++++++++++++-------------- src/event/ngx_event_quic_transport.h | 2 +- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5e61f9f4d..7b2a7a611 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -48,6 +48,8 @@ #define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ #define NGX_QUIC_MAX_SR_PACKET 1200 +#define NGX_QUIC_MAX_ACK_GAP 2 + #define ngx_quic_level_name(lvl) \ (lvl == ssl_encryption_application) ? "application" \ : (lvl == ssl_encryption_initial) ? "initial" \ @@ -107,10 +109,11 @@ typedef struct { uint64_t pending_ack; /* non sent ack-eliciting */ uint64_t largest_range; uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; ngx_uint_t nranges; ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; - struct timeval ack_received; - ngx_uint_t send_ack; /* unsigned send_ack:1 */ + ngx_uint_t send_ack; } ngx_quic_send_ctx_t; @@ -250,8 +253,6 @@ static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t pn); static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); -static ngx_int_t ngx_quic_ack_delay(ngx_connection_t *c, - struct timeval *received, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); @@ -1911,11 +1912,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->validated = 1; } - if (pkt->level == ssl_encryption_early_data - || pkt->level == ssl_encryption_application) - { - ngx_gettimeofday(&pkt->received); - } + pkt->received = ngx_current_msec; c->log->action = "handling payload"; @@ -2320,7 +2317,11 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_post_event(&c->quic->push, &ngx_posted_events); - ctx->send_ack = 1; + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; if (ctx->pending_ack == NGX_QUIC_UNSET_PN || ctx->pending_ack < pkt->pn) @@ -2334,7 +2335,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) if (base == NGX_QUIC_UNSET_PN) { ctx->largest_range = pn; - ctx->ack_received = pkt->received; + ctx->largest_received = pkt->received; return NGX_OK; } @@ -2350,7 +2351,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) if (pn - base == 1) { ctx->first_range++; ctx->largest_range = pn; - ctx->ack_received = pkt->received; + ctx->largest_received = pkt->received; return NGX_OK; @@ -2376,7 +2377,12 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx->first_range = 0; ctx->largest_range = pn; - ctx->ack_received = pkt->received; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } i = 0; @@ -2386,6 +2392,11 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) /* pn < base, perform lookup in existing ranges */ + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + if (pn >= smallest && pn <= largest) { return NGX_OK; } @@ -2604,8 +2615,18 @@ static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { size_t ranges_len; + uint64_t ack_delay; ngx_quic_frame_t *frame; + if (ctx->level == ssl_encryption_application) { + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= c->quic->ctp.ack_delay_exponent; + + } else { + ack_delay = 0; + } + ranges_len = sizeof(ngx_quic_ack_range_t) * ctx->nranges; frame = ngx_quic_alloc_frame(c, ranges_len); @@ -2618,7 +2639,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; frame->u.ack.largest = ctx->largest_range; - frame->u.ack.delay = ngx_quic_ack_delay(c, &ctx->ack_received, ctx->level); + frame->u.ack.delay = ack_delay; frame->u.ack.range_count = ctx->nranges; frame->u.ack.first_range = ctx->first_range; frame->u.ack.ranges_start = frame->data; @@ -2633,27 +2654,6 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) } -static ngx_int_t -ngx_quic_ack_delay(ngx_connection_t *c, struct timeval *received, - enum ssl_encryption_level_t level) -{ - ngx_int_t ack_delay; - struct timeval tv; - - ack_delay = 0; - - if (level == ssl_encryption_application) { - ngx_gettimeofday(&tv); - ack_delay = (tv.tv_sec - received->tv_sec) * 1000000 - + tv.tv_usec - received->tv_usec; - ack_delay = ngx_max(ack_delay, 0); - ack_delay >>= c->quic->ctp.ack_delay_exponent; - } - - return ack_delay; -} - - static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c) { @@ -4054,6 +4054,7 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { ngx_uint_t i; + ngx_msec_t delay; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -4066,12 +4067,30 @@ ngx_quic_output(ngx_connection_t *c) ctx = &qc->send_ctx[i]; if (ctx->send_ack) { + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, qc->tp.max_ack_delay - delay); + } + + goto output; + } + } + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { return NGX_ERROR; } ctx->send_ack = 0; } + output: + if (ngx_quic_output_frames(c, ctx) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 12aad5aae..28f242881 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -293,7 +293,7 @@ typedef struct { struct ngx_quic_secret_s *secret; struct ngx_quic_secret_s *next; - struct timeval received; + ngx_msec_t received; uint64_t number; uint8_t num_len; uint32_t trunc; -- cgit v1.2.3 From 528e5bd1fb7a4ae37ccb66d6ec7a62629d24983f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 27 Oct 2020 14:32:08 +0300 Subject: QUIC: single function for frame debug logging. The function may be called for any initialized frame, both rx and tx. While there, shortened level names. --- src/event/ngx_event_quic.c | 256 +++++++++++++++++++++++++++-------- src/event/ngx_event_quic_transport.c | 96 +------------ src/event/ngx_event_quic_transport.h | 3 +- 3 files changed, 208 insertions(+), 147 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7b2a7a611..c62a3839f 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -51,9 +51,9 @@ #define NGX_QUIC_MAX_ACK_GAP 2 #define ngx_quic_level_name(lvl) \ - (lvl == ssl_encryption_application) ? "application" \ - : (lvl == ssl_encryption_initial) ? "initial" \ - : (lvl == ssl_encryption_handshake) ? "handshake" : "early_data" + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" typedef struct { @@ -366,6 +366,198 @@ static SSL_QUIC_METHOD quic_method = { }; +#if (NGX_DEBUG) + +static void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range; + ngx_uint_t i; + ngx_quic_ack_range_t *ranges; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, + "ACK largest:%uL fr:%uL nranges:%ui delay:%uL", + f->u.ack.largest, f->u.ack.first_range, + f->u.ack.range_count, f->u.ack.delay); + + if (tx) { + ranges = (ngx_quic_ack_range_t *) f->u.ack.ranges_start; + + for (i = 0; i < f->u.ack.range_count; i++) { + p = ngx_slprintf(p, last, " %uL,%uL", + ranges[i].gap, ranges[i].range); + } + + } else { + pos = f->u.ack.ranges_start; + end = f->u.ack.ranges_end; + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + p = ngx_slprintf(p, last, " %uL,%uL", gap, range); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, "NCID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->u.close.app ? "_APP" : "", f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + p = ngx_slprintf(p, last, + "STREAM type:0x%xi id:0x%xL offset:0x%xL " + "len:0x%xL bits off:%d len:%d fin:%d", + f->type, f->u.stream.stream_id, f->u.stream.offset, + f->u.stream.length, f->u.stream.off, f->u.stream.len, + f->u.stream.fin); + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%xL", + *(uint64_t *) &f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%xL", + f->u.path_response); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), + p - buf, buf); +} + +#else + +#define ngx_quic_log_frame(log, f, tx) + +#endif + + #if BORINGSSL_API_VERSION >= 10 static int @@ -604,9 +796,6 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, fs->sent += fsize; p += fsize; - ngx_sprintf(frame->info, "crypto, generated by SSL len=%ui level=%d", - fsize, level); - ngx_quic_queue_frame(qc, frame); } @@ -2106,6 +2295,8 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_ERROR; } + ngx_quic_log_frame(c->log, &frame, 0); + c->log->action = "handling frames"; p += len; @@ -2546,9 +2737,6 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, frame->u.ack.range_count = 0; frame->u.ack.first_range = largest - smallest; - ngx_sprintf(frame->info, "ACK for PN=%uL..%uL 0 ranges level=%d", - largest, smallest, frame->level); - return NGX_OK; } @@ -2645,9 +2833,6 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) frame->u.ack.ranges_start = frame->data; frame->u.ack.ranges_end = frame->data + ranges_len; - ngx_sprintf(frame->info, "ACK for PN=%uL %ui ranges level=%d", - ctx->largest_range, ctx->nranges, frame->level); - ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -2696,11 +2881,6 @@ ngx_quic_send_cc(ngx_connection_t *c) frame->u.close.reason.data = (u_char *) qc->error_reason; } - ngx_snprintf(frame->info, sizeof(frame->info) - 1, - "CONNECTION_CLOSE%s err:%ui level:%d ft:%ui reason:\"%s\"", - qc->error_app ? "_APP" : "", qc->error, qc->error_level, - qc->error_ftype, qc->error_reason ? qc->error_reason : "-"); - ngx_quic_queue_frame(c->quic, frame); qc->last_cc = ngx_current_msec; @@ -2732,7 +2912,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) frame->type = NGX_QUIC_FT_NEW_TOKEN; frame->u.token.length = token.len; frame->u.token.data = token.data; - ngx_sprintf(frame->info, "NEW_TOKEN"); + ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -2804,7 +2984,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, for (i = 0; i < ack->range_count; i++) { - n = ngx_quic_parse_ack_range(pkt, pos, end, &gap, &range); + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); if (n == NGX_ERROR) { return NGX_ERROR; } @@ -3334,7 +3514,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) /* 12.4 Frames and frame types, figure 8 */ frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_sprintf(frame->info, "HANDSHAKE DONE on handshake completed"); ngx_quic_queue_frame(c->quic, frame); if (ngx_quic_send_new_token(c) != NGX_OK) { @@ -3604,11 +3783,6 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, frame->u.max_stream_data.id = f->id; frame->u.max_stream_data.limit = n; - ngx_sprintf(frame->info, "MAX_STREAM_DATA id:0x%xL limit:%uL level=%d", - frame->u.max_stream_data.id, - frame->u.max_stream_data.limit, - frame->level); - ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -3828,9 +4002,6 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, frame->type = NGX_QUIC_FT_PATH_RESPONSE; frame->u.path_response = *f; - ngx_sprintf(frame->info, "PATH_RESPONSE data:0x%xL level:%d", - *(uint64_t *) &f->data, frame->level); - ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -3994,9 +4165,6 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; frame->u.retire_cid.sequence_number = seqnum; - ngx_sprintf(frame->info, "RETIRE_CONNECTION_ID seqnum=%uL level=%d", - seqnum, frame->level); - ngx_quic_queue_frame(c->quic, frame); return NGX_OK; @@ -4254,8 +4422,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic frame out: %s", f->info); + ngx_quic_log_frame(c->log, f, 1); len = ngx_quic_create_frame(p, f); if (len == -1) { @@ -4966,12 +5133,6 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + (b->end - b->last); - ngx_sprintf(frame->info, - "MAX_STREAM_DATA id:0x%xL limit:%uL l=%d on recv", - frame->u.max_stream_data.id, - frame->u.max_stream_data.limit, - frame->level); - ngx_quic_queue_frame(pc->quic, frame); } @@ -4989,9 +5150,6 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->type = NGX_QUIC_FT_MAX_DATA; frame->u.max_data.max_data = qc->streams.recv_max_data; - ngx_sprintf(frame->info, "MAX_DATA max_data:%uL level=%d on recv", - frame->u.max_data.max_data, frame->level); - ngx_quic_queue_frame(pc->quic, frame); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -5110,9 +5268,6 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) frame->u.stream.length = n; frame->u.stream.data = frame->data; - ngx_sprintf(frame->info, "STREAM id:0x%xL offset:%O len:%uz level:%d", - qs->id, c->sent, n, frame->level); - c->sent += n; qc->streams.sent += n; max_flow -= n; @@ -5259,9 +5414,6 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stop_sending.id = qs->id; frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ - ngx_sprintf(frame->info, "STOP_SENDING id:0x%xL err:0x%xL level:%d", - qs->id, frame->u.stop_sending.error_code, frame->level); - ngx_quic_queue_frame(qc, frame); } } @@ -5284,11 +5436,6 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.max_streams.bidi = 1; } - ngx_sprintf(frame->info, "MAX_STREAMS limit:%uL bidi:%ui level=%d", - frame->u.max_streams.limit, - frame->u.max_streams.bidi, - (int) frame->level); - ngx_quic_queue_frame(qc, frame); if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { @@ -5317,9 +5464,6 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stream.length = 0; frame->u.stream.data = NULL; - ngx_sprintf(frame->info, "STREAM id:0x%xL offset:%O fin:1 level:%d", - qs->id, c->sent, frame->level); - ngx_quic_queue_frame(qc, frame); (void) ngx_quic_output(pc); diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index aac260a0a..1abd6800d 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -699,10 +699,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: CRYPTO length: %uL off:%uL", - f->u.crypto.length, f->u.crypto.offset); - break; case NGX_QUIC_FT_PADDING: @@ -741,13 +737,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.ack.ranges_end = p; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in ACK largest:%uL delay:%uL" - " count:%uL first:%uL", f->u.ack.largest, - f->u.ack.delay, - f->u.ack.range_count, - f->u.ack.first_range); - if (f->type == NGX_QUIC_FT_ACK_ECN) { if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) @@ -802,9 +791,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: NCID seq:%uL retire:%uL len:%ud", - f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; case NGX_QUIC_FT_RETIRE_CONNECTION_ID: @@ -814,10 +800,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: RETIRE_CONNECTION_ID" - " sequence_number:%uL", - f->u.retire_cid.sequence_number); break; case NGX_QUIC_FT_CONNECTION_CLOSE: @@ -848,22 +830,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in CONNECTION_CLOSE" - " err:%s code:0x%xL type:0x%xL reason:'%V'", - ngx_quic_error_text(f->u.close.error_code), - f->u.close.error_code, f->u.close.frame_type, - &f->u.close.reason); - } else { - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: CONNECTION_CLOSE_APP:" - " code:0x%xL reason:'%V'", - f->u.close.error_code, &f->u.close.reason); - } - break; case NGX_QUIC_FT_STREAM0: @@ -912,16 +878,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STREAM type:0x%xi id:0x%xL offset:0x%xL " - "len:0x%xL bits off:%d len:%d fin:%d", - f->type, f->u.stream.stream_id, f->u.stream.offset, - f->u.stream.length, f->u.stream.off, f->u.stream.len, - f->u.stream.fin); - #ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump(pkt->log, "quic STREAM frame", - f->u.stream.data, f->u.stream.length); + ngx_quic_hexdump(pkt->log, "quic STREAM frame", + f->u.stream.data, f->u.stream.length); #endif break; @@ -932,9 +891,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_DATA max_data:%uL", - f->u.max_data.max_data); break; case NGX_QUIC_FT_RESET_STREAM: @@ -947,11 +903,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: RESET_STREAM" - " id:0x%xL error_code:0x%xL final_size:0x%xL", - f->u.reset_stream.id, f->u.reset_stream.error_code, - f->u.reset_stream.final_size); break; case NGX_QUIC_FT_STOP_SENDING: @@ -966,10 +917,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STOP_SENDING id:0x%xL error_code:0x%xL", - f->u.stop_sending.id, f->u.stop_sending.error_code); - break; case NGX_QUIC_FT_STREAMS_BLOCKED: @@ -982,12 +929,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.streams_blocked.bidi = (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STREAMS_BLOCKED limit:%uL bidi:%ui", - f->u.streams_blocked.limit, - f->u.streams_blocked.bidi); - break; case NGX_QUIC_FT_MAX_STREAMS: @@ -1000,10 +941,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_STREAMS limit:%uL bidi:%ui", - f->u.max_streams.limit, - f->u.max_streams.bidi); break; case NGX_QUIC_FT_MAX_STREAM_DATA: @@ -1018,10 +955,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: MAX_STREAM_DATA id:0x%xL limit:%uL", - f->u.max_stream_data.id, - f->u.max_stream_data.limit); break; case NGX_QUIC_FT_DATA_BLOCKED: @@ -1031,9 +964,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: DATA_BLOCKED limit:%uL", - f->u.data_blocked.limit); break; case NGX_QUIC_FT_STREAM_DATA_BLOCKED: @@ -1048,11 +978,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: STREAM_DATA_BLOCKED" - " id:0x%xL limit:%uL", - f->u.stream_data_blocked.id, - f->u.stream_data_blocked.limit); break; case NGX_QUIC_FT_PATH_CHALLENGE: @@ -1062,9 +987,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: PATH_CHALLENGE data:0x%xL", - *(uint64_t *) &f->u.path_challenge.data); break; case NGX_QUIC_FT_PATH_RESPONSE: @@ -1074,9 +996,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic frame in: PATH_RESPONSE data:0x%xL", - *(uint64_t *) &f->u.path_response.data); break; default: @@ -1085,6 +1004,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } + f->level = pkt->level; + return p - start; error: @@ -1173,7 +1094,7 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) ssize_t -ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, +ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, uint64_t *gap, uint64_t *range) { u_char *p; @@ -1182,21 +1103,18 @@ ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, u_char *end, p = ngx_quic_parse_int(p, end, gap); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse ack frame gap"); return NGX_ERROR; } p = ngx_quic_parse_int(p, end, range); if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse ack frame range"); return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ACK range: gap %uL range %uL", *gap, *range); - return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 28f242881..8eccf7f5b 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -284,7 +284,6 @@ struct ngx_quic_frame_s { ngx_quic_path_challenge_frame_t path_challenge; ngx_quic_path_challenge_frame_t path_response; } u; - u_char info[128]; /* for debug */ }; @@ -344,7 +343,7 @@ ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *frame); ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); -ssize_t ngx_quic_parse_ack_range(ngx_quic_header_t *pkt, u_char *start, +ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, uint64_t *gap, uint64_t *range); ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, -- cgit v1.2.3 From 0946f8c3ca9300ddcd2b6b2bd4945c5f620ac391 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 27 Oct 2020 14:12:31 +0300 Subject: QUIC: revised value separators in debug and error messages. All values are prefixed with name and separated from it using colon. Multiple values are listed without commas in between. Rationale: this greatly simplifies log parsing for analysis. --- src/event/ngx_event_quic.c | 97 ++++++++++++++++++----------------- src/event/ngx_event_quic_protection.c | 4 +- src/event/ngx_event_quic_transport.c | 40 +++++++-------- 3 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c62a3839f..5092f0343 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -699,7 +699,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic SSL_get_peer_quic_transport_params():" - " params_len %ui", client_params_len); + " params_len:%ui", client_params_len); if (client_params_len == 0) { /* quic-tls 8.2 */ @@ -828,7 +828,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_alert(), lvl=%d, alert=%d", + "quic ngx_quic_send_alert() lvl:%d alert:%d", (int) level, (int) alert); qc = c->quic; @@ -1582,7 +1582,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_pool_t *pool; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_close_connection, rc: %i", rc); + "quic ngx_quic_close_connection rc:%i", rc); if (!c->quic) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1658,7 +1658,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) if (rc == NGX_OK) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close, drain = %d", + "quic immediate close drain:%d", qc->draining); qc->close.log = c->log; @@ -1678,7 +1678,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to %serror: %ui %s", + "quic immediate close due to %s error: %ui %s", qc->error_app ? "app " : "", qc->error, qc->error_reason ? qc->error_reason : ""); } @@ -2019,7 +2019,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, /* 7.2. Negotiating Connection IDs */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic too short dcid in initial" - " packet: length %i", pkt->dcid.len); + " packet: len:%i", pkt->dcid.len); return NGX_ERROR; } @@ -2465,7 +2465,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) if (p != end) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic trailing garbage in payload: %ui bytes", end - p); + "quic trailing garbage in payload:%ui bytes", end - p); qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; return NGX_ERROR; @@ -2498,8 +2498,8 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_ack_packet pn %uL largest %uL" - " first %uL nranges %ui", pkt->pn, ctx->largest_range, + "quic ngx_quic_ack_packet pn:%uL largest %uL fr:%uL" + " nranges:%ui", pkt->pn, ctx->largest_range, ctx->first_range, ctx->nranges); prev_pending = ctx->pending_ack; @@ -2750,8 +2750,8 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_ack_range_t *r; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_drop_ack_ranges pn %uL largest %uL" - " first %uL nranges %ui", pn, ctx->largest_range, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, ctx->first_range, ctx->nranges); base = ctx->largest_range; @@ -2936,7 +2936,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ctx = ngx_quic_get_send_ctx(qc, pkt->level); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_handle_ack_frame level %d", pkt->level); + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); /* * If any computed packet number is negative, an endpoint MUST @@ -2964,7 +2964,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic updated largest received ack: %uL", max); + "quic updated largest received ack:%uL", max); /* * An endpoint generates an RTT sample on receiving an @@ -2993,7 +2993,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (gap + 2 > min) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range %ui in ack frame", i); + "quic invalid range:%ui in ack frame", i); return NGX_ERROR; } @@ -3002,7 +3002,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (range > max) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range %ui in ack frame", i); + "quic invalid range:%ui in ack frame", i); return NGX_ERROR; } @@ -3030,7 +3030,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_connection_t *qc; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handle ack range: min:%uL max:%uL", min, max); + "quic handle ack range min:%uL max:%uL", min, max); qc = c->quic; @@ -3145,7 +3145,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic rtt sample: latest %M, min %M, avg %M, var %M", + "quic rtt sample latest:%M min:%M avg:%M var:%M", latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); } @@ -3205,7 +3205,7 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) sn->acked += f->u.stream.length; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, - "quic stream ack %uL acked:%uL, unacked:%uL", + "quic stream ack len:%uL acked:%uL unacked:%uL", f->u.stream.length, sn->acked, sent - sn->acked); } @@ -3223,7 +3223,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, if (f->offset > fs->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic out-of-order frame: expecting %uL got %uL", + "quic out-of-order frame: expecting:%uL got:%uL", fs->received, f->offset); return ngx_quic_buffer_frame(c, fs, frame); @@ -3284,7 +3284,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, fs->total -= f->length; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic skipped buffered frame, total %ui", + "quic skipped buffered frame, total:%ui", fs->total); ngx_quic_free_frame(c, frame); continue; @@ -3311,7 +3311,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_queue_remove(q); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic consumed buffered frame, total %ui", fs->total); + "quic consumed buffered frame, total:%ui", fs->total); ngx_quic_free_frame(c, frame); @@ -3387,7 +3387,7 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ordered frame with unexpected offset:" - " buffered, total %ui", fs->total); + " buffered total:%ui", fs->total); if (ngx_queue_empty(&fs->frames)) { ngx_queue_insert_after(&fs->frames, &dst->queue); @@ -3468,7 +3468,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) ssl_conn = c->ssl->connection; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); @@ -3498,7 +3498,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } else if (n == 1 && !SSL_in_init(ssl_conn)) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher: %s", SSL_get_cipher(ssl_conn)); + "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handshake completed successfully"); @@ -3540,7 +3540,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level: %d, SSL_quic_write_level: %d", + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); @@ -4330,7 +4330,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) if (((c->sent + hlen + len + f->len) / 3) > qc->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic hit amplification limit" - " received %uz sent %O", + " received:%uz sent:%O", qc->received, c->sent); break; } @@ -4488,8 +4488,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, res.data = dst; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet ready: %ui bytes at level %d" - " need_ack: %d number: %L encoded %d:0x%xD", + "quic packet ready bytes:%ui level:%d" + " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", out.len, start->level, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); @@ -4771,7 +4771,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) >= qc->streams.server_max_streams_bidi) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server bidi streams: %uL", + "quic too many server bidi streams:%uL", qc->streams.server_streams_bidi); return NULL; } @@ -4780,7 +4780,8 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) | NGX_QUIC_STREAM_SERVER_INITIATED; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream %uL/%uL id:0x%xL", + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", qc->streams.server_streams_bidi, qc->streams.server_max_streams_bidi, id); @@ -4792,7 +4793,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) >= qc->streams.server_max_streams_uni) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server uni streams: %uL", + "quic too many server uni streams:%uL", qc->streams.server_streams_uni); return NULL; } @@ -4802,7 +4803,8 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) | NGX_QUIC_STREAM_UNIDIRECTIONAL; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server uni stream %uL/%uL id:0x%xL", + "quic creating server uni stream" + " streams:%uL max:%uL id:0x%xL", qc->streams.server_streams_uni, qc->streams.server_max_streams_uni, id); @@ -4879,7 +4881,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL is new", id); + "quic stream id:0x%xL is new", id); qc = c->quic; @@ -4970,7 +4972,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL create", id); + "quic stream id:0x%xL create", id); qc = c->quic; @@ -5089,7 +5091,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL recv: eof:%d, avail:%z", + "quic stream recv id:0x%xL eof:%d avail:%z", qs->id, rev->pending_eof, b->last - b->pos); if (b->pos == b->last) { @@ -5101,7 +5103,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL recv() not ready", qs->id); + "quic stream id:0x%xL recv() not ready", qs->id); return NGX_AGAIN; } @@ -5119,7 +5121,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL recv: %z of %uz", qs->id, len, size); + "quic stream id:0x%xL recv len:%z of size:%uz", + qs->id, len, size); if (!rev->pending_eof) { frame = ngx_quic_alloc_frame(pc, 0); @@ -5153,7 +5156,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_queue_frame(pc->quic, frame); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL recv: increased max data: %uL", + "quic stream id:0x%xL recv: increased max_data:%uL", qs->id, qc->streams.recv_max_data); } @@ -5299,7 +5302,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz, frames:%ui", sent, nframes); + "quic send_chain sent:%uz nframes:%ui", sent, nframes); return in; } @@ -5368,7 +5371,7 @@ ngx_quic_max_stream_flow(ngx_connection_t *c) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow: %uz", size); + "quic send flow:%uz", size); return size; } @@ -5389,7 +5392,7 @@ ngx_quic_stream_cleanup_handler(void *data) qc = pc->quic; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL cleanup", qs->id); + "quic stream id:0x%xL cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_frames(pc, &qs->fs.frames); @@ -5445,7 +5448,7 @@ ngx_quic_stream_cleanup_handler(void *data) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id 0x%xL send fin", qs->id); + "quic stream id:0x%xL send fin", qs->id); frame = ngx_quic_alloc_frame(pc, 0); if (frame == NULL) { @@ -5547,7 +5550,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion ack recovery win:%uz, ss:%uz, if:%uz", + "quic congestion ack recovery win:%uz ss:%uz if:%uz", cg->window, cg->ssthresh, cg->in_flight); return; @@ -5557,14 +5560,14 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) cg->window += f->plen; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion slow start win:%uz, ss:%uz, if:%uz", + "quic congestion slow start win:%uz ss:%uz if:%uz", cg->window, cg->ssthresh, cg->in_flight); } else { cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion avoidance win:%uz, ss:%uz, if:%uz", + "quic congestion avoidance win:%uz ss:%uz if:%uz", cg->window, cg->ssthresh, cg->in_flight); } @@ -5598,7 +5601,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost recovery win:%uz, ss:%uz, if:%uz", + "quic congestion lost recovery win:%uz ss:%uz if:%uz", cg->window, cg->ssthresh, cg->in_flight); return; @@ -5614,7 +5617,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) cg->ssthresh = cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost win:%uz, ss:%uz, if:%uz", + "quic congestion lost win:%uz ss:%uz if:%uz", cg->window, cg->ssthresh, cg->in_flight); } diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 450281aea..b2e7e5529 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1113,9 +1113,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->flags = clearflags; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic clear flags: %xd", clearflags); + "quic clearflags:%xd", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet number: %uL, len: %xi", pn, pnl); + "quic packet number:%uL len:%xi", pn, pnl); /* packet protection */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 1abd6800d..7850b3c3e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -597,7 +597,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic initial packet length: %uL", varint); + "quic initial packet len:%uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -636,7 +636,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic handshake packet length: %uL", plen); + "quic handshake packet len:%uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -747,7 +747,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ACK ECN counters: %uL %uL %uL", + "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); } @@ -1013,7 +1013,7 @@ error: pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse frame type 0x%xi", f->type); + "quic failed to parse frame type:0x%xi", f->type); return NGX_ERROR; } @@ -1509,7 +1509,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, case NGX_QUIC_TP_SR_TOKEN: ngx_log_error(NGX_LOG_INFO, log, 0, "quic client sent forbidden transport param" - " id 0x%xL", id); + " id:0x%xL", id); return NGX_ERROR; } @@ -1517,7 +1517,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse" - " transport param id 0x%xL length", id); + " transport param id:0x%xL length", id); return NGX_ERROR; } @@ -1526,13 +1526,13 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (rc == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, log, 0, "quic failed to parse" - " transport param id 0x%xL data", id); + " transport param id:0x%xL data", id); return NGX_ERROR; } if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, log, 0, - "quic unknown transport param id 0x%xL, skipped", id); + "quic unknown transport param id:0x%xL, skipped", id); } p += len; @@ -1541,7 +1541,7 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (p != end) { ngx_log_error(NGX_LOG_INFO, log, 0, "quic trailing garbage in" - " transport parameters: %ui bytes", + " transport parameters: bytes:%ui", end - p); return NGX_ERROR; } @@ -1553,45 +1553,45 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, "quic tp disable active migration: %ui", tp->disable_active_migration); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", tp->max_idle_timeout); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_udp_payload_size: %ui", + "quic tp max_udp_payload_size:%ui", tp->max_udp_payload_size); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", tp->initial_max_data); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_bidi_local: %ui", + "quic tp max_stream_data_bidi_local:%ui", tp->initial_max_stream_data_bidi_local); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_bidi_remote: %ui", + "quic tp max_stream_data_bidi_remote:%ui", tp->initial_max_stream_data_bidi_remote); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_uni: %ui", + "quic tp max_stream_data_uni:%ui", tp->initial_max_stream_data_uni); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp initial_max_streams_bidi: %ui", + "quic tp initial_max_streams_bidi:%ui", tp->initial_max_streams_bidi); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp initial_max_streams_uni: %ui", + "quic tp initial_max_streams_uni:%ui", tp->initial_max_streams_uni); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp ack_delay_exponent: %ui", + "quic tp ack_delay_exponent:%ui", tp->ack_delay_exponent); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay: %ui", + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", tp->max_ack_delay); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp active_connection_id_limit: %ui", + "quic tp active_connection_id_limit:%ui", tp->active_connection_id_limit); #if (NGX_QUIC_DRAFT_VERSION >= 28) -- cgit v1.2.3 From 1d9e9a1a29aeaac922d80019f41879a17600322d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 26 Oct 2020 00:34:24 +0300 Subject: QUIC: added logging of a declined packet without retry token. --- src/event/ngx_event_quic.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5092f0343..5e19f33c4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1984,6 +1984,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (!pkt->token.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic discard retry packet without token"); return NGX_DECLINED; } -- cgit v1.2.3 From d35db4b3eb26c146b5f5014b6909c918cb91363d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 26 Oct 2020 23:17:54 +0300 Subject: QUIC: added connection state debug to event handlers. --- src/event/ngx_event_quic.c | 87 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 10 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5e19f33c4..335552f91 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -551,9 +551,74 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) p - buf, buf); } + +static void +ngx_quic_connstate_dbg(ngx_connection_t *c) +{ + u_char *p, *last; + ngx_quic_connection_t *qc; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = p + sizeof(buf); + + qc = c->quic; + + p = ngx_slprintf(p, last, "state:"); + + if (qc) { + + if (qc->error) { + p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); + p = ngx_slprintf(p, last, " error:%ui", qc->error); + + if (qc->error_reason) { + p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); + } + } + + 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" : ""); + p = ngx_slprintf(p, last, "%s", qc->in_retry ? " retry" : ""); + p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); + + } else { + p = ngx_slprintf(p, last, " early"); + } + + if (c->read->timer_set) { + p = ngx_slprintf(p, last, + qc && qc->send_timer_set ? " send:%M" : " read:%M", + c->read->timer.key - ngx_current_msec); + } + + if (qc) { + + if (qc->push.timer_set) { + p = ngx_slprintf(p, last, " push:%M", + qc->push.timer.key - ngx_current_msec); + } + + if (qc->pto.timer_set) { + p = ngx_slprintf(p, last, " pto:%M", + qc->pto.timer.key - ngx_current_msec); + } + + if (qc->close.timer_set) { + p = ngx_slprintf(p, last, " close:%M", + qc->close.timer.key - ngx_current_msec); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %*s", p - buf, buf); +} + #else #define ngx_quic_log_frame(log, f, tx) +#define ngx_quic_connstate_dbg(c) #endif @@ -868,6 +933,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) c->read->handler = ngx_quic_input_handler; + ngx_quic_connstate_dbg(c); return; } @@ -1573,6 +1639,8 @@ ngx_quic_input_handler(ngx_event_t *rev) qc->send_timer_set = 0; ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); } @@ -1852,17 +1920,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) #if (NGX_DEBUG) if (pkt.parsed) { - ngx_quic_connection_t *qc; - - qc = c->quic; - - ngx_log_debug8(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pkt done %s decr:%d pn:%L pe:%ui rc:%i" - " closing:%d err:%ui %s", + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %s pkt done decr:%d pn:%L perr:%ui rc:%i", ngx_quic_level_name(pkt.level), pkt.decrypted, - pkt.pn, pkt.error, rc, (qc && qc->closing) ? 1 : 0, - qc ? qc->error : 0, - (qc && qc->error_reason) ? qc->error_reason : ""); + pkt.pn, pkt.error, rc); } else { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic pkt done parse failed rc:%i", rc); @@ -4610,6 +4671,8 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_quic_resend_frames(c, ctx); } + + ngx_quic_connstate_dbg(c); } @@ -4626,6 +4689,8 @@ ngx_quic_push_handler(ngx_event_t *ev) ngx_quic_close_connection(c, NGX_ERROR); return; } + + ngx_quic_connstate_dbg(c); } @@ -4641,6 +4706,8 @@ void ngx_quic_lost_handler(ngx_event_t *ev) if (ngx_quic_detect_lost(c) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); } + + ngx_quic_connstate_dbg(c); } -- cgit v1.2.3 From 37b73608932583e98f8fb8d8ca16417b3fc7bfa1 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 26 Oct 2020 23:47:16 +0300 Subject: QUIC: added "rx" and "tx" prefixes to packet debug. --- src/event/ngx_event_quic.c | 12 ++++++------ src/event/ngx_event_quic_protection.c | 4 ++-- src/event/ngx_event_quic_transport.c | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 335552f91..b42e9fdcc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1921,7 +1921,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) #if (NGX_DEBUG) if (pkt.parsed) { ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic %s pkt done decr:%d pn:%L perr:%ui rc:%i", + "quic pkt %s done decr:%d pn:%L perr:%ui rc:%i", ngx_quic_level_name(pkt.level), pkt.decrypted, pkt.pn, pkt.error, rc); } else { @@ -1999,10 +1999,10 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc = c->quic; #if (NGX_DEBUG) - ngx_quic_hexdump(c->log, "quic pkt dcid", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump(c->log, "quic pkt rx dcid", pkt->dcid.data, pkt->dcid.len); if (pkt->level != ssl_encryption_application) { - ngx_quic_hexdump(c->log, "quic pkt scid", pkt->scid.data, + ngx_quic_hexdump(c->log, "quic pkt rx scid", pkt->scid.data, pkt->scid.len); } #endif @@ -4551,10 +4551,10 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, res.data = dst; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet ready bytes:%ui level:%d" + "quic pkt tx %s bytes:%ui" " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - out.len, start->level, pkt.need_ack, pkt.number, - pkt.num_len, pkt.trunc); + ngx_quic_level_name(start->level), out.len, pkt.need_ack, + pkt.number, pkt.num_len, pkt.trunc); if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) { ngx_quic_free_frames(c, frames); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index b2e7e5529..db1219f46 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1113,9 +1113,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->flags = clearflags; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic clearflags:%xd", clearflags); + "quic pkt rx clearflags:%xd", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet number:%uL len:%xi", pn, pnl); + "quic pkt rx number:%uL len:%xi", pn, pnl); /* packet protection */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7850b3c3e..b6719de94 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -326,7 +326,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic long packet flags:%xd version:%xD", + "quic pkt rx long flags:%xd version:%xD", pkt->flags, pkt->version); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { @@ -540,7 +540,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) end = pkt->data + pkt->len; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic short packet flags:%xd", pkt->flags); + "quic pkt rx short flags:%xd", pkt->flags); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); @@ -597,7 +597,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic initial packet len:%uL", varint); + "quic pkt rx initial len:%uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -636,7 +636,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic handshake packet len:%uL", plen); + "quic pkt rx handshake len:%uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, -- cgit v1.2.3 From 7ba467944d522b398feb1001e2089f4c61d0ee13 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 26 Oct 2020 23:47:49 +0300 Subject: QUIC: got rid of "pkt" abbreviation in logs. --- src/event/ngx_event_quic.c | 11 ++++++----- src/event/ngx_event_quic_protection.c | 4 ++-- src/event/ngx_event_quic_transport.c | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index b42e9fdcc..4593833da 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1921,12 +1921,12 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) #if (NGX_DEBUG) if (pkt.parsed) { ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pkt %s done decr:%d pn:%L perr:%ui rc:%i", + "quic packet %s done decr:%d pn:%L perr:%ui rc:%i", ngx_quic_level_name(pkt.level), pkt.decrypted, pkt.pn, pkt.error, rc); } else { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pkt done parse failed rc:%i", rc); + "quic packet done parse failed rc:%i", rc); } #endif @@ -1999,10 +1999,11 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc = c->quic; #if (NGX_DEBUG) - ngx_quic_hexdump(c->log, "quic pkt rx dcid", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump(c->log, "quic packet rx dcid", + pkt->dcid.data, pkt->dcid.len); if (pkt->level != ssl_encryption_application) { - ngx_quic_hexdump(c->log, "quic pkt rx scid", pkt->scid.data, + ngx_quic_hexdump(c->log, "quic packet rx scid", pkt->scid.data, pkt->scid.len); } #endif @@ -4551,7 +4552,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, res.data = dst; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pkt tx %s bytes:%ui" + "quic packet tx %s bytes:%ui" " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", ngx_quic_level_name(start->level), out.len, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index db1219f46..e59af9b81 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1113,9 +1113,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, pkt->flags = clearflags; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx clearflags:%xd", clearflags); + "quic packet rx clearflags:%xd", clearflags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx number:%uL len:%xi", pn, pnl); + "quic packet rx number:%uL len:%xi", pn, pnl); /* packet protection */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index b6719de94..085e5cb62 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -326,7 +326,7 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx long flags:%xd version:%xD", + "quic packet rx long flags:%xd version:%xD", pkt->flags, pkt->version); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { @@ -540,7 +540,7 @@ ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) end = pkt->data + pkt->len; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx short flags:%xd", pkt->flags); + "quic packet rx short flags:%xd", pkt->flags); if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); @@ -597,7 +597,7 @@ ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx initial len:%uL", varint); + "quic packet rx initial len:%uL", varint); if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, @@ -636,7 +636,7 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic pkt rx handshake len:%uL", plen); + "quic packet rx handshake len:%uL", plen); if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, -- cgit v1.2.3 From ddd665ca666b677a326a90e3646b8302135ff8a3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 26 Oct 2020 23:58:34 +0300 Subject: QUIC: updated anti-amplification check for draft 32. This accounts for the following change: * Require expansion of datagrams to ensure that a path supports at least 1200 bytes: - During the handshake ack-eliciting Initial packets from the server need to be expanded --- src/event/ngx_event_quic.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4593833da..2eb54c37b 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4340,7 +4340,7 @@ ngx_quic_output(ngx_connection_t *c) static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - size_t len, hlen; + size_t len, hlen, cutoff; ngx_uint_t need_ack; ngx_queue_t *q, range; ngx_quic_frame_t *f; @@ -4391,7 +4391,14 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) * send more than three times the data it receives; */ - if (((c->sent + hlen + len + f->len) / 3) > qc->received) { + if (f->level == ssl_encryption_initial) { + cutoff = (c->sent + NGX_QUIC_MIN_INITIAL_SIZE) / 3; + + } else { + cutoff = (c->sent + hlen + len + f->len) / 3; + } + + if (cutoff > qc->received) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic hit amplification limit" " received:%uz sent:%O", -- cgit v1.2.3 From a1473ce8b08cba7959daee8860677f70d4e36c8a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 27 Oct 2020 00:00:56 +0300 Subject: QUIC: added push event afer the address was validated. This allows to continue processing when the anti-amplification limit was hit. --- src/event/ngx_event_quic.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2eb54c37b..dcdea293c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2162,7 +2162,11 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, * that no more Initial packets need to be exchanged */ ngx_quic_discard_ctx(c, ssl_encryption_initial); - qc->validated = 1; + + if (qc->validated == 0) { + qc->validated = 1; + ngx_post_event(&c->quic->push, &ngx_posted_events); + } } pkt->received = ngx_current_msec; -- cgit v1.2.3 From 68f7e9540a0852bc69453058ae7588c77a3a1494 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 27 Oct 2020 00:14:24 +0300 Subject: QUIC: cleanup send context properly. The patch resets ctx->frames queue, which may contain frames. It was possible that congestion or amplification limits prevented all frames to be sent. Retransmitted frames could be accounted twice as inflight: first time in ngx_quic_congestion_lost() called from ngx_quic_resend_frames(), and later from ngx_quic_discard_ctx(). --- src/event/ngx_event_quic.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index dcdea293c..5cffeaebc 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2254,6 +2254,15 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_free_frame(c, f); } + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + ctx->send_ack = 0; } @@ -5677,6 +5686,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) cg = &qc->congestion; cg->in_flight -= f->plen; + f->plen = 0; timer = f->last - cg->recovery_start; -- cgit v1.2.3 From a37d00064ac393e2b95c183358f92674d36ed854 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 27 Oct 2020 13:24:00 +0000 Subject: QUIC: unified range format for rx and tx ACK frames. Previously, tx ACK frames held ranges in an array of ngx_quic_ack_range_t, while rx ACK frames held ranges in the serialized format. Now serialized format is used for both types of frames. --- src/event/ngx_event_quic.c | 53 ++++++++++++++++++------------------ src/event/ngx_event_quic_transport.c | 41 +++++++++++++++++----------- src/event/ngx_event_quic_transport.h | 1 + 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5cffeaebc..217190463 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -371,12 +371,11 @@ static SSL_QUIC_METHOD quic_method = { static void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) { - u_char *p, *last, *pos, *end; - ssize_t n; - uint64_t gap, range; - ngx_uint_t i; - ngx_quic_ack_range_t *ranges; - u_char buf[NGX_MAX_ERROR_STR]; + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; p = buf; last = buf + sizeof(buf); @@ -400,28 +399,18 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) f->u.ack.largest, f->u.ack.first_range, f->u.ack.range_count, f->u.ack.delay); - if (tx) { - ranges = (ngx_quic_ack_range_t *) f->u.ack.ranges_start; + pos = f->u.ack.ranges_start; + end = f->u.ack.ranges_end; - for (i = 0; i < f->u.ack.range_count; i++) { - p = ngx_slprintf(p, last, " %uL,%uL", - ranges[i].gap, ranges[i].range); + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; } - } else { - pos = f->u.ack.ranges_start; - end = f->u.ack.ranges_end; + pos += n; - for (i = 0; i < f->u.ack.range_count; i++) { - n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); - if (n == NGX_ERROR) { - break; - } - - pos += n; - - p = ngx_slprintf(p, last, " %uL,%uL", gap, range); - } + p = ngx_slprintf(p, last, " %uL,%uL", gap, range); } if (f->type == NGX_QUIC_FT_ACK_ECN) { @@ -2879,8 +2868,10 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { + u_char *p; size_t ranges_len; uint64_t ack_delay; + ngx_uint_t i; ngx_quic_frame_t *frame; if (ctx->level == ssl_encryption_application) { @@ -2892,14 +2883,24 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ack_delay = 0; } - ranges_len = sizeof(ngx_quic_ack_range_t) * ctx->nranges; + ranges_len = 0; + + for (i = 0; i < ctx->nranges; i++) { + ranges_len += ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + } frame = ngx_quic_alloc_frame(c, ranges_len); if (frame == NULL) { return NGX_ERROR; } - ngx_memcpy(frame->data, ctx->ranges, ranges_len); + p = frame->data; + + for (i = 0; i < ctx->nranges; i++) { + p += ngx_quic_create_ack_range(p, ctx->ranges[i].gap, + ctx->ranges[i].range); + } frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 085e5cb62..26cc690bf 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1119,6 +1119,27 @@ ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, } +size_t +ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(gap); + len += ngx_quic_varint_len(range); + return len; + } + + start = p; + + ngx_quic_build_int(&p, gap); + ngx_quic_build_int(&p, range); + + return p - start; +} + + ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) { @@ -1187,12 +1208,8 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) { - size_t len; - u_char *start; - ngx_uint_t i; - ngx_quic_ack_range_t *ranges; - - ranges = (ngx_quic_ack_range_t *) ack->ranges_start; + size_t len; + u_char *start; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); @@ -1200,11 +1217,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) len += ngx_quic_varint_len(ack->delay); len += ngx_quic_varint_len(ack->range_count); len += ngx_quic_varint_len(ack->first_range); - - for (i = 0; i < ack->range_count; i++) { - len += ngx_quic_varint_len(ranges[i].gap); - len += ngx_quic_varint_len(ranges[i].range); - } + len += ack->ranges_end - ack->ranges_start; return len; } @@ -1216,11 +1229,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, ack->delay); ngx_quic_build_int(&p, ack->range_count); ngx_quic_build_int(&p, ack->first_range); - - for (i = 0; i < ack->range_count; i++) { - ngx_quic_build_int(&p, ranges[i].gap); - ngx_quic_build_int(&p, ranges[i].range); - } + p = ngx_cpymem(p, ack->ranges_start, ack->ranges_end - ack->ranges_start); return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 8eccf7f5b..336da9d0b 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -345,6 +345,7 @@ 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, u_char *end, uint64_t *gap, uint64_t *range); +size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, ngx_log_t *log); -- cgit v1.2.3 From 42a4e6d3111d701a5f286b34b6b3e873797ab0ab Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 28 Oct 2020 09:15:04 +0000 Subject: QUIC: changed ACK frame debugging. Previously ACK ranges were logged as a gap/range sequence. Now these values are expanded to packet number ranges for easier reading. --- src/event/ngx_event_quic.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 217190463..7763cf325 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -373,7 +373,7 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) { u_char *p, *last, *pos, *end; ssize_t n; - uint64_t gap, range; + uint64_t gap, range, largest, smallest; ngx_uint_t i; u_char buf[NGX_MAX_ERROR_STR]; @@ -394,14 +394,22 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: - p = ngx_slprintf(p, last, - "ACK largest:%uL fr:%uL nranges:%ui delay:%uL", - f->u.ack.largest, f->u.ack.first_range, + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", f->u.ack.range_count, f->u.ack.delay); pos = f->u.ack.ranges_start; end = f->u.ack.ranges_end; + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + for (i = 0; i < f->u.ack.range_count; i++) { n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); if (n == NGX_ERROR) { @@ -410,7 +418,15 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) pos += n; - p = ngx_slprintf(p, last, " %uL,%uL", gap, range); + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } } if (f->type == NGX_QUIC_FT_ACK_ECN) { @@ -3107,9 +3123,6 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_frame_t *f; ngx_quic_connection_t *qc; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handle ack range min:%uL max:%uL", min, max); - qc = c->quic; *send_time = NGX_TIMER_INFINITE; -- cgit v1.2.3 From 54187d2d7a3cd2a34105a9ccaee732b76e6c3cf5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 27 Oct 2020 18:21:36 +0000 Subject: QUIC: changed STREAM frame debugging. --- src/event/ngx_event_quic.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7763cf325..9cdc41e9c 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -474,12 +474,20 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - p = ngx_slprintf(p, last, - "STREAM type:0x%xi id:0x%xL offset:0x%xL " - "len:0x%xL bits off:%d len:%d fin:%d", - f->type, f->u.stream.stream_id, f->u.stream.offset, - f->u.stream.length, f->u.stream.off, f->u.stream.len, - f->u.stream.fin); + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + break; case NGX_QUIC_FT_MAX_DATA: -- cgit v1.2.3 From 21a5955f75688647b794e655635c23ecf1b4e290 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 28 Oct 2020 14:22:51 +0300 Subject: QUIC: avoided retransmission of stale ack frames. Acknowledgments are regenerated using the most recent data available. --- src/event/ngx_event_quic.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9cdc41e9c..9b83ed083 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4843,6 +4843,8 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic resend packet pnum:%uL", start->pnum); + ngx_quic_congestion_lost(c, start); + do { f = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -4853,11 +4855,20 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) q = ngx_queue_next(q); ngx_queue_remove(&f->queue); - ngx_queue_insert_tail(&ctx->frames, &f->queue); - } while (q != ngx_queue_sentinel(&ctx->sent)); + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + ngx_quic_free_frame(c, f); + break; - ngx_quic_congestion_lost(c, start); + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); if (qc->closing) { return; -- cgit v1.2.3 From c3e8e59a55c4bd144dec6ca1c55f06f8d509ec50 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Oct 2020 14:25:02 +0000 Subject: QUIC: handle more frames in ngx_quic_resend_frames(). When a packet is declared lost, its frames are handled differently according to 13.3. Retransmission of Information. --- src/event/ngx_event_quic.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9b83ed083..ab5d0c188 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4832,8 +4832,11 @@ ngx_quic_detect_lost(ngx_connection_t *c) static void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { + size_t n; + ngx_buf_t *b; ngx_queue_t *q; ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; qc = c->quic; @@ -4864,6 +4867,61 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_quic_free_frame(c, f); break; + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + 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; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + sn = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (sn == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + + if (f->u.max_stream_data.limit < n) { + f->u.max_stream_data.limit = n; + } + + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (sn && sn->c->write->error) { + /* RESET_STREAM was sent */ + ngx_quic_free_frame(c, f); + break; + } + + /* fall through */ + default: ngx_queue_insert_tail(&ctx->frames, &f->queue); } -- cgit v1.2.3 From 1a0888aef9c1e18526868f4100579b2a44af5fbb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 29 Oct 2020 21:50:19 +0000 Subject: QUIC: passing ssl_conn to SSL_get0_alpn_selected() directly. No functional changes. --- src/event/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ab5d0c188..3a4ce31c1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -759,7 +759,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, unsigned int len; const unsigned char *data; - SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + SSL_get0_alpn_selected(ssl_conn, &data, &len); if (len == 0) { qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; -- cgit v1.2.3 From 8ed020db75c98361588fa65b71629304a77d0d13 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 29 Oct 2020 21:50:49 +0000 Subject: QUIC: refactored SSL_do_handshake() handling. No functional changes. --- src/event/ngx_event_quic.c | 80 ++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3a4ce31c1..7962c9d10 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3581,9 +3581,14 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) n = SSL_do_handshake(ssl_conn); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - if (n == -1) { + if (n <= 0) { sslerr = SSL_get_error(ssl_conn, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", @@ -3594,54 +3599,53 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) return NGX_ERROR; } - } else if (n == 1 && !SSL_in_init(ssl_conn)) { + return NGX_OK; + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); + if (SSL_in_init(ssl_conn)) { + return NGX_OK; + } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake completed successfully"); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - c->ssl->handshaked = 1; - c->ssl->no_wait_shutdown = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handshake completed successfully"); - frame = ngx_quic_alloc_frame(c, 0); - if (frame == NULL) { - return NGX_ERROR; - } + c->ssl->handshaked = 1; + c->ssl->no_wait_shutdown = 1; - /* 12.4 Frames and frame types, figure 8 */ - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(c->quic, frame); + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } - if (ngx_quic_send_new_token(c) != NGX_OK) { - return NGX_ERROR; - } + /* 12.4 Frames and frame types, figure 8 */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(c->quic, frame); - /* - * Generating next keys before a key update is received. - * See quic-tls 9.4 Header Protection Timing Side-Channels. - */ + if (ngx_quic_send_new_token(c) != NGX_OK) { + return NGX_ERROR; + } - if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application], - &c->quic->next_key) - != NGX_OK) - { - return NGX_ERROR; - } + /* + * Generating next keys before a key update is received. + * See quic-tls 9.4 Header Protection Timing Side-Channels. + */ - /* - * 4.10.2 An endpoint MUST discard its handshake keys - * when the TLS handshake is confirmed - */ - ngx_quic_discard_ctx(c, ssl_encryption_handshake); + if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application], + &c->quic->next_key) + != NGX_OK) + { + return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); + /* + * 4.10.2 An endpoint MUST discard its handshake keys + * when the TLS handshake is confirmed + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); return NGX_OK; } -- cgit v1.2.3 From 0aef8438f4d0c49351205f4e32fd9237b5494064 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 2 Nov 2020 18:21:34 +0300 Subject: QUIC: refactored key handling. All key handling functionality is moved into ngx_quic_protection.c. Public structures from ngx_quic_protection.h are now private and new methods are available to manipulate keys. A negotiated cipher is cached in QUIC connection from the set secret callback to avoid calling SSL_get_current_cipher() on each encrypt/decrypt operation. This also reduces the number of unwanted c->ssl->connection occurrences. --- src/event/ngx_event_quic.c | 126 +++++++++-------------- src/event/ngx_event_quic.h | 3 + src/event/ngx_event_quic_protection.c | 184 +++++++++++++++++++++++----------- src/event/ngx_event_quic_protection.h | 41 +++----- src/event/ngx_event_quic_transport.h | 4 +- 5 files changed, 191 insertions(+), 167 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7962c9d10..1962019d5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -94,9 +94,6 @@ typedef struct { * are also Initial packets. */ typedef struct { - ngx_quic_secret_t client_secret; - ngx_quic_secret_t server_secret; - enum ssl_encryption_level_t level; uint64_t pnum; /* to be sent */ @@ -134,10 +131,11 @@ struct ngx_quic_connection_s { ngx_quic_tp_t ctp; ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; - ngx_quic_secrets_t keys[NGX_QUIC_ENCRYPTION_LAST]; - ngx_quic_secrets_t next_key; + ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_keys_t *keys; + ngx_quic_conf_t *conf; ngx_event_t push; @@ -643,8 +641,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *rsecret, size_t secret_len) { - ngx_connection_t *c; - ngx_quic_secrets_t *keys; + ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -654,11 +651,8 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); #endif - keys = &c->quic->keys[level]; - - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - rsecret, secret_len, - &keys->client); + return ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level, + cipher, rsecret, secret_len); } @@ -667,8 +661,7 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *wsecret, size_t secret_len) { - ngx_connection_t *c; - ngx_quic_secrets_t *keys; + ngx_connection_t *c; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); @@ -678,11 +671,8 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif - keys = &c->quic->keys[level]; - - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - wsecret, secret_len, - &keys->server); + return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level, + cipher, wsecret, secret_len); } #else @@ -692,25 +682,24 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *rsecret, const uint8_t *wsecret, size_t secret_len) { - ngx_int_t rc; - ngx_connection_t *c; - ngx_quic_secrets_t *keys; + ngx_connection_t *c; + const SSL_CIPHER *cipher; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_encryption_secrets() level:%d", level); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic read", rsecret, secret_len); + ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); #endif - keys = &c->quic->keys[level]; + cipher = SSL_get_current_cipher(ssl_conn); - rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - rsecret, secret_len, - &keys->client); - if (rc != 1) { - return rc; + if (ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level, + cipher, rsecret, secret_len) + != 1) + { + return 0; } if (level == ssl_encryption_early_data) { @@ -718,12 +707,11 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic write", wsecret, secret_len); + ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif - return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level, - wsecret, secret_len, - &keys->server); + return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level, + cipher, wsecret, secret_len); } #endif @@ -965,6 +953,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } + qc->keys = ngx_quic_keys_new(c->pool); + if (qc->keys == NULL) { + return NULL; + } + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -1239,7 +1232,7 @@ ngx_quic_send_retry(ngx_connection_t *c) res.data = buf; - if (ngx_quic_encrypt(&pkt, NULL, &res) != NGX_OK) { + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { return NGX_ERROR; } @@ -1990,8 +1983,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_secrets_t *keys, *next, tmp; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -2135,26 +2126,19 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, c->log->action = "decrypting packet"; - keys = &qc->keys[pkt->level]; - - if (keys->client.key.len == 0) { + if (!ngx_quic_keys_available(qc->keys, pkt->level)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic no level %d keys yet, ignoring packet", pkt->level); return NGX_DECLINED; } - next = &qc->next_key; - - pkt->secret = &keys->client; - pkt->next = &next->client; + pkt->keys = qc->keys; pkt->key_phase = qc->key_phase; pkt->plaintext = buf; ctx = ngx_quic_get_send_ctx(qc, pkt->level); - ssl_conn = c->ssl ? c->ssl->connection : NULL; - - rc = ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn); + rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); if (rc != NGX_OK) { qc->error = pkt->error; qc->error_reason = "failed to decrypt packet"; @@ -2190,44 +2174,32 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return ngx_quic_payload_handler(c, pkt); } - /* switch keys on Key Phase change */ + if (!pkt->key_update) { + return ngx_quic_payload_handler(c, pkt); + } - if (pkt->key_update) { - qc->key_phase ^= 1; + /* switch keys and generate next on Key Phase change */ - tmp = *keys; - *keys = *next; - *next = tmp; - } + qc->key_phase ^= 1; + ngx_quic_keys_switch(c, qc->keys); rc = ngx_quic_payload_handler(c, pkt); if (rc != NGX_OK) { return rc; } - /* generate next keys */ - - if (pkt->key_update) { - if (ngx_quic_key_update(c, keys, next) != NGX_OK) { - return NGX_ERROR; - } - } - - return NGX_OK; + return ngx_quic_keys_update(c, qc->keys); } static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c) { - ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; - qc =c->quic; - keys = &qc->keys[ssl_encryption_initial]; + qc = c->quic; - if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server, - &qc->odcid) + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) != NGX_OK) { return NGX_ERROR; @@ -2249,11 +2221,12 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) qc = c->quic; - if (qc->keys[level].client.key.len == 0) { + if (!ngx_quic_keys_available(qc->keys, level)) { return; } - qc->keys[level].client.key.len = 0; + ngx_quic_keys_discard(qc->keys, level); + qc->pto_count = 0; ctx = ngx_quic_get_send_ctx(qc, level); @@ -3634,10 +3607,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) * See quic-tls 9.4 Header Protection Timing Side-Channels. */ - if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application], - &c->quic->next_key) - != NGX_OK) - { + if (ngx_quic_keys_update(c, c->quic->keys) != NGX_OK) { return NGX_ERROR; } @@ -4503,10 +4473,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_str_t out, res; ngx_msec_t now; ngx_queue_t *q; - ngx_ssl_conn_t *ssl_conn; ngx_quic_frame_t *f, *start; ngx_quic_header_t pkt; - ngx_quic_secrets_t *keys; ngx_quic_connection_t *qc; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; @@ -4514,8 +4482,6 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_send_frames"); - ssl_conn = c->ssl ? c->ssl->connection : NULL; - q = ngx_queue_head(frames); start = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -4554,9 +4520,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, qc = c->quic; - keys = &c->quic->keys[start->level]; - - pkt.secret = &keys->server; + pkt.keys = qc->keys; pkt.flags = NGX_QUIC_PKT_FIXED_BIT; @@ -4603,7 +4567,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_level_name(start->level), out.len, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); - if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) { + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { ngx_quic_free_frames(c, frames); return NGX_ERROR; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index cb9fbb35c..9078b6026 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -116,6 +116,9 @@ struct ngx_quic_stream_s { }; +typedef struct ngx_quic_keys_s ngx_quic_keys_t; + + void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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, diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index e59af9b81..a5c73f3a6 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -24,6 +24,7 @@ #define ngx_quic_cipher_t EVP_CIPHER #endif + typedef struct { const ngx_quic_cipher_t *c; const EVP_CIPHER *hp; @@ -31,6 +32,27 @@ typedef struct { } ngx_quic_ciphers_t; +typedef struct ngx_quic_secret_s { + ngx_str_t secret; + ngx_str_t key; + ngx_str_t iv; + ngx_str_t hp; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + + static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len); @@ -41,7 +63,7 @@ static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, uint64_t *largest_pn); static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); -static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, +static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, @@ -56,30 +78,21 @@ static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, - ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); + ngx_str_t *res); static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, - ngx_ssl_conn_t *ssl_conn, ngx_str_t *res); + ngx_str_t *res); static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res); static ngx_int_t -ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, +ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level) { - ngx_int_t id, len; - const SSL_CIPHER *cipher; + ngx_int_t len; if (level == ssl_encryption_initial) { id = NGX_AES_128_GCM_SHA256; - - } else { - cipher = SSL_get_current_cipher(ssl_conn); - if (cipher == NULL) { - return NGX_ERROR; - } - - id = SSL_CIPHER_get_id(cipher) & 0xffff; } switch (id) { @@ -130,14 +143,15 @@ ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers, ngx_int_t -ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, - ngx_quic_secret_t *server, ngx_str_t *secret) +ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, + ngx_str_t *secret) { - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + ngx_quic_secret_t *client, *server; static const uint8_t salt[20] = #if (NGX_QUIC_DRAFT_VERSION >= 29) @@ -148,6 +162,9 @@ ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client, "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; #endif + client = &keys->secrets[ssl_encryption_initial].client; + server = &keys->secrets[ssl_encryption_initial].server; + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ cipher = EVP_aes_128_gcm(); @@ -626,16 +643,25 @@ failed: } -int -ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *secret, - size_t secret_len, ngx_quic_secret_t *peer_secret) +int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) { - ngx_int_t key_len; - ngx_uint_t i; - ngx_quic_ciphers_t ciphers; + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = is_write ? &keys->secrets[level].server + : &keys->secrets[level].client; + + /* + * SSL_CIPHER_get_protocol_id() is not universally available, + * casting to uint16_t works for both OpenSSL and BoringSSL + */ + keys->cipher = (uint16_t) SSL_CIPHER_get_id(cipher); - key_len = ngx_quic_ciphers(ssl_conn, &ciphers, level); + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); if (key_len == NGX_ERROR) { ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); @@ -682,17 +708,56 @@ ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, } +ngx_quic_keys_t * +ngx_quic_keys_new(ngx_pool_t *pool) +{ + return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t)); +} + + +ngx_uint_t +ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + return keys->secrets[level].client.key.len != 0; +} + + +void +ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + keys->secrets[level].client.key.len = 0; +} + + +void +ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_quic_secrets_t *current, *next, tmp; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + tmp = *current; + *current = *next; + *next = tmp; +} + + ngx_int_t -ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, - ngx_quic_secrets_t *next) +ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) { - ngx_uint_t i; - ngx_quic_ciphers_t ciphers; + ngx_uint_t i; + ngx_quic_ciphers_t ciphers; + ngx_quic_secrets_t *current, *next; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); - if (ngx_quic_ciphers(c->ssl->connection, &ciphers, - ssl_encryption_application) + if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) == NGX_ERROR) { return NGX_ERROR; @@ -760,12 +825,12 @@ ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current, static ngx_int_t -ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - ngx_str_t *res) +ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_str_t *res) { u_char *pnp, *sample; ngx_str_t ad, out; ngx_uint_t i; + ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; u_char nonce[12], mask[16]; @@ -780,14 +845,17 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif - if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { return NGX_ERROR; } - ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); - if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, + if (ngx_quic_tls_seal(ciphers.c, secret, &out, nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) { @@ -795,7 +863,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } sample = &out.data[4 - pkt->num_len]; - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { return NGX_ERROR; @@ -815,12 +883,12 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, static ngx_int_t -ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - ngx_str_t *res) +ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_str_t *res) { u_char *pnp, *sample; ngx_str_t ad, out; ngx_uint_t i; + ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; u_char nonce[12], mask[16]; @@ -835,14 +903,17 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); #endif - if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { return NGX_ERROR; } - ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len); + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); - if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out, + if (ngx_quic_tls_seal(ciphers.c, secret, &out, nonce, &pkt->payload, &ad, pkt->log) != NGX_OK) { @@ -850,7 +921,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, } sample = &out.data[4 - pkt->num_len]; - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample) + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) != NGX_OK) { return NGX_ERROR; @@ -902,7 +973,7 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len); #endif - if (ngx_quic_ciphers(NULL, &ciphers, pkt->level) == NGX_ERROR) { + if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { return NGX_ERROR; } @@ -1033,24 +1104,22 @@ ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) ngx_int_t -ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - ngx_str_t *res) +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) { if (ngx_quic_short_pkt(pkt->flags)) { - return ngx_quic_create_short_packet(pkt, ssl_conn, res); + return ngx_quic_create_short_packet(pkt, res); } if (ngx_quic_pkt_retry(pkt->flags)) { return ngx_quic_create_retry_packet(pkt, res); } - return ngx_quic_create_long_packet(pkt, ssl_conn, res); + return ngx_quic_create_long_packet(pkt, res); } ngx_int_t -ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - uint64_t *largest_pn) +ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) { u_char clearflags, *p, *sample; size_t len; @@ -1062,11 +1131,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t ciphers; uint8_t mask[16], nonce[12]; - if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) { + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { return NGX_ERROR; } - secret = pkt->secret; + secret = &pkt->keys->secrets[pkt->level].client; p = pkt->raw->pos; len = pkt->data + pkt->len - p; @@ -1099,7 +1169,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0; if (key_phase != pkt->key_phase) { - secret = pkt->next; + secret = &pkt->keys->next_key.client; pkt->key_update = 1; } } diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h index b7916a498..4e39ea57a 100644 --- a/src/event/ngx_event_quic_protection.h +++ b/src/event/ngx_event_quic_protection.h @@ -15,37 +15,24 @@ #define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) -typedef struct ngx_quic_secret_s { - ngx_str_t secret; - ngx_str_t key; - ngx_str_t iv; - ngx_str_t hp; -} ngx_quic_secret_t; +ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, + ngx_quic_keys_t *keys, ngx_str_t *secret); +int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); - -typedef struct { - ngx_quic_secret_t client; - ngx_quic_secret_t server; -} ngx_quic_secrets_t; - - -ngx_int_t ngx_quic_set_initial_secret(ngx_pool_t *pool, - ngx_quic_secret_t *client, ngx_quic_secret_t *server, - ngx_str_t *secret); - -int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len, - ngx_quic_secret_t *peer_secret); - -ngx_int_t ngx_quic_key_update(ngx_connection_t *c, - ngx_quic_secrets_t *current, ngx_quic_secrets_t *next); ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *key, u_char *token); -ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - ngx_str_t *res); -ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn, - uint64_t *largest_pn); +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 336da9d0b..988790c8c 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -290,8 +290,8 @@ struct ngx_quic_frame_s { typedef struct { ngx_log_t *log; - struct ngx_quic_secret_s *secret; - struct ngx_quic_secret_s *next; + ngx_quic_keys_t *keys; + ngx_msec_t received; uint64_t number; uint8_t num_len; -- cgit v1.2.3 From 609af6e31d5a74b4a59f2b769fe75b2ab736433e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 2 Nov 2020 17:38:11 +0000 Subject: QUIC: fixed address validation issues in a new connection. The client address validation didn't complete with a valid token, which was broken after packet processing refactoring in d0d3fc0697a0. An invalid or expired token was treated as a connection error. Now we proceed as outlined in draft-ietf-quic-transport-32, section 8.1.3 "Address Validation for Future Connections" below, which is unlike validating the client address using Retry packets. When a server receives an Initial packet with an address validation token, it MUST attempt to validate the token, unless it has already completed address validation. If the token is invalid then the server SHOULD proceed as if the client did not have a validated address, including potentially sending a Retry. The connection is now closed in this case on internal errors only. --- src/event/ngx_event_quic.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 1962019d5..9ff09cb05 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1479,7 +1479,7 @@ bad_token: qc->error = NGX_QUIC_ERR_INVALID_TOKEN; qc->error_reason = "invalid_token"; - return NGX_ERROR; + return NGX_DECLINED; } @@ -2104,8 +2104,19 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->token.len) { - if (ngx_quic_validate_token(c, pkt) != NGX_OK) { + rc = ngx_quic_validate_token(c, pkt); + + if (rc == NGX_OK) { + qc->validated = 1; + + } else if (rc == NGX_ERROR) { return NGX_ERROR; + + } else { + /* NGX_DECLINED */ + if (conf->retry) { + return ngx_quic_send_retry(c); + } } } else if (conf->retry) { -- cgit v1.2.3 From b874b822e1270cb19487cffdd7d0f00080f95b94 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 6 Nov 2020 18:21:31 +0300 Subject: QUIC: added proper logging of special values. A number of unsigned variables has a special value, usually -1 or some maximum, which produces huge numeric value in logs and makes them hard to read. In order to distinguish such values in log, they are casted to the signed type and printed as literal '-1'. --- src/event/ngx_event_quic.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 9ff09cb05..60f22b026 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1020,7 +1020,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, ngx_max(2 * qc->tp.max_udp_payload_size, 14720)); - qc->congestion.ssthresh = NGX_MAX_SIZE_T_VALUE; + qc->congestion.ssthresh = (size_t) -1; qc->congestion.recovery_start = ngx_current_msec; if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) { @@ -2572,8 +2572,8 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_ack_packet pn:%uL largest %uL fr:%uL" - " nranges:%ui", pkt->pn, ctx->largest_range, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, ctx->first_range, ctx->nranges); prev_pending = ctx->pending_ack; @@ -5710,7 +5710,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion ack recovery win:%uz ss:%uz if:%uz", + "quic congestion ack recovery win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); return; @@ -5720,14 +5720,14 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) cg->window += f->plen; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion slow start win:%uz ss:%uz if:%uz", + "quic congestion slow start win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); } else { cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion avoidance win:%uz ss:%uz if:%uz", + "quic congestion avoidance win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); } @@ -5762,7 +5762,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) if ((ngx_msec_int_t) timer <= 0) { ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost recovery win:%uz ss:%uz if:%uz", + "quic congestion lost recovery win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); return; @@ -5778,7 +5778,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) cg->ssthresh = cg->window; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost win:%uz ss:%uz if:%uz", + "quic congestion lost win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); } -- cgit v1.2.3 From 66cb03f003559fbf1cbf9a0654fde4d3201a7842 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Nov 2020 00:20:44 +0300 Subject: QUIC: preparatory changes for multiple QUIC versions support. A negotiated version is decoupled from NGX_QUIC_VERSION and, if supported, now stored in c->quic->version after packets processing. It is then used to create long header packets. Otherwise, the list of supported versions (which may be many now) is sent in the Version Negotiation packet. All packets in the connection are expected to have the same version. Incoming packets with mismatched version are now rejected. --- src/event/ngx_event_quic.c | 13 +++++++++++++ src/event/ngx_event_quic_transport.c | 22 +++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 60f22b026..4b0050d07 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -115,6 +115,7 @@ typedef struct { struct ngx_quic_connection_s { + uint32_t version; ngx_str_t scid; /* initial client ID */ ngx_str_t dcid; /* server (our own) ID */ ngx_str_t odcid; /* original server ID */ @@ -958,6 +959,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } + qc->version = pkt->version; + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, ngx_quic_rbtree_insert_stream); @@ -1224,6 +1227,7 @@ ngx_quic_send_retry(ngx_connection_t *c) ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = c->quic->version; pkt.log = c->log; pkt.odcid = c->quic->odcid; pkt.dcid = c->quic->scid; @@ -2020,6 +2024,14 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } + if (pkt->level != ssl_encryption_application) { + if (pkt->version != qc->version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic version mismatch: 0x%xD", pkt->version); + return NGX_DECLINED; + } + } + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { if (pkt->level == ssl_encryption_application) { @@ -4549,6 +4561,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_set_packet_number(&pkt, ctx); + pkt.version = qc->version; pkt.log = c->log; pkt.level = start->level; pkt.dcid = qc->scid; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 26cc690bf..4248e867a 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -72,6 +72,7 @@ static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len); static ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); @@ -268,7 +269,7 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) return NGX_DECLINED; } - if (pkt->version != NGX_QUIC_VERSION) { + if (!ngx_quic_supported_version(pkt->version)) { return NGX_ABORT; } @@ -430,7 +431,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, *p++ = pkt->flags; - p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION); + p = ngx_quic_write_uint32(p, pkt->version); *p++ = pkt->dcid.len; p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); @@ -517,7 +518,7 @@ ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, *p++ = 0xff; - p = ngx_quic_write_uint32(p, NGX_QUIC_VERSION); + p = ngx_quic_write_uint32(p, pkt->version); *p++ = pkt->dcid.len; p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); @@ -651,6 +652,21 @@ ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + #define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) #define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) #define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) -- cgit v1.2.3 From 7f434603875b35998f09e43e8ed27381bb8094cb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Nov 2020 00:23:04 +0300 Subject: QUIC: multiple versions support. Draft-29 and beyond are now treated as compatible versions. --- src/event/ngx_event_quic.c | 4 +--- src/event/ngx_event_quic.h | 1 - src/event/ngx_event_quic_transport.c | 15 +++++++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 4b0050d07..a7e474803 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1163,9 +1163,7 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) { size_t len; ngx_quic_header_t pkt; - - /* buffer size is calculated assuming a single supported version */ - static u_char buf[NGX_QUIC_MAX_LONG_HEADER + sizeof(uint32_t)]; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "sending version negotiation packet"); diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 9078b6026..e8bf926a0 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -16,7 +16,6 @@ #ifndef NGX_QUIC_DRAFT_VERSION #define NGX_QUIC_DRAFT_VERSION 29 #endif -#define NGX_QUIC_VERSION (0xff000000 + NGX_QUIC_DRAFT_VERSION) #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ #define NGX_QUIC_MAX_LONG_HEADER 56 diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 4248e867a..98c178ba0 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -56,6 +56,8 @@ : ((uint64_t) value) <= 1073741823 ? 4 \ : 8) +#define NGX_QUIC_VERSION(c) (0xff000000 + (c)) + static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); static void ngx_quic_build_int(u_char **pos, uint64_t value); @@ -101,9 +103,18 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ngx_quic_tp_t *dst); -/* currently only single version (selected at compile-time) is supported */ uint32_t ngx_quic_versions[] = { - NGX_QUIC_VERSION +#if (NGX_QUIC_DRAFT_VERSION >= 29) + /* pretend we support all versions in range draft-29..v1 */ + NGX_QUIC_VERSION(29), + NGX_QUIC_VERSION(30), + NGX_QUIC_VERSION(31), + NGX_QUIC_VERSION(32), + /* QUICv1 */ + 0x00000001 +#else + NGX_QUIC_VERSION(NGX_QUIC_DRAFT_VERSION) +#endif }; #define NGX_QUIC_NVERSIONS \ -- cgit v1.2.3 From b19923f91bd41f17470c0d4538ba15adcc0b95e8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Nov 2020 00:32:56 +0300 Subject: QUIC: multiple versions support in ALPN. Previously, a version based on NGX_QUIC_DRAFT_VERSION was always set. Now it is taken from the negotiated QUIC version that may differ. --- src/event/ngx_event_quic.c | 11 +++++++++++ src/event/ngx_event_quic.h | 1 + src/http/modules/ngx_http_quic_module.h | 5 ++--- src/http/modules/ngx_http_ssl_module.c | 35 +++++++++++++++++++++++++-------- src/http/v3/ngx_http_v3.h | 5 ++--- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a7e474803..49ed8f7ba 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -5813,3 +5813,14 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) "quic free frame n:%ui", qc->nframes); #endif } + + +uint32_t +ngx_quic_version(ngx_connection_t *c) +{ + uint32_t version; + + version = c->quic->version; + + return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; +} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index e8bf926a0..fc41e0ce6 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -122,6 +122,7 @@ void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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); +uint32_t ngx_quic_version(ngx_connection_t *c); /********************************* DEBUG *************************************/ diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h index e744eb197..bd4930f8a 100644 --- a/src/http/modules/ngx_http_quic_module.h +++ b/src/http/modules/ngx_http_quic_module.h @@ -14,9 +14,8 @@ #include -#define NGX_HTTP_QUIC_ALPN(s) NGX_HTTP_QUIC_ALPN_DRAFT(s) -#define NGX_HTTP_QUIC_ALPN_DRAFT(s) "\x05hq-" #s -#define NGX_HTTP_QUIC_ALPN_ADVERTISE NGX_HTTP_QUIC_ALPN(NGX_QUIC_DRAFT_VERSION) +#define NGX_HTTP_QUIC_ALPN_ADVERTISE "\x02hq" +#define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" extern ngx_module_t ngx_http_quic_module; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index a2db307f7..111de479b 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -418,6 +418,9 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { +#if (NGX_HTTP_QUIC) + const char *fmt; +#endif unsigned int srvlen; unsigned char *srv; #if (NGX_DEBUG) @@ -452,16 +455,32 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } else #endif -#if (NGX_HTTP_V3) - if (hc->addr_conf->http3) { - srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1; - } else -#endif #if (NGX_HTTP_QUIC) if (hc->addr_conf->quic) { - srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1; +#if (NGX_HTTP_V3) + if (hc->addr_conf->http3) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1; + fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; + + } else +#endif + { + srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE; + srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1; + fmt = NGX_HTTP_QUIC_ALPN_DRAFT_FMT; + } + + /* QUIC draft */ + + if (ngx_quic_version(c) > 1) { + srv = ngx_pnalloc(c->pool, sizeof("\x05h3-xx") - 1); + if (srv == NULL) { + return SSL_TLSEXT_ERR_NOACK; + } + srvlen = ngx_sprintf(srv, fmt, ngx_quic_version(c)) - srv; + } + } else #endif { diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index aab27b3ac..c244ab861 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -15,9 +15,8 @@ #include -#define NGX_HTTP_V3_ALPN(s) NGX_HTTP_V3_ALPN_DRAFT(s) -#define NGX_HTTP_V3_ALPN_DRAFT(s) "\x05h3-" #s -#define NGX_HTTP_V3_ALPN_ADVERTISE NGX_HTTP_V3_ALPN(NGX_QUIC_DRAFT_VERSION) +#define NGX_HTTP_V3_ALPN_ADVERTISE "\x02h3" +#define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 -- cgit v1.2.3 From d889cff0e57f7a6d5019ef808aaddb54edf95cee Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 9 Nov 2020 18:58:29 +0000 Subject: QUIC: renamed field and function related to client ids. Particularly, c->curr_seqnum is renamed to c->client_seqnum and ngx_quic_alloc_connection_id() is renamed to ngx_quic_alloc_client_id(). --- src/event/ngx_event_quic.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 49ed8f7ba..3d3cc43c1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -125,7 +125,7 @@ struct ngx_quic_connection_s { ngx_queue_t free_client_ids; ngx_uint_t nclient_ids; uint64_t max_retired_seqnum; - uint64_t curr_seqnum; + uint64_t client_seqnum; ngx_uint_t client_tp_done; ngx_quic_tp_t tp; @@ -304,7 +304,7 @@ static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum); -static ngx_quic_client_id_t *ngx_quic_alloc_connection_id(ngx_connection_t *c, +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, @@ -1042,7 +1042,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - cid = ngx_quic_alloc_connection_id(c, qc); + cid = ngx_quic_alloc_client_id(c, qc); if (cid == NULL) { return NULL; } @@ -1053,7 +1053,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_queue_insert_tail(&qc->client_ids, &cid->queue); qc->nclient_ids++; - qc->curr_seqnum = 0; + qc->client_seqnum = 0; return qc; } @@ -4165,7 +4165,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, } else { - cid = ngx_quic_alloc_connection_id(c, qc); + cid = ngx_quic_alloc_client_id(c, qc); if (cid == NULL) { return NGX_ERROR; } @@ -4180,10 +4180,10 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, qc->nclient_ids++; /* always use latest available connection id */ - if (f->seqnum > qc->curr_seqnum) { + if (f->seqnum > qc->client_seqnum) { qc->scid.len = cid->len; qc->scid.data = cid->id; - qc->curr_seqnum = f->seqnum; + qc->client_seqnum = f->seqnum; } } @@ -4266,7 +4266,7 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, static ngx_quic_client_id_t * -ngx_quic_alloc_connection_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) { ngx_queue_t *q; ngx_quic_client_id_t *cid; -- cgit v1.2.3 From 1be6d80089335f5f8635230f50a0ef4173ed9593 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 11 Nov 2020 11:57:50 +0000 Subject: QUIC: connection multiplexing per port. Also, connection migration within a single worker is implemented. --- src/event/ngx_event.h | 14 ++ src/event/ngx_event_quic.c | 377 +++++++++++++++++++++++++++----- src/event/ngx_event_quic.h | 6 +- src/event/ngx_event_quic_transport.c | 89 ++++++++ src/event/ngx_event_udp.c | 143 +++++++++--- src/http/modules/ngx_http_quic_module.c | 10 +- src/stream/ngx_stream_quic_module.c | 10 +- 7 files changed, 549 insertions(+), 100 deletions(-) diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index 97f9673c9..17e9d58d0 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -167,6 +167,18 @@ struct ngx_event_aio_s { #endif +#if !(NGX_WIN32) + +struct ngx_udp_connection_s { + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_str_t key; + ngx_buf_t *buffer; +}; + +#endif + + typedef struct { ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); @@ -501,6 +513,8 @@ void ngx_event_accept(ngx_event_t *ev); void ngx_event_recvmsg(ngx_event_t *ev); void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, + ngx_str_t *key); #endif void ngx_delete_udp_connection(void *data); ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle); diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 3d3cc43c1..49c99095d 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -121,11 +121,18 @@ struct ngx_quic_connection_s { ngx_str_t odcid; /* original server ID */ ngx_str_t token; + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_queue_t client_ids; + ngx_queue_t server_ids; ngx_queue_t free_client_ids; + ngx_queue_t free_server_ids; ngx_uint_t nclient_ids; + ngx_uint_t nserver_ids; uint64_t max_retired_seqnum; uint64_t client_seqnum; + uint64_t server_seqnum; ngx_uint_t client_tp_done; ngx_quic_tp_t tp; @@ -185,6 +192,15 @@ typedef struct { } ngx_quic_client_id_t; +typedef struct { + ngx_udp_connection_t udp; + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +} ngx_quic_server_id_t; + + typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); @@ -217,8 +233,7 @@ static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); -static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_str_t *odcid); +static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, @@ -304,8 +319,16 @@ static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum); +static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); +static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); +static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); +static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, + ngx_str_t *id); static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); +static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); @@ -439,7 +462,8 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) break; case NGX_QUIC_FT_NEW_CONNECTION_ID: - p = ngx_slprintf(p, last, "NCID seq:%uL retire:%uL len:%ud", + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); break; @@ -983,7 +1007,9 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_queue_init(&qc->free_frames); ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->server_ids); ngx_queue_init(&qc->free_client_ids); + ngx_queue_init(&qc->free_server_ids); qc->avg_rtt = NGX_QUIC_INITIAL_RTT; qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; @@ -992,6 +1018,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, /* * qc->latest_rtt = 0 * qc->nclient_ids = 0 + * qc->nserver_ids = 0 * qc->max_retired_seqnum = 0 */ @@ -1010,6 +1037,16 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->conf = conf; qc->tp = conf->tp; + if (qc->tp.disable_active_migration) { + qc->sockaddr = ngx_palloc(c->pool, c->socklen); + if (qc->sockaddr == NULL) { + return NULL; + } + + ngx_memcpy(qc->sockaddr, c->sockaddr, c->socklen); + qc->socklen = c->socklen; + } + ctp = &qc->ctp; ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; @@ -1026,7 +1063,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->congestion.ssthresh = (size_t) -1; qc->congestion.recovery_start = ngx_current_msec; - if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) { + qc->odcid.len = pkt->dcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->odcid.data == NULL) { + return NULL; + } + + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NULL; + } + + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { return NULL; } @@ -1055,6 +1104,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->nclient_ids++; qc->client_seqnum = 0; + qc->server_seqnum = NGX_QUIC_UNSET_PN; + return qc; } @@ -1186,26 +1237,14 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) static ngx_int_t -ngx_quic_new_dcid(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_str_t *odcid) +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) { - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, NGX_QUIC_SERVER_CID_LEN); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - - if (RAND_bytes(qc->dcid.data, NGX_QUIC_SERVER_CID_LEN) != 1) { + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { return NGX_ERROR; } - ngx_quic_hexdump(c->log, "quic server CID", qc->dcid.data, qc->dcid.len); - - qc->odcid.len = odcid->len; - qc->odcid.data = ngx_pstrdup(c->pool, odcid); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } + ngx_quic_hexdump(c->log, "quic create server id", + id, NGX_QUIC_SERVER_CID_LEN); return NGX_OK; } @@ -1254,6 +1293,10 @@ ngx_quic_send_retry(ngx_connection_t *c) c->quic->tp.retry_scid = c->quic->dcid; c->quic->in_retry = 1; + if (ngx_quic_insert_server_id(c, &c->quic->dcid) == NULL) { + return NGX_ERROR; + } + return NGX_OK; } @@ -1629,6 +1672,16 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } + if (qc->tp.disable_active_migration) { + if (c->socklen != qc->socklen + || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0) + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic dropping packet from new address"); + return; + } + } + b.last += n; qc->received += n; @@ -1694,7 +1747,9 @@ static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { ngx_uint_t i; + ngx_queue_t *q; ngx_quic_send_ctx_t *ctx; + ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; qc = c->quic; @@ -1801,6 +1856,15 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } + while (!ngx_queue_empty(&qc->server_ids)) { + q = ngx_queue_head(&qc->server_ids); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + ngx_queue_remove(q); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + } + if (qc->close.timer_set) { return NGX_AGAIN; } @@ -2065,7 +2129,21 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } - if (ngx_quic_new_dcid(c, qc, &pkt->dcid) != NGX_OK) { + qc->odcid.len = pkt->dcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->odcid.data == NULL) { + return NGX_ERROR; + } + + ngx_quic_clear_temp_server_ids(c); + + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + return NGX_ERROR; + } + + qc->server_seqnum = 0; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { return NGX_ERROR; } @@ -2137,6 +2215,16 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } + if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { + return NGX_ERROR; + } + + qc->server_seqnum = 0; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + return NGX_ERROR; + } + } else if (pkt->level == ssl_encryption_application) { return ngx_quic_send_stateless_reset(c, conf, pkt); @@ -2270,6 +2358,10 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_free_frame(c, f); } + if (level == ssl_encryption_initial) { + ngx_quic_clear_temp_server_ids(c); + } + ctx->send_ack = 0; } @@ -2277,44 +2369,13 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { - ngx_str_t *dcid; ngx_queue_t *q; - ngx_quic_send_ctx_t *ctx; ngx_quic_client_id_t *cid; - dcid = (pkt->level == ssl_encryption_early_data) ? &qc->odcid : &qc->dcid; - - if (pkt->dcid.len == dcid->len - && ngx_memcmp(pkt->dcid.data, dcid->data, dcid->len) == 0) - { - if (pkt->level == ssl_encryption_application) { - return NGX_OK; - } - - goto found; - } - - /* - * a packet sent in response to an initial client packet might be lost, - * thus check also for old dcid - */ - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - - if (pkt->level == ssl_encryption_initial - && ctx->largest_ack == NGX_QUIC_UNSET_PN) - { - if (pkt->dcid.len == qc->odcid.len - && ngx_memcmp(pkt->dcid.data, qc->odcid.data, qc->odcid.len) == 0) - { - goto found; - } + if (pkt->level == ssl_encryption_application) { + return NGX_OK; } - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic dcid"); - return NGX_ERROR; - -found: - for (q = ngx_queue_head(&qc->client_ids); q != ngx_queue_sentinel(&qc->client_ids); q = ngx_queue_next(q)) @@ -2533,6 +2594,16 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) break; case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (ngx_quic_handle_retire_connection_id_frame(c, pkt, + &frame.u.retire_cid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_PATH_RESPONSE: /* TODO: handle */ @@ -3638,6 +3709,10 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) */ ngx_quic_discard_ctx(c, ssl_encryption_handshake); + if (ngx_quic_issue_server_ids(c) != NGX_OK) { + return NGX_ERROR; + } + return NGX_OK; } @@ -4265,6 +4340,173 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, } +static ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = c->quic; + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = ngx_queue_next(q)) + { + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum == f->sequence_number) { + ngx_queue_remove(q); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + + if (c->udp != &sid->udp) { + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + } + + break; + } + } + + return ngx_quic_issue_server_ids(c); +} + + +static ngx_int_t +ngx_quic_issue_server_ids(ngx_connection_t *c) +{ + ngx_str_t dcid; + ngx_uint_t n; + ngx_quic_frame_t *frame; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + u_char id[NGX_QUIC_SERVER_CID_LEN]; + + qc = c->quic; + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); + + while (qc->nserver_ids < n) { + if (ngx_quic_create_server_id(c, id) != NGX_OK) { + return NGX_ERROR; + } + + dcid.len = NGX_QUIC_SERVER_CID_LEN; + dcid.data = id; + + sid = ngx_quic_insert_server_id(c, &dcid); + if (sid == NULL) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(c->quic, frame); + } + + return NGX_OK; +} + + +static void +ngx_quic_clear_temp_server_ids(ngx_connection_t *c) +{ + ngx_queue_t *q, *next; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = c->quic; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear temp server ids"); + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = next) + { + next = ngx_queue_next(q); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum != NGX_QUIC_UNSET_PN) { + continue; + } + + ngx_queue_remove(q); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + + if (c->udp != &sid->udp) { + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + } + } +} + + +static ngx_quic_server_id_t * +ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) +{ + ngx_str_t dcid; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = c->quic; + + sid = ngx_quic_alloc_server_id(c, qc); + if (sid == NULL) { + return NULL; + } + + sid->seqnum = qc->server_seqnum; + + if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { + qc->server_seqnum++; + } + + sid->len = id->len; + ngx_memcpy(sid->id, id->data, id->len); + + ngx_queue_insert_tail(&qc->server_ids, &sid->queue); + qc->nserver_ids++; + + dcid.data = sid->id; + dcid.len = sid->len; + + ngx_insert_udp_connection(c, &sid->udp, &dcid); + + if (c->udp == NULL) { + c->udp = &sid->udp; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic insert server id seqnum:%uL", sid->seqnum); + + ngx_quic_hexdump(c->log, "quic server id", id->data, id->len); + + return sid; +} + + static ngx_quic_client_id_t * ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) { @@ -4292,6 +4534,33 @@ ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) } +static ngx_quic_server_id_t * +ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + + if (!ngx_queue_empty(&qc->free_server_ids)) { + + q = ngx_queue_head(&qc->free_server_ids); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + ngx_queue_remove(&sid->queue); + + ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); + + } else { + + sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); + if (sid == NULL) { + return NULL; + } + } + + return sid; +} + + static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index fc41e0ce6..4f81dbd60 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -58,6 +58,8 @@ #define NGX_QUIC_SR_TOKEN_LEN 16 +#define NGX_QUIC_MAX_SERVER_IDS 8 + typedef struct { /* configurable */ @@ -72,8 +74,8 @@ typedef struct { ngx_uint_t initial_max_streams_bidi; ngx_uint_t initial_max_streams_uni; ngx_uint_t ack_delay_exponent; - ngx_uint_t disable_active_migration; ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; ngx_str_t original_dcid; ngx_str_t initial_scid; ngx_str_t retry_scid; @@ -123,6 +125,8 @@ 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); uint32_t ngx_quic_version(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); /********************************* DEBUG *************************************/ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 98c178ba0..626da6c9e 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -10,6 +10,11 @@ #include +#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 +#define NGX_QUIC_LONG_DCID_OFFSET 6 +#define NGX_QUIC_SHORT_DCID_OFFSET 1 + + #if (NGX_HAVE_NONALIGNED) #define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) @@ -95,6 +100,8 @@ static size_t ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md); static size_t ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_new_connection_id(u_char *p, + ngx_quic_new_conn_id_frame_t *rcid); static size_t ngx_quic_create_retire_connection_id(u_char *p, ngx_quic_retire_cid_frame_t *rcid); static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); @@ -321,6 +328,46 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) } +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt) { @@ -1222,6 +1269,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_PATH_RESPONSE: return ngx_quic_create_path_response(p, &f->u.path_response); + case NGX_QUIC_FT_NEW_CONNECTION_ID: + return ngx_quic_create_new_connection_id(p, &f->u.ncid); + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); @@ -1704,6 +1754,35 @@ ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) } +static size_t +ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); + len += ngx_quic_varint_len(ncid->seqnum); + len += ngx_quic_varint_len(ncid->retire); + len++; + len += ncid->len; + len += NGX_QUIC_SR_TOKEN_LEN; + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); + ngx_quic_build_int(&p, ncid->seqnum); + ngx_quic_build_int(&p, ncid->retire); + *p++ = ncid->len; + p = ngx_cpymem(p, ncid->cid, ncid->len); + p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); + + return p - start; +} + + static size_t ngx_quic_create_retire_connection_id(u_char *p, ngx_quic_retire_cid_frame_t *rcid) @@ -1783,6 +1862,11 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, *clen = len; } + if (tp->disable_active_migration) { + len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + len += ngx_quic_varint_len(0); + } + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, tp->active_connection_id_limit); @@ -1830,6 +1914,11 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + if (tp->disable_active_migration) { + ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + ngx_quic_build_int(&p, 0); + } + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, tp->active_connection_id_limit); diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index 557283050..884b1cd51 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -12,19 +12,12 @@ #if !(NGX_WIN32) -struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_buf_t *buffer; -}; - - static void ngx_close_accepted_udp_connection(ngx_connection_t *c); static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); -static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c); +static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c); static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls, - struct sockaddr *sockaddr, socklen_t socklen, + ngx_str_t *key, struct sockaddr *sockaddr, socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen); @@ -32,6 +25,7 @@ void ngx_event_recvmsg(ngx_event_t *ev) { ssize_t n; + ngx_str_t key; ngx_buf_t buf; ngx_log_t *log; ngx_err_t err; @@ -229,8 +223,18 @@ ngx_event_recvmsg(ngx_event_t *ev) #endif - c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr, - local_socklen); + ngx_str_null(&key); + +#if (NGX_QUIC) + if (ls->quic) { + if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { + goto next; + } + } +#endif + + c = ngx_lookup_udp_connection(ls, &key, sockaddr, socklen, + local_sockaddr, local_socklen); if (c) { @@ -403,7 +407,7 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - if (ngx_insert_udp_connection(c) != NGX_OK) { + if (ngx_create_udp_connection(c) != NGX_OK) { ngx_close_accepted_udp_connection(c); return; } @@ -492,8 +496,13 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); + + if (rc == 0 && udp->key.len == 0) { + rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, + ct->sockaddr, ct->socklen, 1); + } if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -519,12 +528,18 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, static ngx_int_t -ngx_insert_udp_connection(ngx_connection_t *c) +ngx_create_udp_connection(ngx_connection_t *c) { - uint32_t hash; + ngx_str_t key; ngx_pool_cleanup_t *cln; ngx_udp_connection_t *udp; +#if (NGX_QUIC) + if (c->listening->quic) { + return NGX_OK; + } +#endif + if (c->udp) { return NGX_OK; } @@ -534,19 +549,6 @@ ngx_insert_udp_connection(ngx_connection_t *c) return NGX_ERROR; } - udp->connection = c; - - ngx_crc32_init(hash); - ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen); - - if (c->listening->wildcard) { - ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); - } - - ngx_crc32_final(hash); - - udp->node.key = hash; - cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -555,7 +557,9 @@ ngx_insert_udp_connection(ngx_connection_t *c) cln->data = c; cln->handler = ngx_delete_udp_connection; - ngx_rbtree_insert(&c->listening->rbtree, &udp->node); + key.len = 0; + + ngx_insert_udp_connection(c, udp, &key); c->udp = udp; @@ -563,6 +567,34 @@ ngx_insert_udp_connection(ngx_connection_t *c) } +void +ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, + ngx_str_t *key) +{ + uint32_t hash; + + ngx_crc32_init(hash); + + ngx_crc32_update(&hash, key->data, key->len); + + if (key->len == 0) { + ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen); + } + + if (c->listening->wildcard) { + ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); + } + + ngx_crc32_final(hash); + + udp->connection = c; + udp->key = *key; + udp->node.key = hash; + + ngx_rbtree_insert(&c->listening->rbtree, &udp->node); +} + + void ngx_delete_udp_connection(void *data) { @@ -579,8 +611,9 @@ ngx_delete_udp_connection(void *data) static ngx_connection_t * -ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, - socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen) +ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, + struct sockaddr *sockaddr, socklen_t socklen, + struct sockaddr *local_sockaddr, socklen_t local_socklen) { uint32_t hash; ngx_int_t rc; @@ -608,7 +641,12 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, sentinel = ls->rbtree.sentinel; ngx_crc32_init(hash); - ngx_crc32_update(&hash, (u_char *) sockaddr, socklen); + + ngx_crc32_update(&hash, key->data, key->len); + + if (key->len == 0) { + ngx_crc32_update(&hash, (u_char *) sockaddr, socklen); + } if (ls->wildcard) { ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen); @@ -634,8 +672,12 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, c = udp->connection; - rc = ngx_cmp_sockaddr(sockaddr, socklen, - c->sockaddr, c->socklen, 1); + rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len); + + if (rc == 0 && key->len == 0) { + rc = ngx_cmp_sockaddr(sockaddr, socklen, + c->sockaddr, c->socklen, 1); + } if (rc == 0 && ls->wildcard) { rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, @@ -643,6 +685,37 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, } if (rc == 0) { + if (key->len) { + rc = ngx_cmp_sockaddr(sockaddr, socklen, + c->sockaddr, c->socklen, 1); + + if (rc) { +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + ngx_str_t addr; + u_char text[NGX_SOCKADDR_STRLEN]; + + addr.data = text; + addr.len = ngx_sock_ntop(sockaddr, socklen, text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "client migrated to %V", &addr); + } +#endif + + if (c->socklen < socklen) { + c->sockaddr = ngx_palloc(c->pool, socklen); + if (c->sockaddr == NULL) { + return c; + } + } + + ngx_memcpy(c->sockaddr, sockaddr, socklen); + c->socklen = socklen; + } + } + return c; } diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 34898984a..3993d4692 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -104,9 +104,9 @@ static ngx_command_t ngx_http_quic_commands[] = { offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), &ngx_http_quic_ack_delay_exponent_bounds }, - { ngx_string("quic_active_migration"), + { ngx_string("quic_disable_active_migration"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_flag_slot, NGX_HTTP_SRV_CONF_OFFSET, offsetof(ngx_quic_conf_t, tp.disable_active_migration), NULL }, @@ -246,7 +246,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; - conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT; + conf->tp.disable_active_migration = NGX_CONF_UNSET; conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; @@ -301,8 +301,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->tp.ack_delay_exponent, NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - ngx_conf_merge_uint_value(conf->tp.disable_active_migration, - prev->tp.disable_active_migration, 1); + ngx_conf_merge_value(conf->tp.disable_active_migration, + prev->tp.disable_active_migration, 0); ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, prev->tp.active_connection_id_limit, 2); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index ba601a030..edf5ac704 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -105,9 +105,9 @@ static ngx_command_t ngx_stream_quic_commands[] = { offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), &ngx_stream_quic_ack_delay_exponent_bounds }, - { ngx_string("quic_active_migration"), + { ngx_string("quic_disable_active_migration"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, + ngx_conf_set_flag_slot, NGX_STREAM_SRV_CONF_OFFSET, offsetof(ngx_quic_conf_t, tp.disable_active_migration), NULL }, @@ -236,7 +236,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; - conf->tp.disable_active_migration = NGX_CONF_UNSET_UINT; + conf->tp.disable_active_migration = NGX_CONF_UNSET; conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; @@ -290,8 +290,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->tp.ack_delay_exponent, NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - ngx_conf_merge_uint_value(conf->tp.disable_active_migration, - prev->tp.disable_active_migration, 1); + ngx_conf_merge_value(conf->tp.disable_active_migration, + prev->tp.disable_active_migration, 0); ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, prev->tp.active_connection_id_limit, 2); -- cgit v1.2.3 From 4b41b1478f108800d30bff981204bb0b85fc809e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Nov 2020 18:38:42 +0000 Subject: QUIC: got rid of the c->quic field. Now QUIC connection is accessed via the c->udp field. --- src/core/ngx_connection.h | 1 - src/event/ngx_event_quic.c | 323 +++++++++++++++++++++++++-------------------- src/event/ngx_event_quic.h | 2 + 3 files changed, 179 insertions(+), 147 deletions(-) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 2ce0f153b..a075d84a3 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -151,7 +151,6 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; #if (NGX_QUIC || NGX_COMPAT) - ngx_quic_connection_t *quic; ngx_quic_stream_t *qs; #endif diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 49c99095d..c9ea2de1e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -115,6 +115,8 @@ typedef struct { struct ngx_quic_connection_s { + ngx_udp_connection_t udp; + uint32_t version; ngx_str_t scid; /* initial client ID */ ngx_str_t dcid; /* server (our own) ID */ @@ -598,7 +600,7 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) p = buf; last = p + sizeof(buf); - qc = c->quic; + qc = ngx_quic_get_connection(c); p = ngx_slprintf(p, last, "state:"); @@ -666,9 +668,11 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *rsecret, size_t secret_len) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_read_secret() level:%d", level); @@ -676,7 +680,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level, + return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, cipher, rsecret, secret_len); } @@ -686,9 +690,11 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *wsecret, size_t secret_len) { - ngx_connection_t *c; + ngx_connection_t *c; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_write_secret() level:%d", level); @@ -696,7 +702,7 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level, + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, cipher, wsecret, secret_len); } @@ -707,10 +713,12 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *rsecret, const uint8_t *wsecret, size_t secret_len) { - ngx_connection_t *c; - const SSL_CIPHER *cipher; + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_encryption_secrets() level:%d", level); @@ -720,7 +728,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, cipher = SSL_get_current_cipher(ssl_conn); - if (ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level, + if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, cipher, rsecret, secret_len) != 1) { @@ -735,7 +743,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level, + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, cipher, wsecret, secret_len); } @@ -755,7 +763,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_quic_frames_stream_t *fs; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = c->quic; + qc = ngx_quic_get_connection(c); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_add_handshake_data"); @@ -922,7 +930,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, "quic ngx_quic_send_alert() lvl:%d alert:%d", (int) level, (int) alert); - qc = c->quic; + qc = ngx_quic_get_connection(c); if (qc == NULL) { return 1; } @@ -944,7 +952,8 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) { - ngx_int_t rc; + ngx_int_t rc; + ngx_quic_connection_t *qc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); @@ -954,8 +963,10 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) return; } - ngx_add_timer(c->read, c->quic->in_retry ? NGX_QUIC_RETRY_TIMEOUT - : c->quic->tp.max_idle_timeout); + qc = ngx_quic_get_connection(c); + + ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT + : qc->tp.max_idle_timeout); c->read->handler = ngx_quic_input_handler; @@ -1174,7 +1185,7 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); /* A stateless reset uses an entire UDP datagram */ if (pkt->raw->start != pkt->data) { @@ -1253,10 +1264,13 @@ ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c) { - ssize_t len; - ngx_str_t res, token; - ngx_quic_header_t pkt; - u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + + qc = ngx_quic_get_connection(c); if (ngx_quic_new_token(c, &token) != NGX_OK) { return NGX_ERROR; @@ -1264,11 +1278,11 @@ ngx_quic_send_retry(ngx_connection_t *c) ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; - pkt.version = c->quic->version; + pkt.version = qc->version; pkt.log = c->log; - pkt.odcid = c->quic->odcid; - pkt.dcid = c->quic->scid; - pkt.scid = c->quic->dcid; + pkt.odcid = qc->odcid; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; pkt.token = token; res.data = buf; @@ -1286,14 +1300,14 @@ ngx_quic_send_retry(ngx_connection_t *c) return NGX_ERROR; } - c->quic->token = token; + qc->token = token; #if (NGX_QUIC_DRAFT_VERSION < 28) - c->quic->tp.original_dcid = c->quic->odcid; + qc->tp.original_dcid = qc->odcid; #endif - c->quic->tp.retry_scid = c->quic->dcid; - c->quic->in_retry = 1; + qc->tp.retry_scid = qc->dcid; + qc->in_retry = 1; - if (ngx_quic_insert_server_id(c, &c->quic->dcid) == NULL) { + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { return NGX_ERROR; } @@ -1304,16 +1318,17 @@ ngx_quic_send_retry(ngx_connection_t *c) static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) { - int len, iv_len; - u_char *data, *p, *key, *iv; - ngx_msec_t now; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - struct sockaddr_in *sin; + int len, iv_len; + u_char *data, *p, *key, *iv; + ngx_msec_t now; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + struct sockaddr_in *sin; #if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; + struct sockaddr_in6 *sin6; #endif - u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + ngx_quic_connection_t *qc; + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; switch (c->sockaddr->sa_family) { @@ -1365,7 +1380,8 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) return NGX_ERROR; } - key = c->quic->conf->token_key; + qc = ngx_quic_get_connection(c); + key = qc->conf->token_key; iv = token->data; if (RAND_bytes(iv, iv_len) <= 0 @@ -1416,7 +1432,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_connection_t *qc; u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - qc = c->quic; + qc = ngx_quic_get_connection(c); /* Retry token */ @@ -1435,7 +1451,7 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) /* NEW_TOKEN in a previous connection */ cipher = EVP_aes_256_cbc(); - key = c->quic->conf->token_key; + key = qc->conf->token_key; iv = pkt->token.data; iv_len = EVP_CIPHER_iv_length(cipher); @@ -1537,7 +1553,7 @@ ngx_quic_init_connection(ngx_connection_t *c) ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { return NGX_ERROR; @@ -1640,7 +1656,7 @@ ngx_quic_input_handler(ngx_event_t *rev) b.memory = 1; c = rev->data; - qc = c->quic; + qc = ngx_quic_get_connection(c); c->log->action = "handling quic input"; @@ -1708,12 +1724,15 @@ ngx_quic_input_handler(ngx_event_t *rev) static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) { - ngx_pool_t *pool; + ngx_pool_t *pool; + ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_close_connection rc:%i", rc); - if (!c->quic) { + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close connection early error"); @@ -1752,7 +1771,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (!qc->closing) { @@ -1873,7 +1892,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) "quic part of connection is terminated"); /* may be tested from SSL callback during SSL shutdown */ - c->quic = NULL; + c->udp = NULL; return NGX_OK; } @@ -1885,7 +1904,7 @@ ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, { ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); qc->error = err; qc->error_reason = reason; qc->error_app = 1; @@ -1964,10 +1983,11 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { - u_char *p; - ngx_int_t rc; - ngx_uint_t good; - ngx_quic_header_t pkt; + u_char *p; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; good = 0; @@ -1983,9 +2003,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) pkt.flags = p[0]; pkt.raw->pos++; - if (c->quic) { - c->quic->error = 0; - c->quic->error_reason = 0; + qc = ngx_quic_get_connection(c); + if (qc) { + qc->error = 0; + qc->error_reason = 0; } rc = ngx_quic_process_packet(c, conf, &pkt); @@ -2066,7 +2087,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, c->log->action = "processing quic packet"; - qc = c->quic; + qc = ngx_quic_get_connection(c); #if (NGX_DEBUG) ngx_quic_hexdump(c->log, "quic packet rx dcid", @@ -2184,7 +2205,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - c->quic = qc; + c->udp = &qc->udp; if (ngx_terminate || ngx_exiting) { qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; @@ -2271,7 +2292,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, if (qc->validated == 0) { qc->validated = 1; - ngx_post_event(&c->quic->push, &ngx_posted_events); + ngx_post_event(&qc->push, &ngx_posted_events); } } @@ -2306,7 +2327,7 @@ ngx_quic_init_secrets(ngx_connection_t *c) { ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) != NGX_OK) @@ -2328,7 +2349,7 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (!ngx_quic_keys_available(qc->keys, level)) { return; @@ -2403,7 +2424,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_frame_t frame; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (qc->closing) { /* @@ -2642,15 +2663,18 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) { - uint64_t base, largest, smallest, gs, ge, gap, range, pn; - uint64_t prev_pending; - ngx_uint_t i, nr; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_range_t *r; + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; c->log->action = "preparing ack"; - ctx = ngx_quic_get_send_ctx(c->quic, pkt->level); + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" @@ -2661,7 +2685,7 @@ ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) if (pkt->need_ack) { - ngx_post_event(&c->quic->push, &ngx_posted_events); + ngx_post_event(&qc->push, &ngx_posted_events); if (ctx->send_ack == 0) { ctx->ack_delay_start = ngx_current_msec; @@ -2957,16 +2981,19 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - u_char *p; - size_t ranges_len; - uint64_t ack_delay; - ngx_uint_t i; - ngx_quic_frame_t *frame; + u_char *p; + size_t ranges_len; + uint64_t ack_delay; + ngx_uint_t i; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); if (ctx->level == ssl_encryption_application) { ack_delay = ngx_current_msec - ctx->largest_received; ack_delay *= 1000; - ack_delay >>= c->quic->ctp.ack_delay_exponent; + ack_delay >>= qc->ctp.ack_delay_exponent; } else { ack_delay = 0; @@ -3000,7 +3027,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) frame->u.ack.ranges_start = frame->data; frame->u.ack.ranges_end = frame->data + ranges_len; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); return NGX_OK; } @@ -3012,7 +3039,7 @@ ngx_quic_send_cc(ngx_connection_t *c) ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (qc->draining) { return NGX_OK; @@ -3048,7 +3075,7 @@ ngx_quic_send_cc(ngx_connection_t *c) frame->u.close.reason.data = (u_char *) qc->error_reason; } - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); qc->last_cc = ngx_current_msec; @@ -3059,10 +3086,13 @@ ngx_quic_send_cc(ngx_connection_t *c) static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c) { - ngx_str_t token; - ngx_quic_frame_t *frame; + ngx_str_t token; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); - if (!c->quic->conf->retry) { + if (!qc->conf->retry) { return NGX_OK; } @@ -3080,7 +3110,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) frame->u.token.length = token.len; frame->u.token.data = token.data; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); return NGX_OK; } @@ -3098,7 +3128,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, pkt->level); @@ -3196,7 +3226,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_frame_t *f; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); *send_time = NGX_TIMER_INFINITE; found = 0; @@ -3275,7 +3305,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); latest_rtt = ngx_current_msec - send_time; qc->latest_rtt = latest_rtt; @@ -3320,7 +3350,7 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_msec_t duration; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); /* PTO calculation: quic-recovery, Appendix 8 */ duration = qc->avg_rtt; @@ -3350,7 +3380,7 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); if (sn == NULL) { @@ -3588,7 +3618,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_crypto_frame_t *f; ngx_quic_frames_stream_t *fs; - qc = c->quic; + qc = ngx_quic_get_connection(c); fs = &qc->crypto[pkt->level]; f = &frame->u.crypto; @@ -3596,7 +3626,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, last = f->offset + f->length; if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { - c->quic->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; return NGX_ERROR; } @@ -3625,8 +3655,11 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { int n, sslerr; ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; ngx_quic_crypto_frame_t *f; + qc = ngx_quic_get_connection(c); + f = &frame->u.crypto; ssl_conn = c->ssl->connection; @@ -3688,7 +3721,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) /* 12.4 Frames and frame types, figure 8 */ frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); if (ngx_quic_send_new_token(c) != NGX_OK) { return NGX_ERROR; @@ -3699,7 +3732,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) * See quic-tls 9.4 Header Protection Timing Side-Channels. */ - if (ngx_quic_keys_update(c, c->quic->keys) != NGX_OK) { + if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { return NGX_ERROR; } @@ -3731,7 +3764,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_frame_t *f; ngx_quic_frames_stream_t *fs; - qc = c->quic; + qc = ngx_quic_get_connection(c); f = &frame->u.stream; if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) @@ -3763,7 +3796,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, window = b->end - b->last; if (last > window) { - c->quic->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; goto cleanup; } @@ -3784,7 +3817,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, window = (b->pos - b->start) + (b->end - b->last); if (last > fs->received && last - fs->received > window) { - c->quic->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; return NGX_ERROR; } @@ -3812,7 +3845,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; - qc = c->quic; + qc = ngx_quic_get_connection(c); sn = data; f = &frame->u.stream; @@ -3863,7 +3896,7 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); tree = &qc->streams.tree; if (f->max_data <= qc->streams.send_max_data) { @@ -3910,7 +3943,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) @@ -3952,7 +3985,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, frame->u.max_stream_data.id = f->id; frame->u.max_stream_data.limit = n; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); return NGX_OK; } @@ -3967,7 +4000,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) @@ -4028,7 +4061,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) @@ -4082,7 +4115,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) @@ -4133,7 +4166,7 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, { ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (f->bidi) { if (qc->streams.server_max_streams_bidi < f->limit) { @@ -4160,7 +4193,10 @@ static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { - ngx_quic_frame_t *frame; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { @@ -4171,7 +4207,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, frame->type = NGX_QUIC_FT_PATH_RESPONSE; frame->u.path_response = *f; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); return NGX_OK; } @@ -4185,7 +4221,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_client_id_t *cid, *item; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (f->seqnum < qc->max_retired_seqnum) { /* @@ -4323,7 +4359,10 @@ static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum) { - ngx_quic_frame_t *frame; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { @@ -4334,7 +4373,7 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; frame->u.retire_cid.sequence_number = seqnum; - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); return NGX_OK; } @@ -4348,7 +4387,7 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); for (q = ngx_queue_head(&qc->server_ids); q != ngx_queue_sentinel(&qc->server_ids); @@ -4358,13 +4397,9 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, if (sid->seqnum == f->sequence_number) { ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); qc->nserver_ids--; - - if (c->udp != &sid->udp) { - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - } - break; } } @@ -4383,7 +4418,7 @@ ngx_quic_issue_server_ids(ngx_connection_t *c) ngx_quic_connection_t *qc; u_char id[NGX_QUIC_SERVER_CID_LEN]; - qc = c->quic; + qc = ngx_quic_get_connection(c); n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); @@ -4422,7 +4457,7 @@ ngx_quic_issue_server_ids(ngx_connection_t *c) return NGX_ERROR; } - ngx_quic_queue_frame(c->quic, frame); + ngx_quic_queue_frame(qc, frame); } return NGX_OK; @@ -4436,7 +4471,7 @@ ngx_quic_clear_temp_server_ids(ngx_connection_t *c) ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic clear temp server ids"); @@ -4453,12 +4488,9 @@ ngx_quic_clear_temp_server_ids(ngx_connection_t *c) } ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); qc->nserver_ids--; - - if (c->udp != &sid->udp) { - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - } } } @@ -4470,7 +4502,7 @@ ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); sid = ngx_quic_alloc_server_id(c, qc); if (sid == NULL) { @@ -4494,10 +4526,6 @@ ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) ngx_insert_udp_connection(c, &sid->udp, &dcid); - if (c->udp == NULL) { - c->udp = &sid->udp; - } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic insert server id seqnum:%uL", sid->seqnum); @@ -4591,7 +4619,7 @@ ngx_quic_output(ngx_connection_t *c) c->log->action = "sending frames"; - qc = c->quic; + qc = ngx_quic_get_connection(c); for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { @@ -4646,7 +4674,7 @@ ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); cg = &qc->congestion; if (ngx_queue_empty(&ctx->frames)) { @@ -4808,7 +4836,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, out.len = p - out.data; - qc = c->quic; + qc = ngx_quic_get_connection(c); pkt.keys = qc->keys; @@ -4821,7 +4849,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; } else { - if (c->quic->key_phase) { + if (qc->key_phase) { pkt.flags |= NGX_QUIC_PKT_KPHASE; } } @@ -4946,7 +4974,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); c = ev->data; - qc = c->quic; + qc = ngx_quic_get_connection(c); qc->pto_count++; @@ -4969,7 +4997,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic pto pnum:%uL pto_count:%ui level:%d", - start->pnum, c->quic->pto_count, start->level); + start->pnum, qc->pto_count, start->level); ngx_quic_resend_frames(c, ctx); } @@ -5023,7 +5051,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); now = ngx_current_msec; min_wait = 0; @@ -5098,7 +5126,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); q = ngx_queue_head(&ctx->sent); start = ngx_queue_data(q, ngx_quic_frame_t, queue); @@ -5204,7 +5232,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) ngx_quic_connection_t *qc; qs = c->qs; - qc = qs->parent->quic; + qc = ngx_quic_get_connection(qs->parent); if (bidi) { if (qc->streams.server_streams_bidi @@ -5323,7 +5351,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL is new", id); - qc = c->quic; + qc = ngx_quic_get_connection(c); if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { @@ -5414,7 +5442,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL create", id); - qc = c->quic; + qc = ngx_quic_get_connection(c); pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); if (pool == NULL) { @@ -5503,7 +5531,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) cln->handler = ngx_quic_stream_cleanup_handler; cln->data = sn->c; - ngx_rbtree_insert(&c->quic->streams.tree, &sn->node); + ngx_rbtree_insert(&qc->streams.tree, &sn->node); return sn; } @@ -5523,7 +5551,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) qs = c->qs; b = qs->b; pc = qs->parent; - qc = pc->quic; + qc = ngx_quic_get_connection(pc); rev = c->read; if (rev->error) { @@ -5576,7 +5604,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + (b->end - b->last); - ngx_quic_queue_frame(pc->quic, frame); + ngx_quic_queue_frame(qc, frame); } if ((qc->streams.recv_max_data / 2) < qc->streams.received) { @@ -5593,7 +5621,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->type = NGX_QUIC_FT_MAX_DATA; frame->u.max_data.max_data = qc->streams.recv_max_data; - ngx_quic_queue_frame(pc->quic, frame); + ngx_quic_queue_frame(qc, frame); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL recv: increased max_data:%uL", @@ -5652,7 +5680,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) qs = c->qs; pc = qs->parent; - qc = pc->quic; + qc = ngx_quic_get_connection(pc); wev = c->write; if (wev->error) { @@ -5770,7 +5798,7 @@ ngx_quic_max_stream_flow(ngx_connection_t *c) ngx_quic_connection_t *qc; qs = c->qs; - qc = qs->parent->quic; + qc = ngx_quic_get_connection(qs->parent); size = NGX_QUIC_STREAM_BUFSIZE; sent = c->sent; @@ -5829,7 +5857,7 @@ ngx_quic_stream_cleanup_handler(void *data) qs = c->qs; pc = qs->parent; - qc = pc->quic; + qc = ngx_quic_get_connection(pc); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL cleanup", qs->id); @@ -5931,7 +5959,7 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) p = NULL; } - qc = c->quic; + qc = ngx_quic_get_connection(c); if (!ngx_queue_empty(&qc->free_frames)) { @@ -5981,7 +6009,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) return; } - qc = c->quic; + qc = ngx_quic_get_connection(c); cg = &qc->congestion; cg->in_flight -= f->plen; @@ -6032,7 +6060,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) return; } - qc = c->quic; + qc = ngx_quic_get_connection(c); cg = &qc->congestion; cg->in_flight -= f->plen; @@ -6068,7 +6096,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) { ngx_quic_connection_t *qc; - qc = c->quic; + qc = ngx_quic_get_connection(c); if (frame->data) { ngx_free(frame->data); @@ -6087,9 +6115,12 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) uint32_t ngx_quic_version(ngx_connection_t *c) { - uint32_t version; + uint32_t version; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); - version = c->quic->version; + version = qc->version; return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 4f81dbd60..51b6491f1 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -60,6 +60,8 @@ #define NGX_QUIC_MAX_SERVER_IDS 8 +#define ngx_quic_get_connection(c) ((ngx_quic_connection_t *)(c)->udp) + typedef struct { /* configurable */ -- cgit v1.2.3 From 2fd31c8959fbae8f069d09b61f339358214e75d1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Nov 2020 19:40:00 +0000 Subject: QUIC: renamed c->qs to c->quic. --- src/core/ngx_connection.h | 2 +- src/event/ngx_event.c | 6 +++--- src/event/ngx_event_openssl.c | 2 +- src/event/ngx_event_quic.c | 12 ++++++------ src/http/modules/ngx_http_quic_module.c | 2 +- src/http/ngx_http_request.c | 8 ++++---- src/http/ngx_http_request_body.c | 2 +- src/http/ngx_http_upstream.c | 4 ++-- src/http/v3/ngx_http_v3.h | 6 +++--- src/http/v3/ngx_http_v3_parse.c | 2 +- src/http/v3/ngx_http_v3_request.c | 6 +++--- src/http/v3/ngx_http_v3_streams.c | 18 +++++++++--------- src/http/v3/ngx_http_v3_tables.c | 16 ++++++++-------- src/stream/ngx_stream_core_module.c | 2 +- src/stream/ngx_stream_handler.c | 2 +- src/stream/ngx_stream_quic_module.c | 2 +- 16 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index a075d84a3..3ac6205e2 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -151,7 +151,7 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; #if (NGX_QUIC || NGX_COMPAT) - ngx_quic_stream_t *qs; + ngx_quic_stream_t *quic; #endif #if (NGX_SSL || NGX_COMPAT) diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index de32630fd..bbb75dea8 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -274,7 +274,7 @@ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) c = rev->data; - if (c->qs) { + if (c->quic) { if (!rev->active && !rev->ready) { rev->active = 1; @@ -368,7 +368,7 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) #if (NGX_QUIC) - if (c->qs) { + if (c->quic) { if (!wev->active && !wev->ready) { wev->active = 1; @@ -953,7 +953,7 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat) int sndlowat; #if (NGX_QUIC) - if (c->qs) { + if (c->quic) { return NGX_OK; } #endif diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index d404bbe24..00260e3b7 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2870,7 +2870,7 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_uint_t tries; #if (NGX_QUIC) - if (c->qs) { + if (c->quic) { /* QUIC streams inherit SSL object */ return NGX_OK; } diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index c9ea2de1e..7643cecfb 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -5231,7 +5231,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) ngx_quic_stream_t *qs, *sn; ngx_quic_connection_t *qc; - qs = c->qs; + qs = c->quic; qc = ngx_quic_get_connection(qs->parent); if (bidi) { @@ -5482,7 +5482,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) return NULL; } - sn->c->qs = sn; + sn->c->quic = sn; sn->c->type = SOCK_STREAM; sn->c->pool = pool; sn->c->ssl = c->ssl; @@ -5548,7 +5548,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->qs; + qs = c->quic; b = qs->b; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -5678,7 +5678,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->qs; + qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); wev = c->write; @@ -5797,7 +5797,7 @@ ngx_quic_max_stream_flow(ngx_connection_t *c) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->qs; + qs = c->quic; qc = ngx_quic_get_connection(qs->parent); size = NGX_QUIC_STREAM_BUFSIZE; @@ -5855,7 +5855,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->qs; + qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 3993d4692..515d6c953 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -179,7 +179,7 @@ static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - if (r->connection->qs) { + if (r->connection->quic) { v->len = 4; v->valid = 1; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index f33555687..8df43891a 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -330,7 +330,7 @@ ngx_http_init_connection(ngx_connection_t *c) #endif - if (c->qs == NULL) { + if (c->quic == NULL) { c->log->connection = c->number; qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, @@ -339,7 +339,7 @@ ngx_http_init_connection(ngx_connection_t *c) return; } - phc = c->qs->parent->data; + phc = c->quic->parent->data; if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; @@ -2847,7 +2847,7 @@ ngx_http_finalize_connection(ngx_http_request_t *r) #endif #if (NGX_HTTP_QUIC) - if (r->connection->qs) { + if (r->connection->quic) { ngx_http_close_request(r, 0); return; } @@ -3064,7 +3064,7 @@ ngx_http_test_reading(ngx_http_request_t *r) #if (NGX_HTTP_QUIC) - if (c->qs) { + if (c->quic) { if (c->read->error) { err = 0; goto closed; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index 760a3cd05..c8ed658cd 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -580,7 +580,7 @@ ngx_http_discard_request_body(ngx_http_request_t *r) #endif #if (NGX_HTTP_QUIC) - if (r->connection->qs) { + if (r->connection->quic) { return NGX_OK; } #endif diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index 419d936b8..ceb98f140 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -524,7 +524,7 @@ ngx_http_upstream_init(ngx_http_request_t *r) #endif #if (NGX_HTTP_QUIC) - if (c->qs) { + if (c->quic) { ngx_http_upstream_init_request(r); return; } @@ -1354,7 +1354,7 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, #if (NGX_HTTP_QUIC) - if (c->qs) { + if (c->quic) { if (c->write->error) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index c244ab861..949fc2206 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -77,11 +77,11 @@ #define ngx_http_v3_get_module_srv_conf(c, module) \ ngx_http_get_module_srv_conf( \ - ((ngx_http_v3_connection_t *) c->qs->parent->data)->hc.conf_ctx, \ - module) + ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ + module) #define ngx_http_v3_finalize_connection(c, code, reason) \ - ngx_quic_finalize_connection(c->qs->parent, code, reason) + ngx_quic_finalize_connection(c->quic->parent, code, reason) typedef struct { diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 6ce5f10a7..d5ff3cb8f 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -279,7 +279,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_client_ack_header(c, c->qs->id) != NGX_OK) { + if (ngx_http_v3_client_ack_header(c, c->quic->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 d9f4c9d55..5511e3031 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -469,7 +469,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) out = NULL; ll = &out; - if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 && r->method != NGX_HTTP_HEAD) { if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { @@ -1123,7 +1123,7 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_http_v3_connection_t *h3c; c = r->connection; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1196,7 +1196,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, goto failed; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); c->data = hc; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 8d5147f4d..8ac048715 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -50,7 +50,7 @@ ngx_http_v3_init_connection(ngx_connection_t *c) hc = c->data; - if (c->qs == NULL) { + if (c->quic == NULL) { h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); if (h3c == NULL) { return NGX_ERROR; @@ -69,7 +69,7 @@ ngx_http_v3_init_connection(ngx_connection_t *c) return NGX_ERROR; } - if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { return NGX_OK; } @@ -101,7 +101,7 @@ ngx_http_v3_close_uni_stream(ngx_connection_t *c) ngx_http_v3_uni_stream_t *us; us = c->data; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); @@ -131,7 +131,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) c = rev->data; us = c->data; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); @@ -363,7 +363,7 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) goto failed; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; h3c->npushing++; cln->handler = ngx_http_v3_push_cleanup; @@ -419,7 +419,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) index = -1; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; if (index >= 0) { if (h3c->known_streams[index]) { @@ -476,7 +476,7 @@ ngx_http_v3_send_settings(ngx_connection_t *c) ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; if (h3c->settings_sent) { return NGX_OK; @@ -763,7 +763,7 @@ ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) { ngx_http_v3_connection_t *h3c; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 MAX_PUSH_ID:%uL", max_push_id); @@ -786,7 +786,7 @@ ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) ngx_http_v3_push_t *push; ngx_http_v3_connection_t *h3c; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 CANCEL_PUSH:%uL", push_id); diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index bf4f1449c..5389d0e2f 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -198,7 +198,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; dt = &h3c->table; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -250,7 +250,7 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 set capacity %ui", capacity); - pc = c->qs->parent; + pc = c->quic->parent; h3c = pc->data; h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); @@ -324,7 +324,7 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; dt = &h3c->table; if (need > dt->capacity) { @@ -367,7 +367,7 @@ ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; dt = &h3c->table; if (dt->base + dt->nelts <= index) { @@ -463,7 +463,7 @@ ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; dt = &h3c->table; if (index < dt->base || index - dt->base >= dt->nelts) { @@ -506,7 +506,7 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) return NGX_OK; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; dt = &h3c->table; h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); @@ -555,7 +555,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - pc = c->qs->parent; + pc = c->quic->parent; h3c = pc->data; dt = &h3c->table; @@ -636,7 +636,7 @@ ngx_http_v3_new_header(ngx_connection_t *c) ngx_http_v3_block_t *block; ngx_http_v3_connection_t *h3c; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header, blocked:%ui", h3c->nblocked); diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 6990ae3f6..5f4fe13a0 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -326,7 +326,7 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, if (c->type == SOCK_STREAM #if (NGX_STREAM_QUIC) - && c->qs == NULL + && c->quic == NULL #endif && cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 33f7bc191..c5b2e54a2 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -120,7 +120,7 @@ ngx_stream_init_connection(ngx_connection_t *c) if (addr_conf->quic) { ngx_quic_conf_t *qcf; - if (c->qs == NULL) { + if (c->quic == NULL) { c->log->connection = c->number; qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index edf5ac704..49b1a6f85 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -170,7 +170,7 @@ static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - if (s->connection->qs) { + if (s->connection->quic) { v->len = 4; v->valid = 1; -- cgit v1.2.3 From db7fbc4d04d678c16500d4453df88f471ff27c61 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 11 Nov 2020 17:56:02 +0000 Subject: QUIC: reallocate qc->dcid on retry. Previously new dcid was generated in the same memory that was allocated for qc->dcid when creating the QUIC connection. However this memory was also referenced by initial_source_connection_id and retry_source_connection_id transport parameters. As a result these parameters changed their values after retry which broke the protocol. --- src/event/ngx_event_quic.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 7643cecfb..099f8778e 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2158,6 +2158,12 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_clear_temp_server_ids(c); + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From fc5a0886659187c88e5d26f2732239eafffb320e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 11 Nov 2020 19:39:23 +0000 Subject: QUIC: added quic_stateless_reset_token_key Stream directive. A similar directive is already available in HTTP. --- src/stream/ngx_stream_quic_module.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 49b1a6f85..fc767e991 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -126,6 +126,13 @@ static ngx_command_t ngx_stream_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, + { ngx_string("quic_stateless_reset_token_key"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, sr_token_key), + NULL }, + ngx_null_command }; @@ -223,6 +230,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.retry_scid = { 0, NULL }; * conf->tp.stateless_reset_token = { 0 } * conf->tp.preferred_address = NULL + * conf->sr_token_key = { 0, NULL } * conf->require_alpn = 0; */ @@ -304,6 +312,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); conf->ssl = &scf->ssl; -- cgit v1.2.3 From 6e6daf459234f0f7330c69de1f27d0064bb217ae Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 11 Nov 2020 19:40:41 +0000 Subject: QUIC: removed comment. --- src/stream/ngx_stream_quic_module.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index fc767e991..4ddf5c90a 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -228,7 +228,6 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.original_dcid = { 0, NULL }; * conf->tp.initial_scid = { 0, NULL }; * conf->tp.retry_scid = { 0, NULL }; - * conf->tp.stateless_reset_token = { 0 } * conf->tp.preferred_address = NULL * conf->sr_token_key = { 0, NULL } * conf->require_alpn = 0; -- cgit v1.2.3 From 5bbc3f1967a8ac1cce0f16b428f156301b81beb9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 11 Nov 2020 21:08:48 +0000 Subject: QUIC: generate default stateless reset token key. Previously, if quic_stateless_reset_token_key was empty or unspecified, initial stateless reset token was not generated. However subsequent tokens were generated with empty key, which resulted in error with certain SSL libraries, for example OpenSSL. Now a random 32-byte stateless reset token key is generated if none is specified in the configuration. As a result, stateless reset tokens are now generated for all server ids. --- src/event/ngx_event_quic.c | 24 ++++++++---------------- src/event/ngx_event_quic.h | 2 +- src/event/ngx_event_quic_transport.c | 16 ++++++---------- src/http/modules/ngx_http_quic_module.c | 13 +++++++++++++ src/stream/ngx_stream_quic_module.c | 13 +++++++++++++ 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 099f8778e..97ffd96c4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1133,10 +1133,6 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic handle stateless reset output"); - if (conf->sr_token_key.len == 0) { - return NGX_DECLINED; - } - if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { return NGX_DECLINED; } @@ -1573,20 +1569,16 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif - if (qc->conf->sr_token_key.len) { - qc->tp.sr_enabled = 1; - - if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, - qc->tp.sr_token) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_quic_hexdump(c->log, "quic stateless reset token", - qc->tp.sr_token, (size_t) NGX_QUIC_SR_TOKEN_LEN); + if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, + qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; } + ngx_quic_hexdump(c->log, "quic stateless reset token", + qc->tp.sr_token, (size_t) NGX_QUIC_SR_TOKEN_LEN); + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); /* always succeeds */ diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index 51b6491f1..db24b6642 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -27,6 +27,7 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 #define NGX_QUIC_RETRY_TIMEOUT 3000 #define NGX_QUIC_RETRY_LIFETIME 30000 @@ -82,7 +83,6 @@ typedef struct { ngx_str_t initial_scid; ngx_str_t retry_scid; u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; - ngx_uint_t sr_enabled; /* TODO */ void *preferred_address; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 626da6c9e..756b679e5 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -1883,11 +1883,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, } #endif - if (tp->sr_enabled) { - 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; - } + 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; @@ -1935,11 +1933,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, } #endif - if (tp->sr_enabled) { - 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/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 515d6c953..ff79cdc8d 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -317,6 +317,19 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + if (conf->sr_token_key.len == 0) { + conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; + + conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); + if (conf->sr_token_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { + return NGX_CONF_ERROR; + } + } + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->ssl = &sscf->ssl; diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 4ddf5c90a..eaaaba89a 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -313,6 +313,19 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + if (conf->sr_token_key.len == 0) { + conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; + + conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); + if (conf->sr_token_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { + return NGX_CONF_ERROR; + } + } + scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); conf->ssl = &scf->ssl; -- cgit v1.2.3 From eb8f476d599306507300a82b4a26d2a2476b748c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Nov 2020 20:42:45 +0000 Subject: Fixed generating chunked response after 46e3542d51b3. If trailers were missing and a chain carrying the last_buf flag had no data in it, then last HTTP/1 chunk was broken. The problem was introduced while implementing HTTP/3 response body generation. The change fixes the issue and reduces diff to the mainline nginx. --- src/http/modules/ngx_http_chunked_filter_module.c | 45 +++++++++++++---------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index 87b032496..371559e2f 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -18,7 +18,7 @@ typedef struct { static ngx_int_t ngx_http_chunked_filter_init(ngx_conf_t *cf); static ngx_chain_t *ngx_http_chunked_create_trailers(ngx_http_request_t *r, - ngx_http_chunked_filter_ctx_t *ctx, size_t size); + ngx_http_chunked_filter_ctx_t *ctx); static ngx_http_module_t ngx_http_chunked_filter_module_ctx = { @@ -204,8 +204,25 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) out = tl; } +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + } + + } else +#endif + if (cl->buf->last_buf) { - tl = ngx_http_chunked_create_trailers(r, ctx, size); + tl = ngx_http_chunked_create_trailers(r, ctx); if (tl == NULL) { return NGX_ERROR; } @@ -214,12 +231,11 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) *ll = tl; - } else if (size > 0 -#if (NGX_HTTP_V3) - && r->http_version != NGX_HTTP_VERSION_30 -#endif - ) - { + if (size == 0) { + tl->buf->pos += 2; + } + + } else if (size > 0) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; @@ -250,7 +266,7 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) static ngx_chain_t * ngx_http_chunked_create_trailers(ngx_http_request_t *r, - ngx_http_chunked_filter_ctx_t *ctx, size_t size) + ngx_http_chunked_filter_ctx_t *ctx) { size_t len; ngx_buf_t *b; @@ -259,12 +275,6 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r, ngx_list_part_t *part; ngx_table_elt_t *header; -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - return ngx_http_v3_create_trailers(r); - } -#endif - len = 0; part = &r->headers_out.trailers.part; @@ -317,10 +327,7 @@ ngx_http_chunked_create_trailers(ngx_http_request_t *r, b->last = b->pos; - if (size > 0) { - *b->last++ = CR; *b->last++ = LF; - } - + *b->last++ = CR; *b->last++ = LF; *b->last++ = '0'; *b->last++ = CR; *b->last++ = LF; -- cgit v1.2.3 From c092a7de0f7473e1d117792eb5b89e68894abdea Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 13 Nov 2020 13:24:45 +0000 Subject: QUIC: microoptimization in varint parsing. Removed a useless mask from the value being shifted, since it is 1-byte wide. --- src/event/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 756b679e5..7f2c6e4eb 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -160,7 +160,7 @@ ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) } p = pos; - len = 1 << ((*p & 0xc0) >> 6); + len = 1 << (*p >> 6); value = *p++ & 0x3f; -- cgit v1.2.3 From 375b47efb3558c7871c61f682547c2f3a5fc9992 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 13 Nov 2020 15:11:27 +0000 Subject: Core: reduced diff to the default branch. It became feasible to reduce after feec2cc762f6 that removes ngx_quic_connection_t from ngx_connection_s. --- src/core/ngx_connection.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 3ac6205e2..edc1ccbc2 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -148,17 +148,17 @@ struct ngx_connection_s { socklen_t socklen; ngx_str_t addr_text; - ngx_proxy_protocol_t *proxy_protocol; + ngx_proxy_protocol_t *proxy_protocol; #if (NGX_QUIC || NGX_COMPAT) - ngx_quic_stream_t *quic; + ngx_quic_stream_t *quic; #endif #if (NGX_SSL || NGX_COMPAT) - ngx_ssl_connection_t *ssl; + ngx_ssl_connection_t *ssl; #endif - ngx_udp_connection_t *udp; + ngx_udp_connection_t *udp; struct sockaddr *local_sockaddr; socklen_t local_socklen; -- cgit v1.2.3 From 7046a10134a8b15c0c769197502663bfe67ed63b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 13 Nov 2020 15:11:29 +0000 Subject: Core: hide "struct ngx_quic_connection_s" and further reduce diffs. As with the previous change, it became feasible with feec2cc762f6 that removes ngx_quic_connection_t from ngx_connection_s. --- src/core/ngx_core.h | 37 ++++++++++++++++++------------------- src/event/ngx_event_quic.c | 4 ++-- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index ade35be73..8e6c756c9 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -12,25 +12,24 @@ #include -typedef struct ngx_module_s ngx_module_t; -typedef struct ngx_conf_s ngx_conf_t; -typedef struct ngx_cycle_s ngx_cycle_t; -typedef struct ngx_pool_s ngx_pool_t; -typedef struct ngx_chain_s ngx_chain_t; -typedef struct ngx_log_s ngx_log_t; -typedef struct ngx_open_file_s ngx_open_file_t; -typedef struct ngx_command_s ngx_command_t; -typedef struct ngx_file_s ngx_file_t; -typedef struct ngx_event_s ngx_event_t; -typedef struct ngx_event_aio_s ngx_event_aio_t; -typedef struct ngx_connection_s ngx_connection_t; -typedef struct ngx_thread_task_s ngx_thread_task_t; -typedef struct ngx_ssl_s ngx_ssl_t; -typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; -typedef struct ngx_quic_connection_s ngx_quic_connection_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_module_s ngx_module_t; +typedef struct ngx_conf_s ngx_conf_t; +typedef struct ngx_cycle_s ngx_cycle_t; +typedef struct ngx_pool_s ngx_pool_t; +typedef struct ngx_chain_s ngx_chain_t; +typedef struct ngx_log_s ngx_log_t; +typedef struct ngx_open_file_s ngx_open_file_t; +typedef struct ngx_command_s ngx_command_t; +typedef struct ngx_file_s ngx_file_t; +typedef struct ngx_event_s ngx_event_t; +typedef struct ngx_event_aio_s ngx_event_aio_t; +typedef struct ngx_connection_s ngx_connection_t; +typedef struct ngx_thread_task_s ngx_thread_task_t; +typedef struct ngx_ssl_s ngx_ssl_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 void (*ngx_event_handler_pt)(ngx_event_t *ev); typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 97ffd96c4..edd1c7892 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -114,7 +114,7 @@ typedef struct { } ngx_quic_send_ctx_t; -struct ngx_quic_connection_s { +typedef struct { ngx_udp_connection_t udp; uint32_t version; @@ -182,7 +182,7 @@ struct ngx_quic_connection_s { unsigned in_retry:1; unsigned initialized:1; unsigned validated:1; -}; +} ngx_quic_connection_t; typedef struct { -- cgit v1.2.3 From 5ff8f8aaea1a2ea68880782684ba8309a9272581 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 12:22:24 +0000 Subject: QUIC: removed macros for stream limits unused since c5324bb3a704. --- src/event/ngx_event_quic.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index edd1c7892..5ef8df977 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -25,9 +25,6 @@ #define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) -#define NGX_QUIC_STREAMS_INC 16 -#define NGX_QUIC_STREAMS_LIMIT (1ULL < 60) - /* * 7.4. Cryptographic Message Buffering * Implementations MUST support buffering at least 4096 bytes of data -- cgit v1.2.3 From b1d930b8931d7789cc528b873065d938c781a92b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:31:51 +0000 Subject: QUIC: sorted header parsing functions in order of appearance. No functional changes. --- src/event/ngx_event_quic_transport.c | 328 +++++++++++++++++------------------ 1 file changed, 164 insertions(+), 164 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 7f2c6e4eb..90ea572bd 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -74,12 +74,12 @@ static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst); -static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); static ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_supported_version(uint32_t version); static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); @@ -328,43 +328,34 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) } -ngx_int_t -ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, - ngx_str_t *dcid) +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) { - size_t len, offset; - - if (n == 0) { - goto failed; - } + u_char *p, *end; - if (ngx_quic_long_pkt(*data)) { - if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { - goto failed; - } + p = pkt->raw->pos; + end = pkt->data + pkt->len; - len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; - offset = NGX_QUIC_LONG_DCID_OFFSET; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx short flags:%xd", pkt->flags); - } else { - len = NGX_QUIC_SERVER_CID_LEN; - offset = NGX_QUIC_SHORT_DCID_OFFSET; + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; } - if (n < len + offset) { - goto failed; + pkt->dcid.len = dcid_len; + + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; } - dcid->len = len; - dcid->data = &data[offset]; + pkt->raw->pos = p; return NGX_OK; - -failed: - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); - - return NGX_ERROR; } @@ -443,6 +434,150 @@ ngx_quic_parse_long_header(ngx_quic_header_t *pkt) } +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t varint; + + p = pkt->raw->pos; + + end = pkt->raw->last; + + pkt->log->action = "parsing quic initial header"; + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } + + pkt->token.len = varint; + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx initial len:%uL", varint); + + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic truncated initial packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + varint - pkt->data; + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len); + ngx_quic_hexdump(pkt->log, "quic SCID", pkt->scid.data, pkt->scid.len); + ngx_quic_hexdump(pkt->log, "quic token", pkt->token.data, pkt->token.len); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t plen; + + p = pkt->raw->pos; + end = pkt->raw->last; + + pkt->log->action = "parsing quic handshake header"; + + p = ngx_quic_parse_int(p, end, &plen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx handshake len:%uL", plen); + + if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic truncated handshake packet"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + plen - pkt->data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) { @@ -590,141 +725,6 @@ ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, } -static ngx_int_t -ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) -{ - u_char *p, *end; - - p = pkt->raw->pos; - end = pkt->data + pkt->len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx short flags:%xd", pkt->flags); - - if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); - return NGX_ERROR; - } - - pkt->dcid.len = dcid_len; - - p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read dcid"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) -{ - u_char *p, *end; - uint64_t varint; - - p = pkt->raw->pos; - - end = pkt->raw->last; - - pkt->log->action = "parsing quic initial header"; - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse token length"); - return NGX_ERROR; - } - - pkt->token.len = varint; - - p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet too small to read token data"); - return NGX_ERROR; - } - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); - return NGX_ERROR; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx initial len:%uL", varint); - - if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic truncated initial packet"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - pkt->len = p + varint - pkt->data; - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len); - ngx_quic_hexdump(pkt->log, "quic SCID", pkt->scid.data, pkt->scid.len); - ngx_quic_hexdump(pkt->log, "quic token", pkt->token.data, pkt->token.len); -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) -{ - u_char *p, *end; - uint64_t plen; - - p = pkt->raw->pos; - end = pkt->raw->last; - - pkt->log->action = "parsing quic handshake header"; - - p = ngx_quic_parse_int(p, end, &plen); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); - return NGX_ERROR; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx handshake len:%uL", plen); - - if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic truncated handshake packet"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - pkt->len = p + plen - pkt->data; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_supported_version(uint32_t version) -{ - ngx_uint_t i; - - for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { - if (ngx_quic_versions[i] == version) { - return 1; - } - } - - return 0; -} - - #define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) #define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) #define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) -- cgit v1.2.3 From 5e32d82dea03e25c472f8549afe167d827104100 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:32:04 +0000 Subject: QUIC: refactored long header parsing. The largely duplicate type-specific functions ngx_quic_parse_initial_header(), ngx_quic_parse_handshake_header(), and a missing one for 0-RTT, were merged. The new order of functions listed in ngx_event_quic_transport.c reflects this. |_ ngx_quic_parse_long_header - version-invariant long header fields \_ ngx_quic_supported_version - a helper to decide we can go further \_ ngx_quic_parse_long_header_v1 - QUICv1-specific long header fields 0-RTT packets previously appeared as Handshake are now logged as appropriate: *1 quic packet rx long flags:db version:ff00001d *1 quic packet rx early len:870 Logging SCID/DCID is no longer duplicated as were seen with Initial packets. --- src/event/ngx_event_quic.c | 9 ++- src/event/ngx_event_quic_transport.c | 129 +++++++++++------------------------ src/event/ngx_event_quic_transport.h | 5 ++ 3 files changed, 50 insertions(+), 93 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5ef8df977..a11300407 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -47,11 +47,6 @@ #define NGX_QUIC_MAX_ACK_GAP 2 -#define ngx_quic_level_name(lvl) \ - (lvl == ssl_encryption_application) ? "app" \ - : (lvl == ssl_encryption_initial) ? "init" \ - : (lvl == ssl_encryption_handshake) ? "hs" : "early" - typedef struct { ngx_rbtree_t tree; @@ -2086,6 +2081,10 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_hexdump(c->log, "quic packet rx scid", pkt->scid.data, pkt->scid.len); } + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_hexdump(c->log, "quic token", pkt->token.data, pkt->token.len); + } #endif if (qc) { diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 90ea572bd..50ec26f09 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -78,8 +78,7 @@ static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len); static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_supported_version(uint32_t version); -static ngx_int_t ngx_quic_parse_initial_header(ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); @@ -291,36 +290,7 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) return NGX_ABORT; } - if (ngx_quic_pkt_in(pkt->flags)) { - - if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic UDP datagram is too small for initial packet"); - return NGX_DECLINED; - } - - pkt->level = ssl_encryption_initial; - - if (ngx_quic_parse_initial_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } - - return NGX_OK; - } - - if (ngx_quic_pkt_hs(pkt->flags)) { - pkt->level = ssl_encryption_handshake; - - } else if (ngx_quic_pkt_zrtt(pkt->flags)) { - pkt->level = ssl_encryption_early_data; - - } else { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic unknown long packet type"); - return NGX_DECLINED; - } - - if (ngx_quic_parse_handshake_header(pkt) != NGX_OK) { + if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { return NGX_DECLINED; } @@ -450,89 +420,72 @@ ngx_quic_supported_version(uint32_t version) static ngx_int_t -ngx_quic_parse_initial_header(ngx_quic_header_t *pkt) +ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) { u_char *p, *end; uint64_t varint; p = pkt->raw->pos; - end = pkt->raw->last; - pkt->log->action = "parsing quic initial header"; - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse token length"); - return NGX_ERROR; - } - - pkt->token.len = varint; - - p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet too small to read token data"); - return NGX_ERROR; - } - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); - return NGX_ERROR; - } + pkt->log->action = "parsing quic long header"; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx initial len:%uL", varint); + if (ngx_quic_pkt_in(pkt->flags)) { - if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic truncated initial packet"); - return NGX_ERROR; - } + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } - pkt->raw->pos = p; - pkt->len = p + varint - pkt->data; + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(pkt->log, "quic DCID", pkt->dcid.data, pkt->dcid.len); - ngx_quic_hexdump(pkt->log, "quic SCID", pkt->scid.data, pkt->scid.len); - ngx_quic_hexdump(pkt->log, "quic token", pkt->token.data, pkt->token.len); -#endif + pkt->token.len = varint; - return NGX_OK; -} + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + pkt->level = ssl_encryption_initial; -static ngx_int_t -ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt) -{ - u_char *p, *end; - uint64_t plen; + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; - p = pkt->raw->pos; - end = pkt->raw->last; + } else if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; - pkt->log->action = "parsing quic handshake header"; + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; + } - p = ngx_quic_parse_int(p, end, &plen); + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx handshake len:%uL", plen); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx %s len:%uL", + ngx_quic_level_name(pkt->level), varint); - if (plen > (uint64_t)((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic truncated handshake packet"); + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", + ngx_quic_level_name(pkt->level)); return NGX_ERROR; } pkt->raw->pos = p; - pkt->len = p + plen - pkt->data; + pkt->len = p + varint - pkt->data; return NGX_OK; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 988790c8c..bd41842ad 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -40,6 +40,11 @@ #define ngx_quic_pkt_retry(flags) \ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" + /* 12.4. Frames and Frame Types */ #define NGX_QUIC_FT_PADDING 0x00 -- cgit v1.2.3 From 97dcde97991169da1106117da464516445c36a77 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:32:06 +0000 Subject: QUIC: hide header creation internals in ngx_event_quic_transport.c. It doesn't make sense to expose the header type in a public function. --- src/event/ngx_event_quic.c | 2 +- src/event/ngx_event_quic_protection.c | 4 ++-- src/event/ngx_event_quic_transport.c | 17 ++++++++++++++++- src/event/ngx_event_quic_transport.h | 5 +---- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index a11300407..2d19ebea4 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -4858,7 +4858,7 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (start->level == ssl_encryption_initial && pkt.need_ack) { pad_len = NGX_QUIC_MIN_INITIAL_SIZE - EVP_GCM_TLS_TAG_LEN - - ngx_quic_create_long_header(&pkt, NULL, out.len, NULL); + - ngx_quic_create_header(&pkt, NULL, out.len, NULL); pad_len = ngx_min(pad_len, NGX_QUIC_MIN_INITIAL_SIZE); } else { diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index a5c73f3a6..0b491d976 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -837,7 +837,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_str_t *res) out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; ad.data = res->data; - ad.len = ngx_quic_create_long_header(pkt, ad.data, out.len, &pnp); + ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); out.data = res->data + ad.len; @@ -895,7 +895,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_str_t *res) out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; ad.data = res->data; - ad.len = ngx_quic_create_short_header(pkt, ad.data, out.len, &pnp); + ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); out.data = res->data + ad.len; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 50ec26f09..9f0485d54 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -80,6 +80,11 @@ static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_supported_version(uint32_t version); static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); + static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); @@ -562,6 +567,16 @@ ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, + u_char **pnp) +{ + return ngx_quic_short_pkt(pkt->flags) + ? ngx_quic_create_short_header(pkt, out, pkt_len, pnp) + : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); +} + + +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp) { @@ -612,7 +627,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, } -size_t +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp) { diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index bd41842ad..ee89855bd 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -335,10 +335,7 @@ ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); -size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); - -size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, +size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, -- cgit v1.2.3 From cb158c264d201afaa4f5233f4362946a834dfc67 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:32:22 +0000 Subject: QUIC: macros for manipulating header protection and reserved bits. This gets rid of magic numbers from quic protection and allows to push down header construction specifics further to quic transport. --- src/event/ngx_event_quic_protection.c | 17 +++++------------ src/event/ngx_event_quic_transport.h | 8 +++++--- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 0b491d976..422853310 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -870,7 +870,7 @@ ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } /* quic-tls: 5.4.1. Header Protection Application */ - ad.data[0] ^= mask[0] & 0x0f; + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); for (i = 0; i < pkt->num_len; i++) { pnp[i] ^= mask[i + 1]; @@ -928,7 +928,7 @@ ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } /* quic-tls: 5.4.1. Header Protection Application */ - ad.data[0] ^= mask[0] & 0x1f; + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); for (i = 0; i < pkt->num_len; i++) { pnp[i] ^= mask[i + 1]; @@ -1161,11 +1161,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) return NGX_DECLINED; } - if (ngx_quic_long_pkt(pkt->flags)) { - clearflags = pkt->flags ^ (mask[0] & 0x0f); + clearflags = pkt->flags ^ (mask[0] & ngx_quic_pkt_hp_mask(pkt->flags)); - } else { - clearflags = pkt->flags ^ (mask[0] & 0x1f); + if (ngx_quic_short_pkt(pkt->flags)) { key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0; if (key_phase != pkt->key_phase) { @@ -1192,12 +1190,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) in.data = p; in.len = len - pnl; - if (ngx_quic_long_pkt(pkt->flags)) { - badflags = clearflags & NGX_QUIC_PKT_LONG_RESERVED_BIT; - - } else { - badflags = clearflags & NGX_QUIC_PKT_SHORT_RESERVED_BIT; - } + badflags = clearflags & ngx_quic_pkt_rb_mask(pkt->flags); ad.len = p - pkt->data; ad.data = pkt->plaintext; diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index ee89855bd..2e7a6f953 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -19,9 +19,6 @@ #define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ #define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ -#define NGX_QUIC_PKT_LONG_RESERVED_BIT 0x0C -#define NGX_QUIC_PKT_SHORT_RESERVED_BIT 0x18 - #define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) #define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) @@ -40,6 +37,11 @@ #define ngx_quic_pkt_retry(flags) \ (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) +#define ngx_quic_pkt_rb_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) +#define ngx_quic_pkt_hp_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) + #define ngx_quic_level_name(lvl) \ (lvl == ssl_encryption_application) ? "app" \ : (lvl == ssl_encryption_initial) ? "init" \ -- cgit v1.2.3 From 99ae2fbd9529d99964b02c532e59cc16e5dcf76f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:33:12 +0000 Subject: QUIC: merged create_long/short_packet() functions. They no longer differ. --- src/event/ngx_event_quic_protection.c | 70 ++--------------------------------- 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 422853310..e006f8165 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -77,9 +77,7 @@ static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); -static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt, - ngx_str_t *res); -static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt, +static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -825,65 +823,7 @@ ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) static ngx_int_t -ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_str_t *res) -{ - u_char *pnp, *sample; - ngx_str_t ad, out; - ngx_uint_t i; - ngx_quic_secret_t *secret; - ngx_quic_ciphers_t ciphers; - u_char nonce[12], mask[16]; - - out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; - - ad.data = res->data; - ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); - - out.data = res->data + ad.len; - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); -#endif - - if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) - { - return NGX_ERROR; - } - - secret = &pkt->keys->secrets[pkt->level].server; - - ngx_memcpy(nonce, secret->iv.data, secret->iv.len); - ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); - - if (ngx_quic_tls_seal(ciphers.c, secret, &out, - nonce, &pkt->payload, &ad, pkt->log) - != NGX_OK) - { - return NGX_ERROR; - } - - sample = &out.data[4 - pkt->num_len]; - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) - != NGX_OK) - { - return NGX_ERROR; - } - - /* quic-tls: 5.4.1. Header Protection Application */ - ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); - - for (i = 0; i < pkt->num_len; i++) { - pnp[i] ^= mask[i + 1]; - } - - res->len = ad.len + out.len; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) { u_char *pnp, *sample; ngx_str_t ad, out; @@ -1106,15 +1046,11 @@ ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) { - if (ngx_quic_short_pkt(pkt->flags)) { - return ngx_quic_create_short_packet(pkt, res); - } - if (ngx_quic_pkt_retry(pkt->flags)) { return ngx_quic_create_retry_packet(pkt, res); } - return ngx_quic_create_long_packet(pkt, res); + return ngx_quic_create_packet(pkt, res); } -- cgit v1.2.3 From d0a06195779112e76f76c313416cf43b1210c88b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Nov 2020 21:33:16 +0000 Subject: QUIC: simplified and streamlined ngx_quic_decrypt(). Both clearflags and badflags are removed. It makes a little sense now to keep them as intermediate storage. --- src/event/ngx_event_quic_protection.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index e006f8165..64922b57f 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1057,9 +1057,8 @@ ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) { - u_char clearflags, *p, *sample; + u_char *p, *sample; size_t len; - uint8_t badflags; uint64_t pn, lpn; ngx_int_t pnl, rc, key_phase; ngx_str_t in, ad; @@ -1097,10 +1096,10 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) return NGX_DECLINED; } - clearflags = pkt->flags ^ (mask[0] & ngx_quic_pkt_hp_mask(pkt->flags)); + pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); if (ngx_quic_short_pkt(pkt->flags)) { - key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0; + key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; if (key_phase != pkt->key_phase) { secret = &pkt->keys->next_key.client; @@ -1110,14 +1109,13 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) lpn = *largest_pn; - pnl = (clearflags & 0x03) + 1; + pnl = (pkt->flags & 0x03) + 1; pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); pkt->pn = pn; - pkt->flags = clearflags; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx clearflags:%xd", clearflags); + "quic packet rx clearflags:%xd", pkt->flags); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, "quic packet rx number:%uL len:%xi", pn, pnl); @@ -1126,13 +1124,11 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) in.data = p; in.len = len - pnl; - badflags = clearflags & ngx_quic_pkt_rb_mask(pkt->flags); - ad.len = p - pkt->data; ad.data = pkt->plaintext; ngx_memcpy(ad.data, pkt->data, ad.len); - ad.data[0] = clearflags; + ad.data[0] = pkt->flags; do { ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; @@ -1160,7 +1156,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) return NGX_DECLINED; } - if (badflags) { + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { /* * An endpoint MUST treat receipt of a packet that has * a non-zero value for these bits, after removing both -- cgit v1.2.3 From 219053e3e3bd18ecb195815df0023da40dbdff9d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 18 Nov 2020 20:56:11 +0000 Subject: QUIC: rejecting zero-length packets with PROTOCOL_VIOLATION. Per the latest post draft-32 specification updates on the topic: https://github.com/quicwg/base-drafts/pull/4391 --- src/event/ngx_event_quic_protection.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 64922b57f..5637fcec5 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -1146,16 +1146,20 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, nonce, &in, &ad, pkt->log); - -#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) - ngx_quic_hexdump(pkt->log, "quic packet payload", - pkt->payload.data, pkt->payload.len); -#endif - if (rc != NGX_OK) { return NGX_DECLINED; } + if (pkt->payload.len == 0) { + /* + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { /* * An endpoint MUST treat receipt of a packet that has @@ -1169,6 +1173,11 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) return NGX_ERROR; } +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_quic_hexdump(pkt->log, "quic packet payload", + pkt->payload.data, pkt->payload.len); +#endif + *largest_pn = lpn; return NGX_OK; -- cgit v1.2.3 From 49f0b0d99d70fa4e895b939a320c29df28a34fff Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 17 Nov 2020 21:12:36 +0000 Subject: HTTP/3: finalize chunked response body chain with NULL. Unfinalized chain could result in segfault. The problem was introduced in ef83990f0e25. Patch by Andrey Kolyshkin. --- src/http/modules/ngx_http_chunked_filter_module.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index 371559e2f..138369815 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -216,6 +216,9 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->last_buf = 0; *ll = tl; + + } else { + *ll = NULL; } } else -- cgit v1.2.3 From 9129fb3db9e2f9899161e9573e7a19c774a0df6a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 17 Nov 2020 20:54:10 +0000 Subject: HTTP/3: null-terminate empty header value. Header value returned from the HTTP parser is expected to be null-terminated or have a spare byte after the value bytes. When an empty header value was passed by client in a literal header representation, neither was true. This could result in segfault. The fix is to assign a literal empty null-terminated string in this case. Thanks to Andrey Kolyshkin. --- src/http/v3/ngx_http_v3_parse.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index d5ff3cb8f..afe442464 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -746,6 +746,7 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, st->literal.length = st->pint.value; if (st->literal.length == 0) { + st->value.data = (u_char *) ""; goto done; } -- cgit v1.2.3 From 7cfc5eb11fbfe3beb9d793dbacae70fb658d46c2 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Nov 2020 17:57:43 +0000 Subject: HTTP/3: eliminated r->method_start. The field was introduced to ease parsing HTTP/3 requests. The change reduces diff to the default branch. --- src/http/ngx_http_parse.c | 7 +------ src/http/ngx_http_request.c | 8 ++++++-- src/http/ngx_http_request.h | 1 - src/http/v3/ngx_http_v3_request.c | 15 +++++++-------- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 2015f56b2..f8ec03c50 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -145,7 +145,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case sw_start: r->parse_start = p; r->request_start = p; - r->method_start = p; if (ch == CR || ch == LF) { break; @@ -160,7 +159,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) case sw_method: if (ch == ' ') { - r->method_end = p; + r->method_end = p - 1; m = r->request_start; switch (p - m) { @@ -833,10 +832,6 @@ done: r->request_end = p; } - if (r->http_protocol.data) { - r->http_protocol.len = r->request_end - r->http_protocol.data; - } - r->http_version = r->http_major * 1000 + r->http_minor; r->state = sw_start; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 3b9e59005..a60c5758f 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1162,8 +1162,12 @@ ngx_http_process_request_line(ngx_event_t *rev) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); - r->method_name.len = r->method_end - r->method_start; - r->method_name.data = r->method_start; + r->method_name.len = r->method_end - r->request_start + 1; + r->method_name.data = r->request_line.data; + + if (r->http_protocol.data) { + r->http_protocol.len = r->request_end - r->http_protocol.data; + } if (ngx_http_process_request_uri(r) != NGX_OK) { break; diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 3d33d93d4..9a610e805 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -585,7 +585,6 @@ struct ngx_http_request_s { u_char *args_start; u_char *request_start; u_char *request_end; - u_char *method_start; u_char *method_end; u_char *schema_start; u_char *schema_end; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5511e3031..2ff0440d9 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -129,11 +129,9 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) continue; } - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - len = (r->method_end - r->method_start) + 1 + len = r->method_name.len + 1 + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3") - 1; + + sizeof("HTTP/3.0") - 1; p = ngx_pnalloc(c->pool, len); if (p == NULL) { @@ -142,11 +140,13 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) r->request_start = p; - p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + r->method_end = p - 1; *p++ = ' '; p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); *p++ = ' '; - p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + r->http_protocol.data = p; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); r->request_end = p; r->state = 0; @@ -309,8 +309,7 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, c = r->connection; if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { - r->method_start = value->data; - r->method_end = value->data + value->len; + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) / sizeof(ngx_http_v3_methods[0]); i++) -- cgit v1.2.3 From 153aaff1ee74ee8450c3c8ac1a9432abc0349149 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 27 Nov 2020 18:43:36 +0300 Subject: QUIC: removed ngx_quic_hexdump() macro. Instead, appropriate format specifier for hexadecimal is used in ngx_log_debug(). The STREAM frame "data" debug is moved into ngx_quic_log_frame(), similar to all other frame fields debug. --- src/event/ngx_event_quic.c | 67 +++++++++++++++++++++++------------ src/event/ngx_event_quic.h | 26 -------------- src/event/ngx_event_quic_protection.c | 29 +++++++++------ src/event/ngx_event_quic_transport.c | 9 ++--- 4 files changed, 65 insertions(+), 66 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 2d19ebea4..0f59f73ea 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -505,6 +505,11 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) p = ngx_slprintf(p, last, " fin:1"); } +#ifdef NGX_QUIC_DEBUG_FRAMES + p = ngx_slprintf(p, last, " data len:%uL %*xs", f->u.stream.length, + (size_t) f->u.stream.length, f->u.stream.data); +#endif + break; case NGX_QUIC_FT_MAX_DATA: @@ -669,7 +674,9 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_read_secret() level:%d", level); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); #endif return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, @@ -691,7 +698,9 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_write_secret() level:%d", level); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); #endif return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, @@ -715,7 +724,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_set_encryption_secrets() level:%d", level); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); #endif cipher = SSL_get_current_cipher(ssl_conn); @@ -732,7 +743,9 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); #endif return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, @@ -1226,7 +1239,8 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) len = ngx_quic_create_version_negotiation(&pkt, buf); #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic vnego packet to send", buf, len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); #endif (void) c->send(c, buf, len); @@ -1242,9 +1256,9 @@ ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) return NGX_ERROR; } - ngx_quic_hexdump(c->log, "quic create server id", - id, NGX_QUIC_SERVER_CID_LEN); - + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create server id %*xs", + (size_t) NGX_QUIC_SERVER_CID_LEN, id); return NGX_OK; } @@ -1280,7 +1294,8 @@ ngx_quic_send_retry(ngx_connection_t *c) } #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic packet to send", res.data, res.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); #endif len = c->send(c, res.data, res.len); @@ -1398,7 +1413,8 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) EVP_CIPHER_CTX_free(ctx); #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic new token", token->data, token->len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new token len:%uz %xV", token->len, token); #endif return NGX_OK; @@ -1568,8 +1584,9 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } - ngx_quic_hexdump(c->log, "quic stateless reset token", - qc->tp.sr_token, (size_t) NGX_QUIC_SR_TOKEN_LEN); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); /* always succeeds */ @@ -1585,7 +1602,8 @@ ngx_quic_init_connection(ngx_connection_t *c) } #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_quic_hexdump(c->log, "quic transport parameters", p, len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); #endif if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { @@ -2073,17 +2091,21 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc = ngx_quic_get_connection(c); -#if (NGX_DEBUG) - ngx_quic_hexdump(c->log, "quic packet rx dcid", - pkt->dcid.data, pkt->dcid.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx dcid len:%uz %xV", + pkt->dcid.len, &pkt->dcid); +#if (NGX_DEBUG) if (pkt->level != ssl_encryption_application) { - ngx_quic_hexdump(c->log, "quic packet rx scid", pkt->scid.data, - pkt->scid.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx scid len:%uz %xV", + pkt->scid.len, &pkt->scid); } if (pkt->level == ssl_encryption_initial) { - ngx_quic_hexdump(c->log, "quic token", pkt->token.data, pkt->token.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic token len:%uz %xV", + pkt->token.len, &pkt->token); } #endif @@ -4520,10 +4542,9 @@ ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) ngx_insert_udp_connection(c, &sid->udp, &dcid); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic insert server id seqnum:%uL", sid->seqnum); - - ngx_quic_hexdump(c->log, "quic server id", id->data, id->len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic insert server id seqnum:%uL id len:%uz %xV", + sid->seqnum, id->len, id); return sid; } diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index db24b6642..a6f5f4fba 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -138,30 +138,4 @@ ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, /* #define NGX_QUIC_DEBUG_FRAMES_ALLOC */ /* log frames alloc/reuse/free */ /* #define NGX_QUIC_DEBUG_CRYPTO */ -#if (NGX_DEBUG) - -#define ngx_quic_hexdump(log, fmt, data, len) \ - ngx_quic_hexdump_real(log, fmt, (u_char *) data, (size_t) len) - -static ngx_inline -void ngx_quic_hexdump_real(ngx_log_t *log, const char *label, u_char *data, - size_t len) -{ - ngx_int_t m; - u_char buf[2048]; - - if (log->log_level & NGX_LOG_DEBUG_EVENT) { - m = ngx_hex_dump(buf, data, (len > 1024) ? 1024 : len) - buf; - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, - "%s len:%uz data:%*s%s", - label, len, m, buf, len < 2048 ? "" : "..."); - } -} - -#else - -#define ngx_quic_hexdump(log, fmt, data, len) - -#endif - #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index 5637fcec5..db9068d69 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -183,8 +183,10 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, "quic ngx_quic_set_initial_secret"); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pool->log, "quic salt", salt, sizeof(salt)); - ngx_quic_hexdump(pool->log, "quic initial secret", is, is_len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic initial secret len:%uz %*xs", is_len, is_len, is); #endif /* draft-ietf-quic-tls-23#section-5.2 */ @@ -292,8 +294,8 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pool->log, 0, "quic expand %V", label); - ngx_quic_hexdump(pool->log, "quic key", out->data, out->len); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic expand %V key len:%uz %xV", label, out->len, out); #endif return NGX_OK; @@ -840,7 +842,8 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); #endif if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) @@ -910,7 +913,8 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) itag.len = EVP_GCM_TLS_TAG_LEN; #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic retry itag len:%uz %xV", ad.len, &ad); #endif if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { @@ -982,8 +986,9 @@ ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); #if (NGX_DEBUG) - ngx_quic_hexdump(c->log, "quic stateless reset token", token, - (size_t) NGX_QUIC_SR_TOKEN_LEN); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); #endif return NGX_OK; @@ -1138,7 +1143,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); #endif pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; @@ -1174,8 +1180,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) } #if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) - ngx_quic_hexdump(pkt->log, "quic packet payload", - pkt->payload.data, pkt->payload.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet payload len:%uz %xV", + pkt->payload.len, &pkt->payload); #endif *largest_pn = lpn; diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 9f0485d54..4c852436c 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -920,10 +920,6 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } -#ifdef NGX_QUIC_DEBUG_FRAMES - ngx_quic_hexdump(pkt->log, "quic STREAM frame", - f->u.stream.data, f->u.stream.length); -#endif break; case NGX_QUIC_FT_MAX_DATA: @@ -1649,8 +1645,9 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, tp->active_connection_id_limit); #if (NGX_QUIC_DRAFT_VERSION >= 28) - ngx_quic_hexdump(log, "quic tp initial_source_connection_id:", - tp->initial_scid.data, tp->initial_scid.len); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial source_connection_id len:%uz %xV", + tp->initial_scid.len, &tp->initial_scid); #endif return NGX_OK; -- cgit v1.2.3 From 3b8dbfcab4643fbfd3f276d3e9302b406e07f7fc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 2 Dec 2020 10:55:49 +0300 Subject: QUIC: fixed send contexts cleanup. The ngx_quic_get_send_ctx() macro takes 'level' argument, not send context index. --- src/event/ngx_event_quic.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 0f59f73ea..e1ab107b1 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1779,8 +1779,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) /* drop packets from retransmit queues, no ack is expected */ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ctx = ngx_quic_get_send_ctx(qc, i); - ngx_quic_free_frames(c, &ctx->sent); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } if (rc == NGX_DONE) { -- cgit v1.2.3 From 4b440cbf97af3ffe0ab31cb083fb1ce5b0fb5f89 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Nov 2020 17:46:21 +0000 Subject: HTTP/3: introduced ngx_http_v3_filter. The filter is responsible for creating HTTP/3 response header and body. The change removes differences to the default branch for ngx_http_chunked_filter_module and ngx_http_header_filter_module. --- auto/modules | 13 + src/http/modules/ngx_http_chunked_filter_module.c | 50 +- src/http/ngx_http_header_filter_module.c | 28 +- src/http/v3/ngx_http_v3.h | 2 - src/http/v3/ngx_http_v3_filter_module.c | 1360 +++++++++++++++++++++ src/http/v3/ngx_http_v3_request.c | 1133 ----------------- 6 files changed, 1382 insertions(+), 1204 deletions(-) create mode 100644 src/http/v3/ngx_http_v3_filter_module.c diff --git a/auto/modules b/auto/modules index 205023d3a..3f2430b62 100644 --- a/auto/modules +++ b/auto/modules @@ -119,6 +119,7 @@ if [ $HTTP = YES ]; then # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -151,6 +152,7 @@ if [ $HTTP = YES ]; then ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -212,6 +214,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index 138369815..4d6fd3eed 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -106,7 +106,6 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; off_t size; - size_t n; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; @@ -162,68 +161,27 @@ ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in) chunk = b->start; if (chunk == NULL) { + /* the "0000000000000000" is 64-bit hexadecimal string */ -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - n = NGX_HTTP_V3_VARLEN_INT_LEN * 2; - - } else -#endif - { - /* the "0000000000000000" is 64-bit hexadecimal string */ - n = sizeof("0000000000000000" CRLF) - 1; - } - - chunk = ngx_palloc(r->pool, n); + chunk = ngx_palloc(r->pool, sizeof("0000000000000000" CRLF) - 1); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; - b->end = chunk + n; + b->end = chunk + sizeof("0000000000000000" CRLF) - 1; } b->tag = (ngx_buf_tag_t) &ngx_http_chunked_filter_module; b->memory = 0; b->temporary = 1; b->pos = chunk; - -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, - NGX_HTTP_V3_FRAME_DATA); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); - - } else -#endif - { - b->last = ngx_sprintf(chunk, "%xO" CRLF, size); - } + b->last = ngx_sprintf(chunk, "%xO" CRLF, size); tl->next = out; out = tl; } -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - - if (cl->buf->last_buf) { - tl = ngx_http_v3_create_trailers(r); - if (tl == NULL) { - return NGX_ERROR; - } - - cl->buf->last_buf = 0; - - *ll = tl; - - } else { - *ll = NULL; - } - - } else -#endif - if (cl->buf->last_buf) { tl = ngx_http_chunked_create_trailers(r, ctx); if (tl == NULL) { diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index 85edf3657..9b8940590 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -187,29 +187,6 @@ ngx_http_header_filter(ngx_http_request_t *r) r->header_only = 1; } - if (r->headers_out.status_line.len == 0) { - if (r->headers_out.status == NGX_HTTP_NO_CONTENT - || r->headers_out.status == NGX_HTTP_NOT_MODIFIED) - { - r->header_only = 1; - } - } - -#if (NGX_HTTP_V3) - - if (r->http_version == NGX_HTTP_VERSION_30) { - ngx_chain_t *cl; - - cl = ngx_http_v3_create_header(r); - if (cl == NULL) { - return NGX_ERROR; - } - - return ngx_http_write_filter(r, cl); - } - -#endif - if (r->headers_out.last_modified_time != -1) { if (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT @@ -243,6 +220,7 @@ ngx_http_header_filter(ngx_http_request_t *r) /* 2XX */ if (status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; ngx_str_null(&r->headers_out.content_type); r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; @@ -259,6 +237,10 @@ ngx_http_header_filter(ngx_http_request_t *r) { /* 3XX */ + if (status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; status_line = &ngx_http_status_lines[status]; len += ngx_http_status_lines[status].len; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 949fc2206..89ed9c98a 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -140,8 +140,6 @@ ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t allow_underscores); ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_http_chunked_t *ctx); -ngx_chain_t *ngx_http_v3_create_header(ngx_http_request_t *r); -ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r); uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c new file mode 100644 index 000000000..9a2313a14 --- /dev/null +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -0,0 +1,1360 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#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_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; +} ngx_http_v3_filter_ctx_t; + + +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, + ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, + ngx_str_t *path, ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_create_push_request( + ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, + const char *name, ngx_str_t *value); +static void ngx_http_v3_push_request_handler(ngx_event_t *ev); +static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, + ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); +static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* 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_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_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 +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + u_char *p; + size_t len, n; + ngx_buf_t *b; + ngx_str_t host; + ngx_uint_t i, port; + ngx_chain_t *out, *hl, *cl, **ll; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_v3_filter_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_header_filter(r); + } + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.last_modified_time != -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + } + + if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; + ngx_str_null(&r->headers_out.content_type); + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + } + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + + c = r->connection; + + out = NULL; + ll = &out; + + if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + && r->method != NGX_HTTP_HEAD) + { + if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { + return NGX_ERROR; + } + } + + len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + } + + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + r->headers_out.location->hash = 0; + + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + n = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + n += sizeof(":65535") - 1; + } + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, n); + + } else { + ngx_str_null(&host); + port = 0; + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.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 (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_header_l(NULL, &header[i].key, + &header[i].value); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); + } + + if (r->headers_out.date == NULL) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + + p = b->last; + b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + b->last = ngx_cpymem(b->last, "; charset=", + sizeof("; charset=") - 1); + b->last = ngx_cpymem(b->last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = b->last - p; + r->headers_out.content_type.data = p; + } + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); + + } else if (r->headers_out.content_length_n == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + + b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); + } + + if (host.data) { + n = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (port) { + n += ngx_sprintf(b->last, ":%ui", port) - b->last; + } + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + NULL, n); + + p = b->last; + b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); + b->last = ngx_cpymem(b->last, host.data, host.len); + + if (port) { + b->last = ngx_sprintf(b->last, ":%ui", port); + } + + b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = b->last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + } +#endif + + part = &r->headers_out.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 (header[i].hash == 0) { + continue; + } + + b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, + &header[i].key, + &header[i].value); + } + + if (r->header_only) { + b->last_buf = 1; + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(c->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(c->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + *ll = hl; + ll = &cl->next; + + if (r->headers_out.content_length_n >= 0 && !r->header_only) { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(c->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, + r->headers_out.content_length_n); + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + } else { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t +ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) +{ + u_char *start, *end, *last; + ngx_str_t path; + ngx_int_t rc; + ngx_uint_t i, push; + ngx_table_elt_t **h; + ngx_http_v3_loc_conf_t *h3lcf; + ngx_http_complex_value_t *pushes; + + h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); + + if (h3lcf->pushes) { + pushes = h3lcf->pushes->elts; + + for (i = 0; i < h3lcf->pushes->nelts; i++) { + + if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { + return NGX_ERROR; + } + + if (path.len == 0) { + continue; + } + + if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { + continue; + } + + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + } + + if (!h3lcf->push_preload) { + return NGX_OK; + } + + h = r->headers_out.link.elts; + + for (i = 0; i < r->headers_out.link.nelts; i++) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 parse link: \"%V\"", &h[i]->value); + + start = h[i]->value.data; + end = h[i]->value.data + h[i]->value.len; + + next_link: + + while (start < end && *start == ' ') { start++; } + + if (start == end || *start++ != '<') { + continue; + } + + while (start < end && *start == ' ') { start++; } + + for (last = start; last < end && *last != '>'; last++) { + /* void */ + } + + if (last == start || last == end) { + continue; + } + + path.len = last - start; + path.data = start; + + start = last + 1; + + while (start < end && *start == ' ') { start++; } + + if (start == end) { + continue; + } + + if (*start == ',') { + start++; + goto next_link; + } + + if (*start++ != ';') { + continue; + } + + last = ngx_strlchr(start, end, ','); + + if (last == NULL) { + last = end; + } + + push = 0; + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 6 + && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) + { + start += 6; + + if (start == last || *start == ' ' || *start == ';') { + push = 0; + break; + } + + goto next_param; + } + + if (last - start >= 11 + && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) + { + start += 11; + + if (start == last || *start == ' ' || *start == ';') { + push = 1; + } + + goto next_param; + } + + if (last - start >= 4 + && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) + { + start += 4; + + while (start < last && *start == ' ') { start++; } + + if (start == last || *start++ != '"') { + goto next_param; + } + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 7 + && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) + { + start += 7; + + if (start < last && (*start == ' ' || *start == '"')) { + push = 1; + break; + } + } + + while (start < last && *start != ' ' && *start != '"') { + start++; + } + + if (start == last) { + break; + } + + if (*start == '"') { + break; + } + + start++; + } + } + + next_param: + + start = ngx_strlchr(start, last, ';'); + + if (start == NULL) { + break; + } + + start++; + } + + if (push) { + while (path.len && path.data[path.len - 1] == ' ') { + path.len--; + } + } + + if (push && path.len + && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) + { + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + + if (last < end) { + start = last + 1; + goto next_link; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, + ngx_chain_t ***ll) +{ + uint64_t push_id; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = r->connection; + h3c = c->quic->parent->data; + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", + path, h3c->npushing, h3scf->max_concurrent_pushes, + h3c->next_push_id, h3c->max_push_id); + + if (!ngx_path_separator(path->data[0])) { + ngx_log_error(NGX_LOG_WARN, c->log, 0, + "non-absolute path \"%V\" not pushed", path); + return NGX_DECLINED; + } + + if (h3c->next_push_id > h3c->max_push_id) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_push_id"); + return NGX_ABORT; + } + + if (h3c->npushing >= h3scf->max_concurrent_pushes) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_concurrent_pushes"); + return NGX_ABORT; + } + + push_id = h3c->next_push_id++; + + rc = ngx_http_v3_create_push_request(r, path, push_id); + if (rc != NGX_OK) { + return rc; + } + + cl = ngx_http_v3_create_push_promise(r, path, push_id); + if (cl == NULL) { + return NGX_ERROR; + } + + for (**ll = cl; **ll; *ll = &(**ll)->next); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, + uint64_t push_id) +{ + ngx_pool_t *pool; + ngx_connection_t *c, *pc; + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_connection_t *h3c; + + pc = pr->connection; + + r = NULL; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "http3 create push request id:%uL", push_id); + + c = ngx_http_v3_create_push_stream(pc, push_id); + if (c == NULL) { + return NGX_ABORT; + } + + hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + goto failed; + } + + h3c = c->quic->parent->data; + ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); + c->data = hc; + + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + goto failed; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log->handler = ngx_http_log_error; + c->log->data = ctx; + c->log->action = "processing pushed request headers"; + + c->log_error = NGX_ERROR_INFO; + + r = ngx_http_create_request(c); + if (r == NULL) { + goto failed; + } + + c->data = r; + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + r->method_name = ngx_http_core_get_method; + r->method = NGX_HTTP_GET; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + goto failed; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + goto failed; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + r->schema.data = ngx_pstrdup(r->pool, &pr->schema); + if (r->schema.data == NULL) { + goto failed; + } + + r->schema.len = pr->schema.len; + + r->uri_start = ngx_pstrdup(r->pool, path); + if (r->uri_start == NULL) { + goto failed; + } + + r->uri_end = r->uri_start + path->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) + != NGX_OK) + { + goto failed; + } + + if (pr->headers_in.accept_encoding) { + if (ngx_http_v3_set_push_header(r, "accept-encoding", + &pr->headers_in.accept_encoding->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.accept_language) { + if (ngx_http_v3_set_push_header(r, "accept-language", + &pr->headers_in.accept_language->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.user_agent) { + if (ngx_http_v3_set_push_header(r, "user-agent", + &pr->headers_in.user_agent->value) + != NGX_OK) + { + goto failed; + } + } + + c->read->handler = ngx_http_v3_push_request_handler; + c->read->handler = ngx_http_v3_push_request_handler; + + ngx_post_event(c->read, &ngx_posted_events); + + return NGX_OK; + +failed: + + if (r) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, + ngx_str_t *value) +{ + u_char *p; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 push header \"%s\": \"%V\"", name, value); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + p = ngx_pnalloc(r->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) name; + h->key.len = ngx_strlen(name); + h->hash = ngx_hash_key(h->key.data, h->key.len); + h->lowcase_key = (u_char *) name; + h->value.data = p; + h->value.len = value->len; + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_push_request_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + c = ev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); + + ngx_http_process_request(r); +} + + +static ngx_chain_t * +ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, + uint64_t push_id) +{ + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create push promise id:%uL", push_id); + + len = ngx_http_v3_encode_varlen_int(NULL, push_id); + + len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + NULL, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, NULL, + r->headers_in.user_agent->value.len); + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); + + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); + + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + r->headers_in.server.data, + r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + path->data, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + r->schema.data, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, + r->headers_in.accept_encoding->value.data, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, + r->headers_in.accept_language->value.data, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, + r->headers_in.user_agent->value.data, + r->headers_in.user_agent->value.len); + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_PUSH_PROMISE); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *chunk; + off_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_filter_ctx_t *ctx; + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + out = NULL; + ll = &out; + + size = 0; + cl = in; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 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) { + chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = out; + out = tl; + } + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + + } else { + *ll = NULL; + } + + rc = ngx_http_next_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_filter_module); + + return rc; +} + + +static ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create trailers"); + + /* XXX */ + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->last_buf = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + return cl; +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_v3_body_filter; + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2ff0440d9..2b50133f1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,38 +10,8 @@ #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_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 - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, - ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_create_push_request( - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, - const char *name, ngx_str_t *value); -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, - ngx_str_t *path, uint64_t push_id); struct { @@ -443,1106 +413,3 @@ failed: return NGX_ERROR; } - - -ngx_chain_t * -ngx_http_v3_create_header(ngx_http_request_t *r) -{ - u_char *p; - size_t len, n; - ngx_buf_t *b; - ngx_str_t host; - ngx_uint_t i, port; - ngx_chain_t *out, *hl, *cl, **ll; - ngx_list_part_t *part; - ngx_table_elt_t *header; - ngx_connection_t *c; - ngx_http_core_loc_conf_t *clcf; - ngx_http_core_srv_conf_t *cscf; - u_char addr[NGX_SOCKADDR_STRLEN]; - - c = r->connection; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - - out = NULL; - ll = &out; - - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { - return NULL; - } - } - - len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); - - if (r->headers_out.status == NGX_HTTP_OK) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200); - - } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200, - NULL, 3); - } - - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - - if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - n = sizeof(NGINX_VER) - 1; - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - n = sizeof(NGINX_VER_BUILD) - 1; - - } else { - n = sizeof("nginx") - 1; - } - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SERVER, - NULL, n); - } - - if (r->headers_out.date == NULL) { - len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, - NULL, ngx_cached_http_time.len); - } - - if (r->headers_out.content_type.len) { - n = r->headers_out.content_type.len; - - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { - n += sizeof("; charset=") - 1 + r->headers_out.charset.len; - } - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, - NULL, n); - } - - if (r->headers_out.content_length == NULL) { - if (r->headers_out.content_length_n > 0) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, - NULL, NGX_OFF_T_LEN); - - } else if (r->headers_out.content_length_n == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); - } - } - - if (r->headers_out.last_modified == NULL - && r->headers_out.last_modified_time != -1) - { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, - sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); - } - - if (r->headers_out.location - && r->headers_out.location->value.len - && r->headers_out.location->value.data[0] == '/' - && clcf->absolute_redirect) - { - r->headers_out.location->hash = 0; - - if (clcf->server_name_in_redirect) { - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - host = cscf->server_name; - - } else if (r->headers_in.server.len) { - host = r->headers_in.server; - - } else { - host.len = NGX_SOCKADDR_STRLEN; - host.data = addr; - - if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { - return NULL; - } - } - - port = ngx_inet_get_port(c->local_sockaddr); - - n = sizeof("https://") - 1 + host.len - + r->headers_out.location->value.len; - - if (clcf->port_in_redirect) { - port = (port == 443) ? 0 : port; - - } else { - port = 0; - } - - if (port) { - n += sizeof(":65535") - 1; - } - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_LOCATION, NULL, n); - - } else { - ngx_str_null(&host); - port = 0; - } - -#if (NGX_HTTP_GZIP) - if (r->gzip_vary) { - if (clcf->gzip_vary) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); - - } else { - r->gzip_vary = 0; - } - } -#endif - - part = &r->headers_out.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 (header[i].hash == 0) { - continue; - } - - len += ngx_http_v3_encode_header_l(NULL, &header[i].key, - &header[i].value); - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); - - if (r->headers_out.status == NGX_HTTP_OK) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_STATUS_200); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_STATUS_200, - NULL, 3); - b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); - } - - if (r->headers_out.server == NULL) { - if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { - p = (u_char *) NGINX_VER; - n = sizeof(NGINX_VER) - 1; - - } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { - p = (u_char *) NGINX_VER_BUILD; - n = sizeof(NGINX_VER_BUILD) - 1; - - } else { - p = (u_char *) "nginx"; - n = sizeof("nginx") - 1; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SERVER, - p, n); - } - - if (r->headers_out.date == NULL) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_DATE, - ngx_cached_http_time.data, - ngx_cached_http_time.len); - } - - if (r->headers_out.content_type.len) { - n = r->headers_out.content_type.len; - - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { - n += sizeof("; charset=") - 1 + r->headers_out.charset.len; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, - NULL, n); - - p = b->last; - b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, - r->headers_out.content_type.len); - - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { - b->last = ngx_cpymem(b->last, "; charset=", - sizeof("; charset=") - 1); - b->last = ngx_cpymem(b->last, r->headers_out.charset.data, - r->headers_out.charset.len); - - /* update r->headers_out.content_type for possible logging */ - - r->headers_out.content_type.len = b->last - p; - r->headers_out.content_type.data = p; - } - } - - if (r->headers_out.content_length == NULL) { - if (r->headers_out.content_length_n > 0) { - p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); - n = p - b->last; - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, - NULL, n); - - b->last = ngx_sprintf(b->last, "%O", - r->headers_out.content_length_n); - - } else if (r->headers_out.content_length_n == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); - } - } - - if (r->headers_out.last_modified == NULL - && r->headers_out.last_modified_time != -1) - { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, - sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); - - b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); - } - - if (host.data) { - n = sizeof("https://") - 1 + host.len - + r->headers_out.location->value.len; - - if (port) { - n += ngx_sprintf(b->last, ":%ui", port) - b->last; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LOCATION, - NULL, n); - - p = b->last; - b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); - b->last = ngx_cpymem(b->last, host.data, host.len); - - if (port) { - b->last = ngx_sprintf(b->last, ":%ui", port); - } - - b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, - r->headers_out.location->value.len); - - /* update r->headers_out.location->value for possible logging */ - - r->headers_out.location->value.len = b->last - p; - r->headers_out.location->value.data = p; - ngx_str_set(&r->headers_out.location->key, "Location"); - } - -#if (NGX_HTTP_GZIP) - if (r->gzip_vary) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); - } -#endif - - part = &r->headers_out.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 (header[i].hash == 0) { - continue; - } - - b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, - &header[i].key, - &header[i].value); - } - - if (r->header_only) { - b->last_buf = 1; - } - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(c->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, - NGX_HTTP_V3_FRAME_HEADERS); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - - hl = ngx_alloc_chain_link(c->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - *ll = hl; - ll = &cl->next; - - if (r->headers_out.content_length_n >= 0 && !r->header_only) { - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) - + ngx_http_v3_encode_varlen_int(NULL, - r->headers_out.content_length_n); - - b = ngx_create_temp_buf(c->pool, len); - if (b == NULL) { - return NULL; - } - - 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, - r->headers_out.content_length_n); - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - *ll = cl; - } - - return out; -} - - -ngx_chain_t * -ngx_http_v3_create_trailers(ngx_http_request_t *r) -{ - ngx_buf_t *b; - ngx_chain_t *cl; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create trailers"); - - /* XXX */ - - b = ngx_calloc_buf(r->pool); - if (b == NULL) { - return NULL; - } - - b->last_buf = 1; - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - return cl; -} - - -static ngx_int_t -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) -{ - u_char *start, *end, *last; - ngx_str_t path; - ngx_int_t rc; - ngx_uint_t i, push; - ngx_table_elt_t **h; - ngx_http_v3_loc_conf_t *h3lcf; - ngx_http_complex_value_t *pushes; - - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); - - if (h3lcf->pushes) { - pushes = h3lcf->pushes->elts; - - for (i = 0; i < h3lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h3lcf->push_preload) { - return NGX_OK; - } - - h = r->headers_out.link.elts; - - for (i = 0; i < r->headers_out.link.nelts; i++) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 parse link: \"%V\"", &h[i]->value); - - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_chain_t ***ll) -{ - uint64_t push_id; - ngx_int_t rc; - ngx_chain_t *cl; - ngx_connection_t *c; - ngx_http_v3_srv_conf_t *h3scf; - ngx_http_v3_connection_t *h3c; - - c = r->connection; - h3c = c->quic->parent->data; - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", - path, h3c->npushing, h3scf->max_concurrent_pushes, - h3c->next_push_id, h3c->max_push_id); - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, c->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - if (h3c->next_push_id > h3c->max_push_id) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_push_id"); - return NGX_ABORT; - } - - if (h3c->npushing >= h3scf->max_concurrent_pushes) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_concurrent_pushes"); - return NGX_ABORT; - } - - push_id = h3c->next_push_id++; - - rc = ngx_http_v3_create_push_request(r, path, push_id); - if (rc != NGX_OK) { - return rc; - } - - cl = ngx_http_v3_create_push_promise(r, path, push_id); - if (cl == NULL) { - return NGX_ERROR; - } - - for (**ll = cl; **ll; *ll = &(**ll)->next); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, - uint64_t push_id) -{ - ngx_pool_t *pool; - ngx_connection_t *c, *pc; - ngx_http_request_t *r; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc; - ngx_http_core_srv_conf_t *cscf; - ngx_http_v3_connection_t *h3c; - - pc = pr->connection; - - r = NULL; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "http3 create push request id:%uL", push_id); - - c = ngx_http_v3_create_push_stream(pc, push_id); - if (c == NULL) { - return NGX_ABORT; - } - - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); - if (hc == NULL) { - goto failed; - } - - h3c = c->quic->parent->data; - ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); - c->data = hc; - - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); - if (ctx == NULL) { - goto failed; - } - - ctx->connection = c; - ctx->request = NULL; - ctx->current_request = NULL; - - c->log->handler = ngx_http_log_error; - c->log->data = ctx; - c->log->action = "processing pushed request headers"; - - c->log_error = NGX_ERROR_INFO; - - r = ngx_http_create_request(c); - if (r == NULL) { - goto failed; - } - - c->data = r; - - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - r->header_in = ngx_create_temp_buf(r->pool, - cscf->client_header_buffer_size); - if (r->header_in == NULL) { - goto failed; - } - - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - goto failed; - } - - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; - - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); - if (r->schema.data == NULL) { - goto failed; - } - - r->schema.len = pr->schema.len; - - r->uri_start = ngx_pstrdup(r->pool, path); - if (r->uri_start == NULL) { - goto failed; - } - - r->uri_end = r->uri_start + path->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - goto failed; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - goto failed; - } - - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) - != NGX_OK) - { - goto failed; - } - - if (pr->headers_in.accept_encoding) { - if (ngx_http_v3_set_push_header(r, "accept-encoding", - &pr->headers_in.accept_encoding->value) - != NGX_OK) - { - goto failed; - } - } - - if (pr->headers_in.accept_language) { - if (ngx_http_v3_set_push_header(r, "accept-language", - &pr->headers_in.accept_language->value) - != NGX_OK) - { - goto failed; - } - } - - if (pr->headers_in.user_agent) { - if (ngx_http_v3_set_push_header(r, "user-agent", - &pr->headers_in.user_agent->value) - != NGX_OK) - { - goto failed; - } - } - - c->read->handler = ngx_http_v3_push_request_handler; - c->read->handler = ngx_http_v3_push_request_handler; - - ngx_post_event(c->read, &ngx_posted_events); - - return NGX_OK; - -failed: - - if (r) { - ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - } - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, - ngx_str_t *value) -{ - u_char *p; - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 push header \"%s\": \"%V\"", name, value); - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - p = ngx_pnalloc(r->pool, value->len + 1); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, value->data, value->len); - p[value->len] = '\0'; - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - - h->key.data = (u_char *) name; - h->key.len = ngx_strlen(name); - h->hash = ngx_hash_key(h->key.data, h->key.len); - h->lowcase_key = (u_char *) name; - h->value.data = p; - h->value.len = value->len; - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static void -ngx_http_v3_push_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - ngx_http_request_t *r; - - c = ev->data; - r = c->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); - - ngx_http_process_request(r); -} - - -static ngx_chain_t * -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, - uint64_t push_id) -{ - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create push promise id:%uL", push_id); - - len = ngx_http_v3_encode_varlen_int(NULL, push_id); - - len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); - - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - NULL, r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - NULL, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - NULL, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, - r->headers_in.user_agent->value.len); - } - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); - - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); - - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - r->headers_in.server.data, - r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - path->data, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - r->schema.data, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, - r->headers_in.accept_encoding->value.data, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, - r->headers_in.accept_language->value.data, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, - r->headers_in.user_agent->value.data, - r->headers_in.user_agent->value.len); - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, - NGX_HTTP_V3_FRAME_PUSH_PROMISE); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - - hl = ngx_alloc_chain_link(r->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - return hl; -} -- cgit v1.2.3 From 90ec7ef6dbc5e64a3eeafc5f6b79a06e830cedea Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 4 Dec 2020 15:19:03 +0300 Subject: QUIC: fixed missing quic flag on listener in the stream module. --- src/stream/ngx_stream.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index bc4aa09a3..1ef689c17 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -514,6 +514,9 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ls->reuseport = addr[i].opt.reuseport; #endif +#if (NGX_STREAM_QUIC) + ls->quic = addr[i].opt.quic; +#endif stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); if (stport == NULL) { return NGX_CONF_ERROR; -- cgit v1.2.3 From 1d748f1ca351d255a7d256c7a99819e96f5c6a7c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sun, 6 Dec 2020 14:24:38 +0000 Subject: QUIC: disabling bidirectional SSL shutdown earlier. Notably, this fixes an issue with Chrome that can emit a "certificate_unknown" alert during the SSL handshake where c->ssl->no_wait_shutdown is not yet set. --- src/event/ngx_event_quic.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index e1ab107b1..19ee17aaf 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -1563,6 +1563,8 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } + c->ssl->no_wait_shutdown = 1; + ssl_conn = c->ssl->connection; if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { @@ -3726,7 +3728,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) "quic handshake completed successfully"); c->ssl->handshaked = 1; - c->ssl->no_wait_shutdown = 1; frame = ngx_quic_alloc_frame(c, 0); if (frame == NULL) { -- cgit v1.2.3 From ed203729adfd3982072187a2e78f6b6437a48c93 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 7 Dec 2020 14:06:00 +0300 Subject: QUIC: fixed handling of clients connected to wildcard address. The patch replaces c->send() occurences with c->send_chain(), because the latter accounts for the local address, which may be different if the wildcard listener is used. Previously, server sent response to client using address different from one client connected to. --- src/event/ngx_event_quic.c | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 19ee17aaf..69d0ea4de 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -333,6 +333,7 @@ static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); @@ -1171,7 +1172,7 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - (void) c->send(c, buf, len); + (void) ngx_quic_send(c, buf, len); return NGX_DECLINED; } @@ -1243,7 +1244,7 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) "quic vnego packet to send len:%uz %*xs", len, len, buf); #endif - (void) c->send(c, buf, len); + (void) ngx_quic_send(c, buf, len); return NGX_ERROR; } @@ -1298,8 +1299,8 @@ ngx_quic_send_retry(ngx_connection_t *c) "quic packet to send len:%uz %xV", res.len, &res); #endif - len = c->send(c, res.data, res.len); - if (len == NGX_ERROR || (size_t) len != res.len) { + len = ngx_quic_send(c, res.data, res.len); + if (len == NGX_ERROR) { return NGX_ERROR; } @@ -4906,8 +4907,8 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return NGX_ERROR; } - len = c->send(c, res.data, res.len); - if (len == NGX_ERROR || (size_t) len != res.len) { + len = ngx_quic_send(c, res.data, res.len); + if (len == NGX_ERROR) { ngx_quic_free_frames(c, frames); return NGX_ERROR; } @@ -4946,6 +4947,31 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) +{ + ngx_buf_t b; + ngx_chain_t cl, *res; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.pos = b.start = buf; + b.last = b.end = buf + len; + b.last_buf = 1; + b.temporary = 1; + + cl.buf = &b; + cl.next= NULL; + + res = c->send_chain(c, &cl, 0); + if (res == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return len; +} + + static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) { -- cgit v1.2.3 From ec99ccee3647357c21635702e55788897da6d9e8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 1 Dec 2020 19:11:01 +0000 Subject: QUIC: introduced QUIC buffers. Buffers are used to hold frame data. They have a fixed size and are reused after being freed. --- README | 2 +- src/event/ngx_event_quic.c | 438 ++++++++++++++++++++++++++--------- src/event/ngx_event_quic.h | 4 +- src/event/ngx_event_quic_transport.c | 76 ++++-- src/event/ngx_event_quic_transport.h | 7 +- 5 files changed, 381 insertions(+), 146 deletions(-) diff --git a/README b/README index ce3c54b84..40ee3907f 100644 --- a/README +++ b/README @@ -237,7 +237,7 @@ Example configuration: #define NGX_QUIC_DEBUG_PACKETS #define NGX_QUIC_DEBUG_FRAMES - #define NGX_QUIC_DEBUG_FRAMES_ALLOC + #define NGX_QUIC_DEBUG_ALLOC #define NGX_QUIC_DEBUG_CRYPTO 6. Contributing diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 69d0ea4de..33d5a8a96 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -143,7 +143,6 @@ typedef struct { ngx_event_t push; ngx_event_t pto; ngx_event_t close; - ngx_queue_t free_frames; ngx_msec_t last_cc; ngx_msec_t latest_rtt; @@ -153,8 +152,12 @@ typedef struct { ngx_uint_t pto_count; -#if (NGX_DEBUG) + ngx_queue_t free_frames; + ngx_chain_t *free_bufs; + +#ifdef NGX_QUIC_DEBUG_ALLOC ngx_uint_t nframes; + ngx_uint_t nbufs; #endif ngx_quic_streams_t streams; @@ -265,7 +268,7 @@ static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_ack_frame_t *f); + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, ngx_msec_t *send_time); @@ -361,7 +364,7 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, static size_t ngx_quic_max_stream_frame(ngx_quic_connection_t *qc); static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); -static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c, size_t size); +static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_ack(ngx_connection_t *c, @@ -369,6 +372,13 @@ static void ngx_quic_congestion_ack(ngx_connection_t *c, static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *frame); +static ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); +static ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, + size_t len); +static ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, + size_t limit); + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 @@ -414,8 +424,14 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", f->u.ack.range_count, f->u.ack.delay); - pos = f->u.ack.ranges_start; - end = f->u.ack.ranges_end; + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->end; + + } else { + pos = NULL; + end = NULL; + } largest = f->u.ack.largest; smallest = f->u.ack.largest - f->u.ack.first_range; @@ -507,8 +523,16 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) } #ifdef NGX_QUIC_DEBUG_FRAMES - p = ngx_slprintf(p, last, " data len:%uL %*xs", f->u.stream.length, - (size_t) f->u.stream.length, f->u.stream.data); + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } #endif break; @@ -885,18 +909,20 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, fsize = ngx_min(limit, (size_t) (end - p)); - frame = ngx_quic_alloc_frame(c, fsize); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return 0; } - ngx_memcpy(frame->data, p, fsize); + frame->data = ngx_quic_copy_buf(c, p, fsize); + if (frame->data == NGX_CHAIN_ERROR) { + return 0; + } frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; frame->u.crypto.offset = fs->sent; frame->u.crypto.length = fsize; - frame->u.crypto.data = frame->data; fs->sent += fsize; p += fsize; @@ -1870,15 +1896,6 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_delete_posted_event(&qc->push); } - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { - ngx_quic_free_frames(c, &qc->crypto[i].frames); - } - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ngx_quic_free_frames(c, &qc->send_ctx[i].frames); - ngx_quic_free_frames(c, &qc->send_ctx[i].sent); - } - while (!ngx_queue_empty(&qc->server_ids)) { q = ngx_queue_head(&qc->server_ids); sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); @@ -2438,7 +2455,9 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *end, *p; ssize_t len; + ngx_buf_t buf; ngx_uint_t do_close; + ngx_chain_t chain; ngx_quic_frame_t frame; ngx_quic_connection_t *qc; @@ -2472,6 +2491,12 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) c->log->action = "parsing frames"; + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + chain.buf = &buf; + chain.next = NULL; + frame.data = &chain; + len = ngx_quic_parse_frame(pkt, p, end, &frame); if (len < 0) { @@ -2488,7 +2513,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) switch (frame.type) { case NGX_QUIC_FT_ACK: - if (ngx_quic_handle_ack_frame(c, pkt, &frame.u.ack) != NGX_OK) { + if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { return NGX_ERROR; } @@ -2922,7 +2947,7 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, { ngx_quic_frame_t *frame; - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -2999,10 +3024,11 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - u_char *p; - size_t ranges_len; + size_t len, left; uint64_t ack_delay; + ngx_buf_t *b; ngx_uint_t i; + ngx_chain_t *cl, **ll; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -3017,33 +3043,51 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ack_delay = 0; } - ranges_len = 0; - - for (i = 0; i < ctx->nranges; i++) { - ranges_len += ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, - ctx->ranges[i].range); - } - - frame = ngx_quic_alloc_frame(c, ranges_len); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } - p = frame->data; + ll = &frame->data; + b = NULL; for (i = 0; i < ctx->nranges; i++) { - p += ngx_quic_create_ack_range(p, ctx->ranges[i].gap, - ctx->ranges[i].range); + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; } + *ll = NULL; + frame->level = ctx->level; frame->type = NGX_QUIC_FT_ACK; frame->u.ack.largest = ctx->largest_range; frame->u.ack.delay = ack_delay; frame->u.ack.range_count = ctx->nranges; frame->u.ack.first_range = ctx->first_range; - frame->u.ack.ranges_start = frame->data; - frame->u.ack.ranges_end = frame->data + ranges_len; ngx_quic_queue_frame(qc, frame); @@ -3077,7 +3121,7 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -3118,7 +3162,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) return NGX_ERROR; } - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -3136,7 +3180,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_ack_frame_t *ack) + ngx_quic_frame_t *f) { ssize_t n; u_char *pos, *end; @@ -3144,6 +3188,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_msec_t send_time; ngx_uint_t i; ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -3153,6 +3198,8 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + ack = &f->u.ack; + /* * If any computed packet number is negative, an endpoint MUST * generate a connection error of type FRAME_ENCODING_ERROR. @@ -3194,8 +3241,14 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } - pos = ack->ranges_start; - end = ack->ranges_end; + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } for (i = 0; i < ack->range_count; i++) { @@ -3537,7 +3590,9 @@ static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, uint64_t offset_in) { - size_t tail; + size_t tail, n; + ngx_buf_t *b; + ngx_chain_t *cl; ngx_quic_ordered_frame_t *f; f = &frame->u.ord; @@ -3558,9 +3613,21 @@ ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, /* intersecting range: adjust data size */ f->offset += tail; - f->data += tail; f->length -= tail; + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + n = ngx_buf_size(b); + + if (n >= tail) { + b->pos += tail; + break; + } + + cl->buf->pos = cl->buf->last; + tail -= n; + } + return NGX_OK; } @@ -3569,7 +3636,6 @@ static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame) { - u_char *data; ngx_queue_t *q; ngx_quic_frame_t *dst, *item; ngx_quic_ordered_frame_t *f, *df; @@ -3581,19 +3647,19 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, /* frame start offset is in the future, buffer it */ - dst = ngx_quic_alloc_frame(c, f->length); + dst = ngx_quic_alloc_frame(c); if (dst == NULL) { return NGX_ERROR; } - data = dst->data; ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); - dst->data = data; - ngx_memcpy(dst->data, f->data, f->length); + dst->data = ngx_quic_copy_chain(c, frame->data, 0); + if (dst->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } df = &dst->u.ord; - df->data = dst->data; fs->total += f->length; @@ -3671,15 +3737,14 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { - int n, sslerr; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - ngx_quic_crypto_frame_t *f; + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - f = &frame->u.crypto; - ssl_conn = c->ssl->connection; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3687,12 +3752,16 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - f->data, f->length)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + b->pos, b->last - b->pos)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } } n = SSL_do_handshake(ssl_conn); @@ -3730,7 +3799,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) c->ssl->handshaked = 1; - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -3858,6 +3927,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) uint64_t id; ngx_buf_t *b; ngx_event_t *rev; + ngx_chain_t *cl; ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -3881,7 +3951,10 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) b->pos = b->start; } - b->last = ngx_cpymem(b->last, f->data, f->length); + for (cl = frame->data; cl; cl = cl->next) { + b->last = ngx_cpymem(b->last, cl->buf->pos, + cl->buf->last - cl->buf->pos); + } rev = sn->c->read; rev->ready = 1; @@ -3992,7 +4065,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); } - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -4215,7 +4288,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -4381,7 +4454,7 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, qc = ngx_quic_get_connection(c); - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -4455,7 +4528,7 @@ ngx_quic_issue_server_ids(ngx_connection_t *c) return NGX_ERROR; } - frame = ngx_quic_alloc_frame(c, 0); + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; } @@ -5634,7 +5707,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) qs->id, len, size); if (!rev->pending_eof) { - frame = ngx_quic_alloc_frame(pc, 0); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_ERROR; } @@ -5650,7 +5723,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) if ((qc->streams.recv_max_data / 2) < qc->streams.received) { - frame = ngx_quic_alloc_frame(pc, 0); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_ERROR; @@ -5703,13 +5776,9 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - u_char *p; - size_t n, max, max_frame, max_flow, max_limit, len; + size_t n, max, max_frame, max_flow, max_limit; #if (NGX_DEBUG) size_t sent; -#endif - ngx_buf_t *b; -#if (NGX_DEBUG) ngx_uint_t nframes; #endif ngx_event_t *wev; @@ -5763,7 +5832,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) break; } - frame = ngx_quic_alloc_frame(pc, n); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_CHAIN_ERROR; } @@ -5778,7 +5847,6 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; frame->u.stream.length = n; - frame->u.stream.data = frame->data; c->sent += n; qc->streams.sent += n; @@ -5793,18 +5861,9 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) nframes++; #endif - for (p = frame->data; n > 0; cl = cl->next) { - b = cl->buf; - - if (!ngx_buf_in_memory(b)) { - continue; - } - - len = ngx_min(n, (size_t) (b->last - b->pos)); - p = ngx_cpymem(p, b->pos, len); - - b->pos += len; - n -= len; + frame->data = ngx_quic_copy_chain(pc, cl, n); + if (frame->data == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; } ngx_quic_queue_frame(qc, frame); @@ -5916,7 +5975,7 @@ ngx_quic_stream_cleanup_handler(void *data) || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { if (!c->read->pending_eof && !c->read->error) { - frame = ngx_quic_alloc_frame(pc, 0); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return; } @@ -5931,7 +5990,7 @@ ngx_quic_stream_cleanup_handler(void *data) } if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { - frame = ngx_quic_alloc_frame(pc, 0); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return; } @@ -5959,7 +6018,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL send fin", qs->id); - frame = ngx_quic_alloc_frame(pc, 0); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return; } @@ -5974,7 +6033,6 @@ ngx_quic_stream_cleanup_handler(void *data) frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; frame->u.stream.length = 0; - frame->u.stream.data = NULL; ngx_quic_queue_frame(qc, frame); @@ -5983,23 +6041,12 @@ ngx_quic_stream_cleanup_handler(void *data) static ngx_quic_frame_t * -ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) +ngx_quic_alloc_frame(ngx_connection_t *c) { - u_char *p; ngx_queue_t *q; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; - if (size) { - p = ngx_alloc(size, c->log); - if (p == NULL) { - return NULL; - } - - } else { - p = NULL; - } - qc = ngx_quic_get_connection(c); if (!ngx_queue_empty(&qc->free_frames)) { @@ -6009,7 +6056,7 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) ngx_queue_remove(&frame->queue); -#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC +#ifdef NGX_QUIC_DEBUG_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic reuse frame n:%ui", qc->nframes); #endif @@ -6017,15 +6064,12 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) } else { frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { - ngx_free(p); return NULL; } -#if (NGX_DEBUG) +#ifdef NGX_QUIC_DEBUG_ALLOC ++qc->nframes; -#endif -#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc frame n:%ui", qc->nframes); #endif @@ -6033,8 +6077,6 @@ ngx_quic_alloc_frame(ngx_connection_t *c, size_t size) ngx_memzero(frame, sizeof(ngx_quic_frame_t)); - frame->data = p; - return frame; } @@ -6140,13 +6182,12 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) qc = ngx_quic_get_connection(c); if (frame->data) { - ngx_free(frame->data); - frame->data = NULL; + ngx_quic_free_bufs(c, frame->data); } ngx_queue_insert_head(&qc->free_frames, &frame->queue); -#ifdef NGX_QUIC_DEBUG_FRAMES_ALLOC +#ifdef NGX_QUIC_DEBUG_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic free frame n:%ui", qc->nframes); #endif @@ -6165,3 +6206,170 @@ ngx_quic_version(ngx_connection_t *c) return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; } + + +static ngx_chain_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->free_bufs) { + cl = qc->free_bufs; + qc->free_bufs = cl->next; + + b = cl->buf; + b->pos = b->start; + b->last = b->start; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse buffer n:%ui", qc->nbufs); +#endif + + return cl; + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); + if (b == NULL) { + return NULL; + } + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + + cl->buf = b; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ++qc->nbufs; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc buffer n:%ui", qc->nbufs); +#endif + + return cl; +} + + +static void +ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer n:%ui", qc->nbufs); +#endif + + cl = in; + in = in->next; + + cl->next = qc->free_bufs; + qc->free_bufs = cl; + } +} + + +static ngx_chain_t * +ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + b = cl->buf; + n = ngx_min((size_t) (b->end - b->last), len); + + b->last = ngx_cpymem(b->last, data, n); + + data += n; + len -= n; + + *ll = cl; + ll = &cl->next; + } + + *ll = NULL; + + return out; +} + + +static ngx_chain_t * +ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (in) { + if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { + in = in->next; + continue; + } + + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + + while (in && b->last != b->end) { + + n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); + + if (limit > 0 && n > limit) { + n = limit; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, n); + + in->buf->pos += n; + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (limit > 0) { + if (limit == n) { + goto done; + } + + limit -= n; + } + } + + } + +done: + + *ll = NULL; + + return out; +} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h index a6f5f4fba..59578feea 100644 --- a/src/event/ngx_event_quic.h +++ b/src/event/ngx_event_quic.h @@ -61,6 +61,8 @@ #define NGX_QUIC_MAX_SERVER_IDS 8 +#define NGX_QUIC_BUFFER_SIZE 4096 + #define ngx_quic_get_connection(c) ((ngx_quic_connection_t *)(c)->udp) @@ -135,7 +137,7 @@ ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, /* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ /* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ -/* #define NGX_QUIC_DEBUG_FRAMES_ALLOC */ /* log frames alloc/reuse/free */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ /* #define NGX_QUIC_DEBUG_CRYPTO */ #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index 4c852436c..dc5ff9801 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -87,15 +87,17 @@ static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); -static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack); +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, + ngx_chain_t *ranges); static size_t ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss); static size_t ngx_quic_create_crypto(u_char *p, - ngx_quic_crypto_frame_t *crypto); + ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); static size_t ngx_quic_create_hs_done(u_char *p); static size_t ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token); -static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data); static size_t ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms); static size_t ngx_quic_create_max_stream_data(u_char *p, @@ -703,8 +705,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, { u_char *p; uint64_t varint; + ngx_buf_t *b; ngx_uint_t i; + b = f->data->buf; + p = start; p = ngx_quic_parse_int(p, end, &varint); @@ -736,11 +741,13 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &f->u.crypto.data); + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); if (p == NULL) { goto error; } + b->last = p; + break; case NGX_QUIC_FT_PADDING: @@ -762,7 +769,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - f->u.ack.ranges_start = p; + b->pos = p; /* process all ranges to get bounds, values are ignored */ for (i = 0; i < f->u.ack.range_count; i++) { @@ -777,7 +784,9 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } } - f->u.ack.ranges_end = p; + b->last = p; + + f->u.ack.ranges_length = b->last - b->pos; if (f->type == NGX_QUIC_FT_ACK_ECN) { @@ -914,12 +923,12 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, f->u.stream.length = end - p; /* up to packet end */ } - p = ngx_quic_read_bytes(p, end, f->u.stream.length, - &f->u.stream.data); + p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); if (p == NULL) { goto error; } + b->last = p; break; case NGX_QUIC_FT_MAX_DATA: @@ -1192,13 +1201,13 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) switch (f->type) { case NGX_QUIC_FT_ACK: f->need_ack = 0; - return ngx_quic_create_ack(p, &f->u.ack); + return ngx_quic_create_ack(p, &f->u.ack, f->data); case NGX_QUIC_FT_STOP_SENDING: return ngx_quic_create_stop_sending(p, &f->u.stop_sending); case NGX_QUIC_FT_CRYPTO: - return ngx_quic_create_crypto(p, &f->u.crypto); + return ngx_quic_create_crypto(p, &f->u.crypto, f->data); case NGX_QUIC_FT_HANDSHAKE_DONE: return ngx_quic_create_hs_done(p); @@ -1214,7 +1223,7 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_STREAM5: case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - return ngx_quic_create_stream(p, &f->u.stream); + return ngx_quic_create_stream(p, &f->u.stream, f->data); case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE_APP: @@ -1247,10 +1256,11 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) static size_t -ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_buf_t *b; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); @@ -1258,7 +1268,7 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) len += ngx_quic_varint_len(ack->delay); len += ngx_quic_varint_len(ack->range_count); len += ngx_quic_varint_len(ack->first_range); - len += ack->ranges_end - ack->ranges_start; + len += ack->ranges_length; return len; } @@ -1270,7 +1280,12 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack) ngx_quic_build_int(&p, ack->delay); ngx_quic_build_int(&p, ack->range_count); ngx_quic_build_int(&p, ack->first_range); - p = ngx_cpymem(p, ack->ranges_start, ack->ranges_end - ack->ranges_start); + + while (ranges) { + b = ranges->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + ranges = ranges->next; + } return p - start; } @@ -1300,10 +1315,12 @@ ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) static size_t -ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, + ngx_chain_t *data) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_buf_t *b; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); @@ -1319,7 +1336,12 @@ ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto) ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); ngx_quic_build_int(&p, crypto->offset); ngx_quic_build_int(&p, crypto->length); - p = ngx_cpymem(p, crypto->data, crypto->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } return p - start; } @@ -1367,10 +1389,12 @@ ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) static size_t -ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_buf_t *b; if (p == NULL) { len = ngx_quic_varint_len(sf->type); @@ -1401,7 +1425,11 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf) /* length is always present in generated frames */ ngx_quic_build_int(&p, sf->length); - p = ngx_cpymem(p, sf->data, sf->length); + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } return p - start; } diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h index 2e7a6f953..aa1c888e0 100644 --- a/src/event/ngx_event_quic_transport.h +++ b/src/event/ngx_event_quic_transport.h @@ -144,8 +144,7 @@ typedef struct { uint64_t ect0; uint64_t ect1; uint64_t ce; - u_char *ranges_start; - u_char *ranges_end; + uint64_t ranges_length; } ngx_quic_ack_frame_t; @@ -171,7 +170,6 @@ typedef struct { typedef struct { uint64_t offset; uint64_t length; - u_char *data; } ngx_quic_ordered_frame_t; typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; @@ -181,7 +179,6 @@ typedef struct { /* initial fields same as in ngx_quic_ordered_frame_t */ uint64_t offset; uint64_t length; - u_char *data; uint8_t type; uint64_t stream_id; @@ -270,7 +267,7 @@ struct ngx_quic_frame_s { ngx_uint_t need_ack; /* unsigned need_ack:1; */ - u_char *data; + ngx_chain_t *data; union { ngx_quic_ack_frame_t ack; ngx_quic_crypto_frame_t crypto; -- cgit v1.2.3 From c9cbd2f8e752be5eb788ae085f9e7c3ee2890671 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 7 Dec 2020 15:09:08 +0000 Subject: QUIC: coalesce output packets into a single UDP datagram. Now initial output packet is not padded anymore if followed by a handshake packet. If the datagram is still not big enough to satisfy minimum size requirements, handshake packet is padded. --- src/event/ngx_event_quic.c | 734 +++++++++++++++++++++++++++------------------ 1 file changed, 447 insertions(+), 287 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 33d5a8a96..f923ebe47 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -154,6 +154,7 @@ typedef struct { ngx_queue_t free_frames; ngx_chain_t *free_bufs; + ngx_buf_t *free_shadow_bufs; #ifdef NGX_QUIC_DEBUG_ALLOC ngx_uint_t nframes; @@ -162,7 +163,7 @@ typedef struct { ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; - size_t received; + off_t received; ngx_uint_t error; enum ssl_encryption_level_t error_level; @@ -331,11 +332,14 @@ static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); static ngx_int_t ngx_quic_output(ngx_connection_t *c); -static ngx_int_t ngx_quic_output_frames(ngx_connection_t *c, +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); -static ngx_int_t ngx_quic_send_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, ngx_queue_t *frames); static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, @@ -361,7 +365,6 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); -static size_t ngx_quic_max_stream_frame(ngx_quic_connection_t *qc); static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); @@ -378,6 +381,8 @@ static ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); static ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit); +static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, + size_t len); static SSL_QUIC_METHOD quic_method = { @@ -785,7 +790,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { u_char *p, *end; - size_t client_params_len, fsize, limit; + size_t client_params_len; const uint8_t *client_params; ngx_quic_frame_t *frame; ngx_connection_t *c; @@ -893,42 +898,26 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->client_tp_done = 1; } - /* - * we need to fit at least 1 frame into a packet, thus account head/tail; - * 17 = 1 + 8x2 is max header for CRYPTO frame, with 1 byte for frame type - */ - limit = qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_LONG_HEADER - 17 - - EVP_GCM_TLS_TAG_LEN; - fs = &qc->crypto[level]; - p = (u_char *) data; - end = (u_char *) data + len; - - while (p < end) { - - fsize = ngx_min(limit, (size_t) (end - p)); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return 0; - } + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } - frame->data = ngx_quic_copy_buf(c, p, fsize); - if (frame->data == NGX_CHAIN_ERROR) { - return 0; - } + frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); + if (frame->data == NGX_CHAIN_ERROR) { + return 0; + } - frame->level = level; - frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset = fs->sent; - frame->u.crypto.length = fsize; + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = fs->sent; + frame->u.crypto.length = len; - fs->sent += fsize; - p += fsize; + fs->sent += len; - ngx_quic_queue_frame(qc, frame); - } + ngx_quic_queue_frame(qc, frame); return 1; } @@ -4701,240 +4690,255 @@ ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) static ngx_int_t ngx_quic_output(ngx_connection_t *c) { - ngx_uint_t i; - ngx_msec_t delay; + off_t max; + size_t len, min; + ssize_t n; + u_char *p; + ngx_uint_t i, pad; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "sending frames"; qc = ngx_quic_get_connection(c); - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + for ( ;; ) { + p = dst; - ctx = &qc->send_ctx[i]; + len = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (ctx->send_ack) { + if (!qc->validated) { + max = qc->received * 3; + max = (c->sent >= max) ? 0 : max - c->sent; + len = ngx_min(len, (size_t) max); + } - if (ctx->level == ssl_encryption_application) { + pad = ngx_quic_get_padding_level(c); - delay = ngx_current_msec - ctx->ack_delay_start; + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP - && delay < qc->tp.max_ack_delay) - { - if (!qc->push.timer_set && !qc->closing) { - ngx_add_timer(&qc->push, qc->tp.max_ack_delay - delay); - } + ctx = &qc->send_ctx[i]; - goto output; - } + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; } - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { return NGX_ERROR; } - ctx->send_ack = 0; + + p += n; + len -= n; } - output: + len = p - dst; + if (len == 0) { + break; + } - if (ngx_quic_output_frames(c, ctx) != NGX_OK) { + n = ngx_quic_send(c, dst, len); + if (n == NGX_ERROR) { return NGX_ERROR; } - } - if (!qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); + if (!qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } } return NGX_OK; } -static ngx_int_t -ngx_quic_output_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) { - size_t len, hlen, cutoff; - ngx_uint_t need_ack; - ngx_queue_t *q, range; + ngx_queue_t *q; ngx_quic_frame_t *f; - ngx_quic_congestion_t *cg; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - if (ngx_queue_empty(&ctx->frames)) { - return NGX_OK; - } - - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - /* all frames in same send_ctx share same level */ - hlen = (f->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; - - do { - len = 0; - need_ack = 0; - ngx_queue_init(&range); + /* + * 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes + */ - do { - /* process group of frames that fits into packet */ - f = ngx_queue_data(q, ngx_quic_frame_t, queue); + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - if (len && hlen + len + f->len > qc->ctp.max_udp_payload_size) { - break; - } + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (f->need_ack) { - need_ack = 1; - } + if (f->need_ack) { + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - if (need_ack && cg->in_flight + len + f->len > cg->window) { - break; + if (ngx_queue_empty(&ctx->frames)) { + return 0; } - if (!qc->validated) { - /* - * Prior to validation, endpoints are limited in what they - * are able to send. During the handshake, a server cannot - * send more than three times the data it receives; - */ - - if (f->level == ssl_encryption_initial) { - cutoff = (c->sent + NGX_QUIC_MIN_INITIAL_SIZE) / 3; + return 1; + } + } - } else { - cutoff = (c->sent + hlen + len + f->len) / 3; - } + return NGX_QUIC_SEND_CTX_LAST; +} - if (cutoff > qc->received) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic hit amplification limit" - " received:%uz sent:%O", - qc->received, c->sent); - break; - } - } - q = ngx_queue_next(q); +static ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; - f->first = ngx_current_msec; + if (!ctx->send_ack) { + return NGX_OK; + } - ngx_queue_remove(&f->queue); - ngx_queue_insert_tail(&range, &f->queue); + if (ctx->level == ssl_encryption_application) { - len += f->len; + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); - } while (q != ngx_queue_sentinel(&ctx->frames)); + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } - if (ngx_queue_empty(&range)) { - break; + return NGX_OK; } + } - if (ngx_quic_send_frames(c, ctx, &range) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } - } while (q != ngx_queue_sentinel(&ctx->frames)); + ctx->send_ack = 0; return NGX_OK; } -static void -ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - - do { - q = ngx_queue_head(frames); - - if (q == ngx_queue_sentinel(frames)) { - break; - } - - ngx_queue_remove(q); - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_quic_free_frame(c, f); - } while (1); -} - - -static ngx_int_t -ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_queue_t *frames) +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) { + size_t len, hlen, pad_len; u_char *p; - size_t pad_len; - ssize_t len; + ssize_t flen; ngx_str_t out, res; + ngx_int_t rc; + ngx_uint_t nframes; ngx_msec_t now; ngx_queue_t *q; - ngx_quic_frame_t *f, *start; + ngx_quic_frame_t *f; ngx_quic_header_t pkt; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_frames"); + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } - q = ngx_queue_head(frames); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + hlen = (ctx->level == ssl_encryption_application) + ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; + + hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); now = ngx_current_msec; - + nframes = 0; p = src; - out.data = src; + len = 0; - for (q = ngx_queue_head(frames); - q != ngx_queue_sentinel(frames); + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); q = ngx_queue_next(q)) { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_log_frame(c->log, f, 1); + if (!pkt.need_ack && f->need_ack && max > cg->window) { + max = cg->window; + } - len = ngx_quic_create_frame(p, f); - if (len == -1) { - ngx_quic_free_frames(c, frames); - return NGX_ERROR; + if (hlen + len >= max) { + break; + } + + if (hlen + len + f->len > max) { + rc = ngx_quic_split_frame(c, f, max - hlen - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } } if (f->need_ack) { pkt.need_ack = 1; } - p += len; + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + f->pnum = ctx->pnum; + f->first = now; f->last = now; f->plen = 0; + + nframes++; } - out.len = p - out.data; + if (nframes == 0) { + return 0; + } - qc = ngx_quic_get_connection(c); + out.data = src; + out.len = len; pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - if (start->level == ssl_encryption_initial) { + if (ctx->level == ssl_encryption_initial) { pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - } else if (start->level == ssl_encryption_handshake) { + } else if (ctx->level == ssl_encryption_handshake) { pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; } else { @@ -4947,17 +4951,19 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.version = qc->version; pkt.log = c->log; - pkt.level = start->level; + pkt.level = ctx->level; pkt.dcid = qc->scid; pkt.scid = qc->dcid; - if (start->level == ssl_encryption_initial && pkt.need_ack) { - pad_len = NGX_QUIC_MIN_INITIAL_SIZE - EVP_GCM_TLS_TAG_LEN - - ngx_quic_create_header(&pkt, NULL, out.len, NULL); - pad_len = ngx_min(pad_len, NGX_QUIC_MIN_INITIAL_SIZE); + pad_len = 4; - } else { - pad_len = 4; + if (min) { + hlen = EVP_GCM_TLS_TAG_LEN + + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + + if (min > hlen + pad_len) { + pad_len = min - hlen; + } } if (out.len < pad_len) { @@ -4967,59 +4973,150 @@ ngx_quic_send_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.payload = out; - res.data = dst; + res.data = data; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet tx %s bytes:%ui" " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(start->level), out.len, pkt.need_ack, + ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - ngx_quic_free_frames(c, frames); return NGX_ERROR; } - len = ngx_quic_send(c, res.data, res.len); - if (len == NGX_ERROR) { - ngx_quic_free_frames(c, frames); - return NGX_ERROR; - } - - /* len == NGX_OK || NGX_AGAIN */ ctx->pnum++; if (pkt.need_ack) { /* move frames into the sent queue to wait for ack */ - if (qc->closing) { - /* if we are closing, any ack will be discarded */ - ngx_quic_free_frames(c, frames); + if (!qc->closing) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + f->plen = res.len; + + do { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sent, q); + } while (--nframes); - } else { - ngx_queue_add(&ctx->sent, frames); if (qc->pto.timer_set) { ngx_del_timer(&qc->pto); } - ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); - start->plen = len; + ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); } - qc->congestion.in_flight += len; + cg->in_flight += res.len; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", - qc->congestion.in_flight); - } else { - /* no ack is expected for this frames, so we can free them */ - ngx_quic_free_frames(c, frames); + "quic congestion send if:%uz", cg->in_flight); } + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + return res.len; +} + + +static ngx_int_t +ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) +{ + size_t shrink; + ngx_quic_frame_t *nf; + ngx_quic_ordered_frame_t *of, *onf; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + nf = ngx_quic_alloc_frame(c); + if (nf == NULL) { + return NGX_ERROR; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + + nf->data = ngx_quic_split_bufs(c, f->data, of->length); + if (nf->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + return NGX_OK; } +static void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) { @@ -5776,11 +5873,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - size_t n, max, max_frame, max_flow, max_limit; -#if (NGX_DEBUG) - size_t sent; - ngx_uint_t nframes; -#endif + size_t n, flow; ngx_event_t *wev; ngx_chain_t *cl; ngx_connection_t *pc; @@ -5797,98 +5890,57 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) return NGX_CHAIN_ERROR; } - max_frame = ngx_quic_max_stream_frame(qc); - max_flow = ngx_quic_max_stream_flow(c); - max_limit = limit; - -#if (NGX_DEBUG) - sent = 0; - nframes = 0; -#endif - - for ( ;; ) { - max = ngx_min(max_frame, max_flow); - - if (limit) { - max = ngx_min(max, max_limit); - } - - for (cl = in, n = 0; in; in = in->next) { - - if (!ngx_buf_in_memory(in->buf)) { - continue; - } + flow = ngx_quic_max_stream_flow(c); + if (flow == 0) { + wev->ready = 0; + return in; + } - n += ngx_buf_size(in->buf); + n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; - if (n > max) { - n = max; - break; - } - } - - if (n == 0) { - wev->ready = (max_flow ? 1 : 0); - break; - } + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_CHAIN_ERROR; - } + frame->data = ngx_quic_copy_chain(pc, in, n); + if (frame->data == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; + for (n = 0, cl = frame->data; cl; cl = cl->next) { + n += ngx_buf_size(cl->buf); + } - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = n; + while (in && ngx_buf_size(in->buf) == 0) { + in = in->next; + } - c->sent += n; - qc->streams.sent += n; - max_flow -= n; + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; - if (limit) { - max_limit -= n; - } + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = n; -#if (NGX_DEBUG) - sent += n; - nframes++; -#endif + c->sent += n; + qc->streams.sent += n; - frame->data = ngx_quic_copy_chain(pc, cl, n); - if (frame->data == NGX_CHAIN_ERROR) { - return NGX_CHAIN_ERROR; - } + ngx_quic_queue_frame(qc, frame); - ngx_quic_queue_frame(qc, frame); - } + wev->ready = (n < flow) ? 1 : 0; - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz nframes:%ui", sent, nframes); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uz", n); return in; } -static size_t -ngx_quic_max_stream_frame(ngx_quic_connection_t *qc) -{ - /* - * we need to fit at least 1 frame into a packet, thus account head/tail; - * 25 = 1 + 8x3 is max header for STREAM frame, with 1 byte for frame type - */ - - return qc->ctp.max_udp_payload_size - NGX_QUIC_MAX_SHORT_HEADER - 25 - - EVP_GCM_TLS_TAG_LEN; -} - - static size_t ngx_quic_max_stream_flow(ngx_connection_t *c) { @@ -6261,6 +6313,7 @@ ngx_quic_alloc_buf(ngx_connection_t *c) static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) { + ngx_buf_t *b, *shadow; ngx_chain_t *cl; ngx_quic_connection_t *qc; @@ -6274,6 +6327,30 @@ ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) cl = in; in = in->next; + b = cl->buf; + + if (b->shadow) { + if (!b->last_shadow) { + b->recycled = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + do { + shadow = b->shadow; + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + b = shadow; + } while (b->recycled); + + if (b->shadow) { + b->last_shadow = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + cl->buf = b; + } cl->next = qc->free_bufs; qc->free_bufs = cl; @@ -6373,3 +6450,86 @@ done: return out; } + + +static ngx_chain_t * +ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *out; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { + n = ngx_buf_size(in->buf); + + if (n == len) { + out = in->next; + in->next = NULL; + return out; + } + + if (n > len) { + break; + } + + len -= n; + in = in->next; + } + + if (in == NULL) { + return NULL; + } + + /* split in->buf by creating shadow bufs which reference it */ + + if (in->buf->shadow == NULL) { + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + return NGX_CHAIN_ERROR; + } + } + + *b = *in->buf; + b->shadow = in->buf; + b->last_shadow = 1; + in->buf = b; + } + + out = ngx_alloc_chain_link(c->pool); + if (out == NULL) { + return NGX_CHAIN_ERROR; + } + + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + ngx_free_chain(c->pool, out); + return NGX_CHAIN_ERROR; + } + } + + out->buf = b; + out->next = in->next; + in->next = NULL; + + *b = *in->buf; + b->last_shadow = 0; + b->pos = b->pos + len; + + in->buf->shadow = b; + in->buf->last = in->buf->pos + len; + + return out; +} -- cgit v1.2.3 From fc3f04b11198dc2a352fd6e5c1e5d7cb6c2c957a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 8 Dec 2020 14:44:41 +0000 Subject: QUIC: set the temporary flag for input frame buffers. Missing flag prevented frame data from being copied as the buffer was not considered a memory buffer. --- src/event/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index f923ebe47..95f92ed21 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -2481,6 +2481,7 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) c->log->action = "parsing frames"; ngx_memzero(&buf, sizeof(ngx_buf_t)); + buf.temporary = 1; chain.buf = &buf; chain.next = NULL; -- cgit v1.2.3 From e5c10dce5ee651d467e6e70952f539cd7bbb7d8f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 8 Dec 2020 17:10:22 +0000 Subject: QUIC: resend handshake packets along with initial. To speed up handshake, resend both initial and handshake packets if there's at least one unacknowledged initial packet. --- src/event/ngx_event_quic.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 95f92ed21..ace2de749 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3717,6 +3717,11 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (!ngx_queue_empty(&ctx->sent)) { ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } } } -- cgit v1.2.3 From 6f9efd423ed225bf1cf25f173aaf99cbedc57e3f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 9 Dec 2020 16:15:24 +0000 Subject: QUIC: use client max_ack_delay when computing pto timeout. Previously, server max_ack_delay was used which is wrong. Also, level check is simplified. --- src/event/ngx_event_quic.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index ace2de749..fef428625 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3423,10 +3423,8 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) return duration; } - if (ctx == &qc->send_ctx[2] && c->ssl->handshaked) { - /* application send space */ - - duration += qc->tp.max_ack_delay << qc->pto_count; + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay << qc->pto_count; } return duration; -- cgit v1.2.3 From 4fd02c58391ed65a4567e16bee6d198670f2539a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 10 Dec 2020 14:54:53 +0000 Subject: QUIC: send and process ack_delay for Initial and Handshake levels. Previously, this only worked for Application level because before quic-transport-30, there were the following constraints: Because the receiver doesn't use the ACK Delay for Initial and Handshake packets, a sender SHOULD send a value of 0. When adjusting an RTT sample using peer-reported acknowledgement delays, an endpoint ... MUST ignore the ACK Delay field of the ACK frame for packets sent in the Initial and Handshake packet number space. --- src/event/ngx_event_quic.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index fef428625..349fbb311 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3024,14 +3024,9 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) qc = ngx_quic_get_connection(c); - if (ctx->level == ssl_encryption_application) { - ack_delay = ngx_current_msec - ctx->largest_received; - ack_delay *= 1000; - ack_delay >>= qc->ctp.ack_delay_exponent; - - } else { - ack_delay = 0; - } + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->ctp.ack_delay_exponent; frame = ngx_quic_alloc_frame(c); if (frame == NULL) { @@ -3379,13 +3374,10 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, } else { qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; - if (level == ssl_encryption_application) { - ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; + if (c->ssl->handshaked) { ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); - - } else { - ack_delay = 0; } adjusted_rtt = latest_rtt; -- cgit v1.2.3 From 240f8a918ed09e67570020261b1693907e5b64c0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 9 Dec 2020 21:26:21 +0000 Subject: QUIC: always calculate rtt for largest acknowledged packet. Previously, when processing client ACK, rtt could be calculated for a packet different than the largest if it was missing in the sent chain. Even though this is an unlikely situation, rtt based on a different packet could be larger than needed leading to bigger pto timeout and performance degradation. --- src/event/ngx_event_quic.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 349fbb311..73519b968 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -3276,7 +3276,6 @@ static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, ngx_msec_t *send_time) { - uint64_t found_num; ngx_uint_t found; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -3286,7 +3285,6 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, *send_time = NGX_TIMER_INFINITE; found = 0; - found_num = 0; q = ngx_queue_last(&ctx->sent); @@ -3316,9 +3314,8 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, break; } - if (f->pnum > found_num || !found) { + if (f->pnum == max) { *send_time = f->last; - found_num = f->pnum; } ngx_queue_remove(&f->queue); -- cgit v1.2.3 From c3714a8089f50e15966db73c227c4eea6d9f8e77 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Dec 2020 12:47:38 +0000 Subject: HTTP/3: staticize ngx_http_v3_methods. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2b50133f1..4597bc180 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -14,7 +14,7 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -struct { +static const struct { ngx_str_t name; ngx_uint_t method; } ngx_http_v3_methods[] = { -- cgit v1.2.3 From 405b9be899fe9bb10ad910bfe10a2fb7cae9a846 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Dec 2020 12:47:41 +0000 Subject: HTTP/3: staticize internal parsing functions. --- src/http/v3/ngx_http_v3_parse.c | 56 +++++++++++++++++++++++++++++++---------- src/http/v3/ngx_http_v3_parse.h | 34 ++----------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index afe442464..7c22b482c 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -14,11 +14,41 @@ ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, u_char ch); + +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, u_char ch); +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, u_char ch); + +static ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, + ngx_http_v3_parse_header_block_prefix_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c, + ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch); +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, + ngx_http_v3_parse_literal_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); + +static ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, + ngx_http_v3_parse_header_t *st, u_char ch); + 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); -ngx_int_t +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, u_char ch) { @@ -95,7 +125,7 @@ done: } -ngx_int_t +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, u_char ch) { @@ -289,7 +319,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, ngx_http_v3_parse_header_block_prefix_t *st, u_char ch) { @@ -366,7 +396,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c, ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch) { @@ -454,7 +484,7 @@ ngx_http_v3_parse_header_rep(ngx_connection_t *c, } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, u_char ch) { @@ -533,7 +563,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -588,7 +618,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -679,7 +709,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -777,7 +807,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -826,7 +856,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -1118,7 +1148,7 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, ngx_http_v3_parse_settings_t *st, u_char ch) { @@ -1277,7 +1307,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { @@ -1364,7 +1394,7 @@ done: } -ngx_int_t +static ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch) { diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index 856f021bd..f039ac28d 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -124,44 +124,14 @@ typedef struct { * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error */ -ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, - ngx_http_v3_parse_varlen_int_t *st, u_char ch); -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, u_char ch); - ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, - ngx_http_v3_parse_header_block_prefix_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c, - ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch); -ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, - ngx_http_v3_parse_literal_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); +ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, + ngx_http_v3_parse_data_t *st, u_char ch); ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch); -ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, - ngx_http_v3_parse_settings_t *st, u_char ch); - ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch); -ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); - ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch); -ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, - ngx_http_v3_parse_data_t *st, u_char ch); - #endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ -- cgit v1.2.3 From 2bc8ee653517f61c047d3519d451fbc718a6313d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 21 Dec 2020 15:05:43 +0300 Subject: QUIC: converted to SSL_CIPHER_get_protocol_id(). This API is available in BoringSSL for quite some time: https://boringssl.googlesource.com/boringssl/+/3743aaf --- src/event/ngx_event_quic_protection.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c index db9068d69..401b71121 100644 --- a/src/event/ngx_event_quic_protection.c +++ b/src/event/ngx_event_quic_protection.c @@ -655,11 +655,7 @@ int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, peer_secret = is_write ? &keys->secrets[level].server : &keys->secrets[level].client; - /* - * SSL_CIPHER_get_protocol_id() is not universally available, - * casting to uint16_t works for both OpenSSL and BoringSSL - */ - keys->cipher = (uint16_t) SSL_CIPHER_get_id(cipher); + keys->cipher = SSL_CIPHER_get_protocol_id(cipher); key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); -- cgit v1.2.3 From 71f9b41c7fa1a5700f196bb4f4fee255b986ff4d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Dec 2020 12:03:43 +0300 Subject: QUIC: fixed build with OpenSSL < 1.1.1. The header is available since OpenSSL 1.1.0, and HKDF API used for separate Extract and Expand steps in TLSv1.3 - since OpenSSL 1.1.1. --- src/event/ngx_event_openssl.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index afccb52d7..3a0bbfdba 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -23,12 +23,14 @@ #include #endif #include +#if (NGX_QUIC) #ifdef OPENSSL_IS_BORINGSSL #include #include #else #include #endif +#endif #include #ifndef OPENSSL_NO_OCSP #include -- cgit v1.2.3 From a96989365676de270fbb2ad19480435ad5e86df4 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Dec 2020 12:04:15 +0300 Subject: QUIC: fixed building ALPN callback without debug and http2. --- src/http/modules/ngx_http_ssl_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 111de479b..97c58e445 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -429,7 +429,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #if (NGX_HTTP_V2 || NGX_HTTP_QUIC) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_QUIC || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); -- cgit v1.2.3 From df8ef280a508ba4589f1171e3f3381fa7fc6c04c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Dec 2020 12:04:15 +0300 Subject: QUIC: fixed logging PATH_CHALLENGE/RESPONSE and build with GCC < 5. --- src/event/ngx_event_quic.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 73519b968..73cde84d5 100644 --- a/src/event/ngx_event_quic.c +++ b/src/event/ngx_event_quic.c @@ -589,13 +589,15 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) break; case NGX_QUIC_FT_PATH_CHALLENGE: - p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%xL", - *(uint64_t *) &f->u.path_challenge.data); + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); break; case NGX_QUIC_FT_PATH_RESPONSE: - p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%xL", - f->u.path_response); + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); break; case NGX_QUIC_FT_NEW_TOKEN: -- cgit v1.2.3 From e00439e55da229c32c4d8d6aeafbdfd823d27b5b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Dec 2020 12:04:16 +0300 Subject: QUIC: fixed -Wtype-limits with GCC <= 5 (ticket #2104). --- src/event/ngx_event_quic_transport.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c index dc5ff9801..45c60c255 100644 --- a/src/event/ngx_event_quic_transport.c +++ b/src/event/ngx_event_quic_transport.c @@ -55,16 +55,11 @@ #define ngx_quic_write_uint32_aligned(p, s) \ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) -#define ngx_quic_varint_len(value) \ - ((value) <= 63 ? 1 \ - : ((uint32_t) value) <= 16383 ? 2 \ - : ((uint64_t) value) <= 1073741823 ? 4 \ - : 8) - #define NGX_QUIC_VERSION(c) (0xff000000 + (c)) static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static ngx_uint_t ngx_quic_varint_len(uint64_t value); static void ngx_quic_build_int(u_char **pos, uint64_t value); static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); @@ -236,6 +231,20 @@ ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) } +static ngx_uint_t +ngx_quic_varint_len(uint64_t value) +{ + ngx_uint_t bits; + + bits = 0; + while (value >> ((8 << bits) - 2)) { + bits++; + } + + return 1 << bits; +} + + static void ngx_quic_build_int(u_char **pos, uint64_t value) { -- cgit v1.2.3 From 45666324afdbd3ccad709d5b17fbad8ff07aef55 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Dec 2020 16:41:56 +0300 Subject: QUIC: removed unused inclusion. The low-level API was used in early QUIC development. --- src/event/ngx_event_openssl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 3a0bbfdba..cb9aceb58 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -14,7 +14,6 @@ #include #include -#include #include #include #include -- cgit v1.2.3 From 49527110972d80d20000044e6e63f6a1a08af67f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 25 Dec 2020 14:01:28 +0300 Subject: QUIC: moved all quic sources into src/event/quic. --- auto/make | 3 +- auto/modules | 12 +- auto/sources | 2 +- src/event/ngx_event_quic.c | 6530 ---------------------------- src/event/ngx_event_quic.h | 143 - src/event/ngx_event_quic_protection.c | 1188 ----- src/event/ngx_event_quic_protection.h | 38 - src/event/ngx_event_quic_transport.c | 1983 --------- src/event/ngx_event_quic_transport.h | 356 -- src/event/quic/ngx_event_quic.c | 6530 ++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic.h | 143 + src/event/quic/ngx_event_quic_protection.c | 1188 +++++ src/event/quic/ngx_event_quic_protection.h | 38 + src/event/quic/ngx_event_quic_transport.c | 1983 +++++++++ src/event/quic/ngx_event_quic_transport.h | 356 ++ 15 files changed, 10247 insertions(+), 10246 deletions(-) delete mode 100644 src/event/ngx_event_quic.c delete mode 100644 src/event/ngx_event_quic.h delete mode 100644 src/event/ngx_event_quic_protection.c delete mode 100644 src/event/ngx_event_quic_protection.h delete mode 100644 src/event/ngx_event_quic_transport.c delete mode 100644 src/event/ngx_event_quic_transport.h create mode 100644 src/event/quic/ngx_event_quic.c create mode 100644 src/event/quic/ngx_event_quic.h create mode 100644 src/event/quic/ngx_event_quic_protection.c create mode 100644 src/event/quic/ngx_event_quic_protection.h create mode 100644 src/event/quic/ngx_event_quic_transport.c create mode 100644 src/event/quic/ngx_event_quic_transport.h diff --git a/auto/make b/auto/make index 98ae07153..2b8f6ea41 100644 --- a/auto/make +++ b/auto/make @@ -11,7 +11,8 @@ mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ - $NGX_OBJS/src/misc + $NGX_OBJS/src/misc \ + $NGX_OBJS/src/event/quic ngx_objs_dir=$NGX_OBJS$ngx_regex_dirsep diff --git a/auto/modules b/auto/modules index 3f2430b62..82a887eb1 100644 --- a/auto/modules +++ b/auto/modules @@ -1327,13 +1327,13 @@ if [ $USE_OPENSSL = YES ]; then if [ $USE_OPENSSL_QUIC = YES ]; then ngx_module_deps="$ngx_module_deps \ - src/event/ngx_event_quic.h \ - src/event/ngx_event_quic_transport.h \ - src/event/ngx_event_quic_protection.h" + src/event/quic/ngx_event_quic.h \ + src/event/quic/ngx_event_quic_transport.h \ + src/event/quic/ngx_event_quic_protection.h" ngx_module_srcs="$ngx_module_srcs \ - src/event/ngx_event_quic.c \ - src/event/ngx_event_quic_transport.c \ - src/event/ngx_event_quic_protection.c" + src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_transport.c \ + src/event/quic/ngx_event_quic_protection.c" fi . auto/module diff --git a/auto/sources b/auto/sources index 3dad11132..37276aa94 100644 --- a/auto/sources +++ b/auto/sources @@ -83,7 +83,7 @@ CORE_SRCS="src/core/nginx.c \ EVENT_MODULES="ngx_events_module ngx_event_core_module" -EVENT_INCS="src/event src/event/modules" +EVENT_INCS="src/event src/event/modules src/event/quic" EVENT_DEPS="src/event/ngx_event.h \ src/event/ngx_event_timer.h \ diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c deleted file mode 100644 index 73cde84d5..000000000 --- a/src/event/ngx_event_quic.c +++ /dev/null @@ -1,6530 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include -#include -#include - - -/* 0-RTT and 1-RTT data exist in the same packet number space, - * so we have 3 packet number spaces: - * - * 0 - Initial - * 1 - Handshake - * 2 - 0-RTT and 1-RTT - */ -#define ngx_quic_get_send_ctx(qc, level) \ - ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ - : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ - : &((qc)->send_ctx[2])) - -#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) - -/* - * 7.4. Cryptographic Message Buffering - * Implementations MUST support buffering at least 4096 bytes of data - */ -#define NGX_QUIC_MAX_BUFFERED 65535 - -#define NGX_QUIC_STREAM_GONE (void *) -1 - -#define NGX_QUIC_UNSET_PN (uint64_t) -1 - -/* - * Endpoints MUST discard packets that are too small to be valid QUIC - * packets. With the set of AEAD functions defined in [QUIC-TLS], - * packets that are smaller than 21 bytes are never valid. - */ -#define NGX_QUIC_MIN_PKT_LEN 21 - -#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ -#define NGX_QUIC_MAX_SR_PACKET 1200 - -#define NGX_QUIC_MAX_ACK_GAP 2 - - -typedef struct { - ngx_rbtree_t tree; - ngx_rbtree_node_t sentinel; - - uint64_t received; - uint64_t sent; - uint64_t recv_max_data; - 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; -} ngx_quic_streams_t; - - -typedef struct { - size_t in_flight; - size_t window; - size_t ssthresh; - ngx_msec_t recovery_start; -} ngx_quic_congestion_t; - - -/* - * 12.3. Packet Numbers - * - * Conceptually, a packet number space is the context in which a packet - * can be processed and acknowledged. Initial packets can only be sent - * with Initial packet protection keys and acknowledged in packets which - * are also Initial packets. -*/ -typedef struct { - enum ssl_encryption_level_t level; - - uint64_t pnum; /* to be sent */ - uint64_t largest_ack; /* received from peer */ - uint64_t largest_pn; /* received from peer */ - - ngx_queue_t frames; - ngx_queue_t sent; - - uint64_t pending_ack; /* non sent ack-eliciting */ - uint64_t largest_range; - uint64_t first_range; - ngx_msec_t largest_received; - ngx_msec_t ack_delay_start; - ngx_uint_t nranges; - ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; - ngx_uint_t send_ack; -} ngx_quic_send_ctx_t; - - -typedef struct { - ngx_udp_connection_t udp; - - uint32_t version; - ngx_str_t scid; /* initial client ID */ - ngx_str_t dcid; /* server (our own) ID */ - ngx_str_t odcid; /* original server ID */ - ngx_str_t token; - - struct sockaddr *sockaddr; - socklen_t socklen; - - ngx_queue_t client_ids; - ngx_queue_t server_ids; - ngx_queue_t free_client_ids; - ngx_queue_t free_server_ids; - ngx_uint_t nclient_ids; - ngx_uint_t nserver_ids; - uint64_t max_retired_seqnum; - uint64_t client_seqnum; - uint64_t server_seqnum; - - ngx_uint_t client_tp_done; - ngx_quic_tp_t tp; - ngx_quic_tp_t ctp; - - ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; - - ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; - - ngx_quic_keys_t *keys; - - ngx_quic_conf_t *conf; - - ngx_event_t push; - ngx_event_t pto; - ngx_event_t close; - ngx_msec_t last_cc; - - ngx_msec_t latest_rtt; - ngx_msec_t avg_rtt; - ngx_msec_t min_rtt; - ngx_msec_t rttvar; - - ngx_uint_t pto_count; - - ngx_queue_t free_frames; - ngx_chain_t *free_bufs; - ngx_buf_t *free_shadow_bufs; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_uint_t nframes; - ngx_uint_t nbufs; -#endif - - ngx_quic_streams_t streams; - ngx_quic_congestion_t congestion; - off_t received; - - ngx_uint_t error; - enum ssl_encryption_level_t error_level; - ngx_uint_t error_ftype; - const char *error_reason; - - unsigned error_app:1; - unsigned send_timer_set:1; - unsigned closing:1; - unsigned draining:1; - unsigned key_phase:1; - unsigned in_retry:1; - unsigned initialized:1; - unsigned validated:1; -} ngx_quic_connection_t; - - -typedef struct { - ngx_queue_t queue; - uint64_t seqnum; - size_t len; - u_char id[NGX_QUIC_CID_LEN_MAX]; - u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; -} ngx_quic_client_id_t; - - -typedef struct { - ngx_udp_connection_t udp; - ngx_queue_t queue; - uint64_t seqnum; - size_t len; - u_char id[NGX_QUIC_CID_LEN_MAX]; -} ngx_quic_server_id_t; - - -typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - - -#if BORINGSSL_API_VERSION >= 10 -static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len); -static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len); -#else -static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *read_secret, - const uint8_t *write_secret, size_t secret_len); -#endif - -static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len); -static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); -static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, uint8_t alert); - - -static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, - ngx_quic_header_t *inpkt); -static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); -static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); -static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); -static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); -static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); -static void ngx_quic_input_handler(ngx_event_t *rev); - -static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); -static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); -static void ngx_quic_close_timer_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, - ngx_quic_connection_t *qc); - -static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, - ngx_quic_conf_t *conf); -static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); -static void ngx_quic_discard_ctx(ngx_connection_t *c, - enum ssl_encryption_level_t level); -static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); -static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t pn); -static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); -static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); - -static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *f); -static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, - ngx_msec_t *send_time); -static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, - enum ssl_encryption_level_t level, ngx_msec_t send_time); -static ngx_inline ngx_msec_t ngx_quic_pto(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static void ngx_quic_handle_stream_ack(ngx_connection_t *c, - ngx_quic_frame_t *f); - -static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, - ngx_quic_frame_handler_pt handler, void *data); -static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, - ngx_quic_frame_t *f, uint64_t offset_in); -static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); - -static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); -static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - -static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, - ngx_quic_max_data_frame_t *f); -static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); -static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); -static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); -static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); -static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); -static 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); -static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); -static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); -static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum); -static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); -static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); -static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); -static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, - ngx_str_t *id); -static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, - ngx_quic_connection_t *qc); -static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, - ngx_quic_connection_t *qc); - -static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, - ngx_quic_frame_t *frame); - -static ngx_int_t ngx_quic_output(ngx_connection_t *c); -static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); -static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static ssize_t ngx_quic_output_packet(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); -static ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, - size_t len); -static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); -static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); - -static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, - ngx_quic_send_ctx_t *ctx); -static void ngx_quic_pto_handler(ngx_event_t *ev); -static void ngx_quic_lost_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); -static void ngx_quic_resend_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static void ngx_quic_push_handler(ngx_event_t *ev); - -static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, - uint64_t id); -static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, - uint64_t id); -static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, - uint64_t id, size_t rcvbuf_size); -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 ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, - ngx_chain_t *in, off_t limit); -static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); -static void ngx_quic_stream_cleanup_handler(void *data); -static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); -static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); - -static void ngx_quic_congestion_ack(ngx_connection_t *c, - ngx_quic_frame_t *frame); -static void ngx_quic_congestion_lost(ngx_connection_t *c, - ngx_quic_frame_t *frame); - -static ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); -static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); -static ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, - size_t len); -static ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, - size_t limit); -static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, - size_t len); - - -static SSL_QUIC_METHOD quic_method = { -#if BORINGSSL_API_VERSION >= 10 - ngx_quic_set_read_secret, - ngx_quic_set_write_secret, -#else - ngx_quic_set_encryption_secrets, -#endif - ngx_quic_add_handshake_data, - ngx_quic_flush_flight, - ngx_quic_send_alert, -}; - - -#if (NGX_DEBUG) - -static void -ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) -{ - u_char *p, *last, *pos, *end; - ssize_t n; - uint64_t gap, range, largest, smallest; - ngx_uint_t i; - u_char buf[NGX_MAX_ERROR_STR]; - - p = buf; - last = buf + sizeof(buf); - - switch (f->type) { - - case NGX_QUIC_FT_CRYPTO: - p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", - f->u.crypto.length, f->u.crypto.offset); - break; - - case NGX_QUIC_FT_PADDING: - p = ngx_slprintf(p, last, "PADDING"); - break; - - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - - p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", - f->u.ack.range_count, f->u.ack.delay); - - if (f->data) { - pos = f->data->buf->pos; - end = f->data->buf->end; - - } else { - pos = NULL; - end = NULL; - } - - largest = f->u.ack.largest; - smallest = f->u.ack.largest - f->u.ack.first_range; - - if (largest == smallest) { - p = ngx_slprintf(p, last, "%uL", largest); - - } else { - p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); - } - - for (i = 0; i < f->u.ack.range_count; i++) { - n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); - if (n == NGX_ERROR) { - break; - } - - pos += n; - - largest = smallest - gap - 2; - smallest = largest - range; - - if (largest == smallest) { - p = ngx_slprintf(p, last, " %uL", largest); - - } else { - p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); - } - } - - if (f->type == NGX_QUIC_FT_ACK_ECN) { - p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", - f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); - } - break; - - case NGX_QUIC_FT_PING: - p = ngx_slprintf(p, last, "PING"); - break; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - p = ngx_slprintf(p, last, - "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", - f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); - break; - - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", - f->u.retire_cid.sequence_number); - break; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE_APP: - p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", - f->u.close.app ? "_APP" : "", f->u.close.error_code); - - if (f->u.close.reason.len) { - p = ngx_slprintf(p, last, " %V", &f->u.close.reason); - } - - if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); - } - - - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); - - if (f->u.stream.off) { - p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); - } - - if (f->u.stream.len) { - p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); - } - - if (f->u.stream.fin) { - p = ngx_slprintf(p, last, " fin:1"); - } - -#ifdef NGX_QUIC_DEBUG_FRAMES - { - ngx_chain_t *cl; - - p = ngx_slprintf(p, last, " data:"); - - for (cl = f->data; cl; cl = cl->next) { - p = ngx_slprintf(p, last, "%*xs", - cl->buf->last - cl->buf->pos, cl->buf->pos); - } - } -#endif - - break; - - case NGX_QUIC_FT_MAX_DATA: - p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", - f->u.max_data.max_data); - break; - - case NGX_QUIC_FT_RESET_STREAM: - p = ngx_slprintf(p, last, "RESET_STREAM" - " id:0x%xL error_code:0x%xL final_size:0x%xL", - f->u.reset_stream.id, f->u.reset_stream.error_code, - f->u.reset_stream.final_size); - break; - - case NGX_QUIC_FT_STOP_SENDING: - p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", - f->u.stop_sending.id, f->u.stop_sending.error_code); - break; - - case NGX_QUIC_FT_STREAMS_BLOCKED: - case NGX_QUIC_FT_STREAMS_BLOCKED2: - p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", - f->u.streams_blocked.limit, f->u.streams_blocked.bidi); - break; - - case NGX_QUIC_FT_MAX_STREAMS: - case NGX_QUIC_FT_MAX_STREAMS2: - p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", - f->u.max_streams.limit, f->u.max_streams.bidi); - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", - f->u.max_stream_data.id, f->u.max_stream_data.limit); - break; - - - case NGX_QUIC_FT_DATA_BLOCKED: - p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", - f->u.data_blocked.limit); - break; - - case NGX_QUIC_FT_STREAM_DATA_BLOCKED: - p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", - f->u.stream_data_blocked.id, - f->u.stream_data_blocked.limit); - break; - - case NGX_QUIC_FT_PATH_CHALLENGE: - p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", - sizeof(f->u.path_challenge.data), - f->u.path_challenge.data); - break; - - case NGX_QUIC_FT_PATH_RESPONSE: - p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", - sizeof(f->u.path_challenge.data), - f->u.path_challenge.data); - break; - - case NGX_QUIC_FT_NEW_TOKEN: - p = ngx_slprintf(p, last, "NEW_TOKEN"); - break; - - case NGX_QUIC_FT_HANDSHAKE_DONE: - p = ngx_slprintf(p, last, "HANDSHAKE DONE"); - break; - - default: - p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); - break; - } - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", - tx ? "tx" : "rx", ngx_quic_level_name(f->level), - p - buf, buf); -} - - -static void -ngx_quic_connstate_dbg(ngx_connection_t *c) -{ - u_char *p, *last; - ngx_quic_connection_t *qc; - u_char buf[NGX_MAX_ERROR_STR]; - - p = buf; - last = p + sizeof(buf); - - qc = ngx_quic_get_connection(c); - - p = ngx_slprintf(p, last, "state:"); - - if (qc) { - - if (qc->error) { - p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); - p = ngx_slprintf(p, last, " error:%ui", qc->error); - - if (qc->error_reason) { - p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); - } - } - - 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" : ""); - p = ngx_slprintf(p, last, "%s", qc->in_retry ? " retry" : ""); - p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); - - } else { - p = ngx_slprintf(p, last, " early"); - } - - if (c->read->timer_set) { - p = ngx_slprintf(p, last, - qc && qc->send_timer_set ? " send:%M" : " read:%M", - c->read->timer.key - ngx_current_msec); - } - - if (qc) { - - if (qc->push.timer_set) { - p = ngx_slprintf(p, last, " push:%M", - qc->push.timer.key - ngx_current_msec); - } - - if (qc->pto.timer_set) { - p = ngx_slprintf(p, last, " pto:%M", - qc->pto.timer.key - ngx_current_msec); - } - - if (qc->close.timer_set) { - p = ngx_slprintf(p, last, " close:%M", - qc->close.timer.key - ngx_current_msec); - } - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic %*s", p - buf, buf); -} - -#else - -#define ngx_quic_log_frame(log, f, tx) -#define ngx_quic_connstate_dbg(c) - -#endif - - -#if BORINGSSL_API_VERSION >= 10 - -static int -ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *rsecret, size_t secret_len) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_read_secret() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic read secret len:%uz %*xs", secret_len, - secret_len, rsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, - cipher, rsecret, secret_len); -} - - -static int -ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_write_secret() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic write secret len:%uz %*xs", secret_len, - secret_len, wsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); -} - -#else - -static int -ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *rsecret, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_connection_t *c; - const SSL_CIPHER *cipher; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_encryption_secrets() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic read secret len:%uz %*xs", secret_len, - secret_len, rsecret); -#endif - - cipher = SSL_get_current_cipher(ssl_conn); - - if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, - cipher, rsecret, secret_len) - != 1) - { - return 0; - } - - if (level == ssl_encryption_early_data) { - return 1; - } - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic write secret len:%uz %*xs", secret_len, - secret_len, wsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); -} - -#endif - - -static int -ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len) -{ - u_char *p, *end; - size_t client_params_len; - const uint8_t *client_params; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; - ngx_quic_frames_stream_t *fs; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - 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 defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - if (qc->conf->require_alpn) { - unsigned int len; - const unsigned char *data; - - SSL_get0_alpn_selected(ssl_conn, &data, &len); - - if (len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; - qc->error_reason = "unsupported protocol in ALPN extension"; - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; - } - } -#endif - - 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); - - if (client_params_len == 0) { - /* quic-tls 8.2 */ - 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"); - return 0; - } - - p = (u_char *) client_params; - end = p + client_params_len; - - if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) - != NGX_OK) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "failed to process transport parameters"; - - return 0; - } - - if (qc->ctp.max_idle_timeout > 0 - && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) - { - qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; - } - - if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE - || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "invalid maximum packet size"; - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic maximum packet size is invalid"); - return 0; - } - - if (qc->ctp.max_udp_payload_size > ngx_quic_max_udp_payload(c)) { - qc->ctp.max_udp_payload_size = ngx_quic_max_udp_payload(c); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic client maximum packet size truncated"); - } - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - if (qc->scid.len != qc->ctp.initial_scid.len - || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, - qc->scid.len) != 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic client initial_source_connection_id " - "mismatch"); - return 0; - } -#endif - - qc->streams.server_max_streams_bidi = qc->ctp.initial_max_streams_bidi; - qc->streams.server_max_streams_uni = qc->ctp.initial_max_streams_uni; - - qc->client_tp_done = 1; - } - - fs = &qc->crypto[level]; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return 0; - } - - frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); - if (frame->data == NGX_CHAIN_ERROR) { - return 0; - } - - frame->level = level; - frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset = fs->sent; - frame->u.crypto.length = len; - - fs->sent += len; - - ngx_quic_queue_frame(qc, frame); - - return 1; -} - - -static int -ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) -{ -#if (NGX_DEBUG) - ngx_connection_t *c; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_flush_flight()"); -#endif - return 1; -} - - -static int -ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, - uint8_t alert) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_alert() lvl:%d alert:%d", - (int) level, (int) alert); - - qc = ngx_quic_get_connection(c); - if (qc == NULL) { - return 1; - } - - qc->error_level = level; - qc->error = NGX_QUIC_ERR_CRYPTO(alert); - qc->error_reason = "TLS alert"; - qc->error_app = 0; - qc->error_ftype = 0; - - if (ngx_quic_send_cc(c) != NGX_OK) { - return 0; - } - - return 1; -} - - -void -ngx_quic_run(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"); - - rc = ngx_quic_input(c, c->buffer, conf); - if (rc != NGX_OK) { - ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); - return; - } - - qc = ngx_quic_get_connection(c); - - ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT - : qc->tp.max_idle_timeout); - - c->read->handler = ngx_quic_input_handler; - - ngx_quic_connstate_dbg(c); - return; -} - - -static ngx_quic_connection_t * -ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) -{ - ngx_uint_t i; - ngx_quic_tp_t *ctp; - ngx_quic_client_id_t *cid; - ngx_quic_connection_t *qc; - - qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); - if (qc == NULL) { - return NULL; - } - - qc->keys = ngx_quic_keys_new(c->pool); - if (qc->keys == NULL) { - return NULL; - } - - qc->version = pkt->version; - - ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, - ngx_quic_rbtree_insert_stream); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ngx_queue_init(&qc->send_ctx[i].frames); - ngx_queue_init(&qc->send_ctx[i].sent); - qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; - qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; - qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; - qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; - } - - qc->send_ctx[0].level = ssl_encryption_initial; - qc->send_ctx[1].level = ssl_encryption_handshake; - qc->send_ctx[2].level = ssl_encryption_application; - - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { - ngx_queue_init(&qc->crypto[i].frames); - } - - ngx_queue_init(&qc->free_frames); - ngx_queue_init(&qc->client_ids); - ngx_queue_init(&qc->server_ids); - ngx_queue_init(&qc->free_client_ids); - ngx_queue_init(&qc->free_server_ids); - - qc->avg_rtt = NGX_QUIC_INITIAL_RTT; - qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; - qc->min_rtt = NGX_TIMER_INFINITE; - - /* - * qc->latest_rtt = 0 - * qc->nclient_ids = 0 - * qc->nserver_ids = 0 - * qc->max_retired_seqnum = 0 - */ - - qc->received = pkt->raw->last - pkt->raw->start; - - qc->pto.log = c->log; - qc->pto.data = c; - qc->pto.handler = ngx_quic_pto_handler; - qc->pto.cancelable = 1; - - qc->push.log = c->log; - qc->push.data = c; - qc->push.handler = ngx_quic_push_handler; - qc->push.cancelable = 1; - - qc->conf = conf; - qc->tp = conf->tp; - - if (qc->tp.disable_active_migration) { - qc->sockaddr = ngx_palloc(c->pool, c->socklen); - if (qc->sockaddr == NULL) { - return NULL; - } - - ngx_memcpy(qc->sockaddr, c->sockaddr, c->socklen); - qc->socklen = c->socklen; - } - - ctp = &qc->ctp; - ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); - ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; - ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; - - qc->streams.recv_max_data = qc->tp.initial_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->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, - ngx_max(2 * qc->tp.max_udp_payload_size, - 14720)); - qc->congestion.ssthresh = (size_t) -1; - qc->congestion.recovery_start = ngx_current_msec; - - qc->odcid.len = pkt->dcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); - if (qc->odcid.data == NULL) { - return NULL; - } - - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NULL; - } - - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { - return NULL; - } - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - qc->tp.original_dcid = qc->odcid; -#endif - qc->tp.initial_scid = qc->dcid; - - qc->scid.len = pkt->scid.len; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); - if (qc->scid.data == NULL) { - return NULL; - } - ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); - - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NULL; - } - - cid->seqnum = 0; - cid->len = pkt->scid.len; - ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); - - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - qc->client_seqnum = 0; - - qc->server_seqnum = NGX_QUIC_UNSET_PN; - - return qc; -} - - -static ngx_int_t -ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) -{ - u_char *token; - size_t len, max; - uint16_t rndbytes; - u_char buf[NGX_QUIC_MAX_SR_PACKET]; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handle stateless reset output"); - - if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { - return NGX_DECLINED; - } - - if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { - len = pkt->len - 1; - - } else { - max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); - - if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { - return NGX_ERROR; - } - - len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) - + NGX_QUIC_MIN_SR_PACKET; - } - - if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { - return NGX_ERROR; - } - - buf[0] &= ~NGX_QUIC_PKT_LONG; - buf[0] |= NGX_QUIC_PKT_FIXED_BIT; - - token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; - - if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token) - != NGX_OK) - { - return NGX_ERROR; - } - - (void) ngx_quic_send(c, buf, len); - - return NGX_DECLINED; -} - - -static ngx_int_t -ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *tail, ch; - ngx_uint_t i; - ngx_queue_t *q; - ngx_quic_client_id_t *cid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - /* A stateless reset uses an entire UDP datagram */ - if (pkt->raw->start != pkt->data) { - return NGX_DECLINED; - } - - tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; - - for (q = ngx_queue_head(&qc->client_ids); - q != ngx_queue_sentinel(&qc->client_ids); - q = ngx_queue_next(q)) - { - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - if (cid->seqnum == 0) { - /* no stateless reset token in initial connection id */ - continue; - } - - /* constant time comparison */ - - for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { - ch |= tail[i] ^ cid->sr_token[i]; - } - - if (ch == 0) { - return NGX_OK; - } - } - - return NGX_DECLINED; -} - - -static ngx_int_t -ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) -{ - size_t len; - ngx_quic_header_t pkt; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sending version negotiation packet"); - - pkt.log = c->log; - pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; - pkt.dcid = inpkt->scid; - pkt.scid = inpkt->dcid; - - len = ngx_quic_create_version_negotiation(&pkt, buf); - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic vnego packet to send len:%uz %*xs", len, len, buf); -#endif - - (void) ngx_quic_send(c, buf, len); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) -{ - if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic create server id %*xs", - (size_t) NGX_QUIC_SERVER_CID_LEN, id); - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_retry(ngx_connection_t *c) -{ - ssize_t len; - ngx_str_t res, token; - ngx_quic_header_t pkt; - ngx_quic_connection_t *qc; - u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; - - qc = ngx_quic_get_connection(c); - - if (ngx_quic_new_token(c, &token) != NGX_OK) { - return NGX_ERROR; - } - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; - pkt.version = qc->version; - pkt.log = c->log; - pkt.odcid = qc->odcid; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; - pkt.token = token; - - res.data = buf; - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet to send len:%uz %xV", res.len, &res); -#endif - - len = ngx_quic_send(c, res.data, res.len); - if (len == NGX_ERROR) { - return NGX_ERROR; - } - - qc->token = token; -#if (NGX_QUIC_DRAFT_VERSION < 28) - qc->tp.original_dcid = qc->odcid; -#endif - qc->tp.retry_scid = qc->dcid; - qc->in_retry = 1; - - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) -{ - int len, iv_len; - u_char *data, *p, *key, *iv; - ngx_msec_t now; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - struct sockaddr_in *sin; -#if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; -#endif - ngx_quic_connection_t *qc; - u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; - - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; - - break; -#endif - -#if (NGX_HAVE_UNIX_DOMAIN) - case AF_UNIX: - - len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now)); - data = c->addr_text.data; - - break; -#endif - - default: /* AF_INET */ - sin = (struct sockaddr_in *) c->sockaddr; - - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; - - break; - } - - p = ngx_cpymem(in, data, len); - - now = ngx_current_msec; - len += sizeof(now); - ngx_memcpy(p, &now, sizeof(now)); - - cipher = EVP_aes_256_cbc(); - iv_len = EVP_CIPHER_iv_length(cipher); - - token->len = iv_len + len + EVP_CIPHER_block_size(cipher); - token->data = ngx_pnalloc(c->pool, token->len); - if (token->data == NULL) { - return NGX_ERROR; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - qc = ngx_quic_get_connection(c); - key = qc->conf->token_key; - iv = token->data; - - if (RAND_bytes(iv, iv_len) <= 0 - || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) - { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len = iv_len; - - if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - EVP_CIPHER_CTX_free(ctx); - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic new token len:%uz %xV", token->len, token); -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - int len, tlen, iv_len; - u_char *key, *iv, *p, *data; - ngx_msec_t msec; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - struct sockaddr_in *sin; -#if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; -#endif - ngx_quic_connection_t *qc; - u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - - qc = ngx_quic_get_connection(c); - - /* Retry token */ - - if (qc->token.len) { - if (pkt->token.len != qc->token.len) { - goto bad_token; - } - - if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { - goto bad_token; - } - - return NGX_OK; - } - - /* NEW_TOKEN in a previous connection */ - - cipher = EVP_aes_256_cbc(); - key = qc->conf->token_key; - iv = pkt->token.data; - iv_len = EVP_CIPHER_iv_length(cipher); - - /* sanity checks */ - - if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { - goto bad_token; - } - - if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { - goto bad_token; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - p = pkt->token.data + iv_len; - len = pkt->token.len - iv_len; - - if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - goto bad_token; - } - - if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { - EVP_CIPHER_CTX_free(ctx); - goto bad_token; - } - - EVP_CIPHER_CTX_free(ctx); - - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; - - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; - - break; -#endif - -#if (NGX_HAVE_UNIX_DOMAIN) - case AF_UNIX: - - len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec)); - data = c->addr_text.data; - - break; -#endif - - default: /* AF_INET */ - sin = (struct sockaddr_in *) c->sockaddr; - - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; - - break; - } - - if (ngx_memcmp(tdec, data, len) != 0) { - goto bad_token; - } - - ngx_memcpy(&msec, tdec + len, sizeof(msec)); - - if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); - return NGX_DECLINED; - } - - return NGX_OK; - -bad_token: - - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - qc->error_reason = "invalid_token"; - - return NGX_DECLINED; -} - - -static ngx_int_t -ngx_quic_init_connection(ngx_connection_t *c) -{ - u_char *p; - size_t clen; - ssize_t len; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { - return NGX_ERROR; - } - - c->ssl->no_wait_shutdown = 1; - - ssl_conn = c->ssl->connection; - - if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_method() failed"); - return NGX_ERROR; - } - -#ifdef SSL_READ_EARLY_DATA_SUCCESS - if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { - SSL_set_quic_early_data_enabled(ssl_conn, 1); - } -#endif - - if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, - qc->tp.sr_token) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); - - len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); - /* always succeeds */ - - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - return NGX_ERROR; - } - - len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); - if (len < 0) { - return NGX_ERROR; - } - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic transport parameters len:%uz %*xs", len, len, p); -#endif - - if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_transport_params() failed"); - return NGX_ERROR; - } - -#if NGX_OPENSSL_QUIC_ZRTT_CTX - if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_early_data_context() failed"); - return NGX_ERROR; - } -#endif - - return NGX_OK; -} - - -static ngx_inline size_t -ngx_quic_max_udp_payload(ngx_connection_t *c) -{ - /* TODO: path MTU discovery */ - -#if (NGX_HAVE_INET6) - if (c->sockaddr->sa_family == AF_INET6) { - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; - } -#endif - - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; -} - - -static void -ngx_quic_input_handler(ngx_event_t *rev) -{ - ssize_t n; - ngx_int_t rc; - ngx_buf_t b; - ngx_connection_t *c; - ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); - - ngx_memzero(&b, sizeof(ngx_buf_t)); - b.start = buf; - b.end = buf + sizeof(buf); - b.pos = b.last = b.start; - b.memory = 1; - - c = rev->data; - qc = ngx_quic_get_connection(c); - - 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) { - qc->error_reason = "graceful shutdown"; - ngx_quic_close_connection(c, NGX_OK); - return; - } - - n = c->recv(c, b.start, b.end - b.start); - - if (n == NGX_AGAIN) { - if (qc->closing) { - ngx_quic_close_connection(c, NGX_OK); - } - return; - } - - if (n == NGX_ERROR) { - c->read->eof = 1; - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - if (qc->tp.disable_active_migration) { - if (c->socklen != qc->socklen - || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0) - { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic dropping packet from new address"); - return; - } - } - - b.last += n; - qc->received += n; - - rc = ngx_quic_input(c, &b, NULL); - - if (rc == NGX_ERROR) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - if (rc == NGX_DECLINED) { - return; - } - - /* rc == NGX_OK */ - - qc->send_timer_set = 0; - ngx_add_timer(rev, qc->tp.max_idle_timeout); - - ngx_quic_connstate_dbg(c); -} - - -static void -ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) -{ - ngx_pool_t *pool; - ngx_quic_connection_t *qc; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_close_connection rc:%i", rc); - - qc = ngx_quic_get_connection(c); - - if (qc == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close connection early error"); - - } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { - return; - } - - if (c->ssl) { - (void) ngx_ssl_shutdown(c); - } - - if (c->read->timer_set) { - ngx_del_timer(c->read); - } - -#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_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) -{ - ngx_uint_t i; - ngx_queue_t *q; - ngx_quic_send_ctx_t *ctx; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!qc->closing) { - - /* drop packets from retransmit queues, no ack is expected */ - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ngx_quic_free_frames(c, &qc->send_ctx[i].sent); - } - - if (rc == NGX_DONE) { - - /* - * 10.2. Idle Timeout - * - * If the idle timeout is enabled by either peer, a connection is - * silently closed and its state is discarded when it remains idle - */ - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic closing %s connection", - qc->draining ? "drained" : "idle"); - - } else { - - /* - * 10.3. Immediate Close - * - * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) - * to terminate the connection immediately. - */ - - qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) - : ssl_encryption_initial; - - if (rc == NGX_OK) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close drain:%d", - qc->draining); - - qc->close.log = c->log; - qc->close.data = c; - qc->close.handler = ngx_quic_close_timer_handler; - qc->close.cancelable = 1; - - ctx = ngx_quic_get_send_ctx(qc, qc->error_level); - - ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); - - qc->error = NGX_QUIC_ERR_NO_ERROR; - - } else { - if (qc->error == 0 && !qc->error_app) { - qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to %s error: %ui %s", - qc->error_app ? "app " : "", qc->error, - qc->error_reason ? qc->error_reason : ""); - } - - (void) ngx_quic_send_cc(c); - - if (qc->error_level == ssl_encryption_handshake) { - /* for clients that might not have handshake keys */ - qc->error_level = ssl_encryption_initial; - (void) ngx_quic_send_cc(c); - } - } - - qc->closing = 1; - } - - if (rc == NGX_ERROR && qc->close.timer_set) { - /* do not wait for timer in case of fatal error */ - ngx_del_timer(&qc->close); - } - - if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { - return NGX_AGAIN; - } - - if (qc->push.timer_set) { - ngx_del_timer(&qc->push); - } - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - if (qc->push.posted) { - ngx_delete_posted_event(&qc->push); - } - - while (!ngx_queue_empty(&qc->server_ids)) { - q = ngx_queue_head(&qc->server_ids); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - ngx_queue_remove(q); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - } - - if (qc->close.timer_set) { - return NGX_AGAIN; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic part of connection is terminated"); - - /* may be tested from SSL callback during SSL shutdown */ - c->udp = NULL; - - return NGX_OK; -} - - -void -ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, - const char *reason) -{ - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - qc->error = err; - qc->error_reason = reason; - qc->error_app = 1; - qc->error_ftype = 0; - - ngx_quic_close_connection(c, NGX_ERROR); -} - - -static void -ngx_quic_close_timer_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer"); - - c = ev->data; - ngx_quic_close_connection(c, NGX_DONE); -} - - -static ngx_int_t -ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_event_t *rev, *wev; - ngx_rbtree_t *tree; - ngx_rbtree_node_t *node; - ngx_quic_stream_t *qs; - -#if (NGX_DEBUG) - ngx_uint_t ns; -#endif - - tree = &qc->streams.tree; - - if (tree->root == tree->sentinel) { - return NGX_OK; - } - -#if (NGX_DEBUG) - ns = 0; -#endif - - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - - rev = qs->c->read; - rev->error = 1; - rev->ready = 1; - - wev = qs->c->write; - wev->error = 1; - wev->ready = 1; - - ngx_post_event(rev, &ngx_posted_events); - - if (rev->timer_set) { - ngx_del_timer(rev); - } - -#if (NGX_DEBUG) - ns++; -#endif - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic connection has %ui active streams", ns); - - return NGX_AGAIN; -} - - -static ngx_int_t -ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) -{ - u_char *p; - ngx_int_t rc; - ngx_uint_t good; - ngx_quic_header_t pkt; - ngx_quic_connection_t *qc; - - good = 0; - - p = b->pos; - - while (p < b->last) { - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - pkt.raw = b; - pkt.data = p; - pkt.len = b->last - p; - pkt.log = c->log; - pkt.flags = p[0]; - pkt.raw->pos++; - - qc = ngx_quic_get_connection(c); - if (qc) { - qc->error = 0; - qc->error_reason = 0; - } - - rc = ngx_quic_process_packet(c, conf, &pkt); - -#if (NGX_DEBUG) - if (pkt.parsed) { - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet %s done decr:%d pn:%L perr:%ui rc:%i", - ngx_quic_level_name(pkt.level), pkt.decrypted, - pkt.pn, pkt.error, rc); - } else { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet done parse failed rc:%i", rc); - } -#endif - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_OK) { - good = 1; - } - - /* NGX_OK || NGX_DECLINED */ - - /* - * we get NGX_DECLINED when there are no keys [yet] available - * to decrypt packet. - * Instead of queueing it, we ignore it and rely on the sender's - * retransmission: - * - * 12.2. Coalescing Packets: - * - * For example, if decryption fails (because the keys are - * not available or any other reason), the receiver MAY either - * discard or buffer the packet for later processing and MUST - * attempt to process the remaining packets. - * - * We also skip packets that don't match connection state - * or cannot be parsed properly. - */ - - /* b->pos is at header end, adjust by actual packet length */ - b->pos = pkt.data + pkt.len; - - /* firefox workaround: skip zero padding at the end of quic packet */ - while (b->pos < b->last && *(b->pos) == 0) { - b->pos++; - } - - p = b->pos; - } - - return good ? NGX_OK : NGX_DECLINED; -} - - -static ngx_int_t -ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) -{ - ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "parsing quic packet"; - - rc = ngx_quic_parse_packet(pkt); - - if (rc == NGX_DECLINED || rc == NGX_ERROR) { - return rc; - } - - pkt->parsed = 1; - - c->log->action = "processing quic packet"; - - qc = ngx_quic_get_connection(c); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet rx dcid len:%uz %xV", - pkt->dcid.len, &pkt->dcid); - -#if (NGX_DEBUG) - if (pkt->level != ssl_encryption_application) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet rx scid len:%uz %xV", - pkt->scid.len, &pkt->scid); - } - - if (pkt->level == ssl_encryption_initial) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic token len:%uz %xV", - pkt->token.len, &pkt->token); - } -#endif - - if (qc) { - - if (rc == NGX_ABORT) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported version: 0x%xD", pkt->version); - return NGX_DECLINED; - } - - if (pkt->level != ssl_encryption_application) { - if (pkt->version != qc->version) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic version mismatch: 0x%xD", pkt->version); - return NGX_DECLINED; - } - } - - if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { - - if (pkt->level == ssl_encryption_application) { - if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic stateless reset packet detected"); - - qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); - - return NGX_OK; - } - - return ngx_quic_send_stateless_reset(c, qc->conf, pkt); - } - - return NGX_DECLINED; - } - - if (qc->in_retry) { - - c->log->action = "retrying quic connection"; - - if (pkt->level != ssl_encryption_initial) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic discard late retry packet"); - return NGX_DECLINED; - } - - if (!pkt->token.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic discard retry packet without token"); - return NGX_DECLINED; - } - - qc->odcid.len = pkt->dcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } - - ngx_quic_clear_temp_server_ids(c); - - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { - return NGX_ERROR; - } - - qc->server_seqnum = 0; - - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { - return NGX_ERROR; - } - - qc->tp.initial_scid = qc->dcid; - qc->in_retry = 0; - - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_validate_token(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - - qc->validated = 1; - } - - } else { - - if (rc == NGX_ABORT) { - return ngx_quic_negotiate_version(c, pkt); - } - - if (pkt->level == ssl_encryption_initial) { - - c->log->action = "creating quic connection"; - - if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { - /* 7.2. Negotiating Connection IDs */ - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic too short dcid in initial" - " packet: len:%i", pkt->dcid.len); - return NGX_ERROR; - } - - qc = ngx_quic_new_connection(c, conf, pkt); - if (qc == NULL) { - return NGX_ERROR; - } - - c->udp = &qc->udp; - - if (ngx_terminate || ngx_exiting) { - qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; - return NGX_ERROR; - } - - if (pkt->token.len) { - rc = ngx_quic_validate_token(c, pkt); - - if (rc == NGX_OK) { - qc->validated = 1; - - } else if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else { - /* NGX_DECLINED */ - if (conf->retry) { - return ngx_quic_send_retry(c); - } - } - - } else if (conf->retry) { - return ngx_quic_send_retry(c); - } - - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { - return NGX_ERROR; - } - - qc->server_seqnum = 0; - - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { - return NGX_ERROR; - } - - } else if (pkt->level == ssl_encryption_application) { - return ngx_quic_send_stateless_reset(c, conf, pkt); - - } else { - return NGX_ERROR; - } - } - - c->log->action = "decrypting packet"; - - if (!ngx_quic_keys_available(qc->keys, pkt->level)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no level %d keys yet, ignoring packet", pkt->level); - return NGX_DECLINED; - } - - pkt->keys = qc->keys; - pkt->key_phase = qc->key_phase; - pkt->plaintext = buf; - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); - if (rc != NGX_OK) { - qc->error = pkt->error; - qc->error_reason = "failed to decrypt packet"; - return rc; - } - - pkt->decrypted = 1; - - if (c->ssl == NULL) { - if (ngx_quic_init_connection(c) != NGX_OK) { - return NGX_ERROR; - } - } - - if (pkt->level == ssl_encryption_handshake) { - /* - * 4.10.1. The successful use of Handshake packets indicates - * that no more Initial packets need to be exchanged - */ - ngx_quic_discard_ctx(c, ssl_encryption_initial); - - if (qc->validated == 0) { - qc->validated = 1; - ngx_post_event(&qc->push, &ngx_posted_events); - } - } - - pkt->received = ngx_current_msec; - - c->log->action = "handling payload"; - - if (pkt->level != ssl_encryption_application) { - return ngx_quic_payload_handler(c, pkt); - } - - if (!pkt->key_update) { - return ngx_quic_payload_handler(c, pkt); - } - - /* switch keys and generate next on Key Phase change */ - - qc->key_phase ^= 1; - ngx_quic_keys_switch(c, qc->keys); - - rc = ngx_quic_payload_handler(c, pkt); - if (rc != NGX_OK) { - return rc; - } - - return ngx_quic_keys_update(c, qc->keys); -} - - -static ngx_int_t -ngx_quic_init_secrets(ngx_connection_t *c) -{ - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) - != NGX_OK) - { - return NGX_ERROR; - } - - qc->initialized = 1; - - return NGX_OK; -} - - -static void -ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!ngx_quic_keys_available(qc->keys, level)) { - return; - } - - ngx_quic_keys_discard(qc->keys, level); - - qc->pto_count = 0; - - ctx = ngx_quic_get_send_ctx(qc, level); - - while (!ngx_queue_empty(&ctx->sent)) { - q = ngx_queue_head(&ctx->sent); - ngx_queue_remove(q); - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_ack(c, f); - ngx_quic_free_frame(c, f); - } - - while (!ngx_queue_empty(&ctx->frames)) { - q = ngx_queue_head(&ctx->frames); - ngx_queue_remove(q); - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_ack(c, f); - ngx_quic_free_frame(c, f); - } - - if (level == ssl_encryption_initial) { - ngx_quic_clear_temp_server_ids(c); - } - - ctx->send_ack = 0; -} - - -static ngx_int_t -ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) -{ - ngx_queue_t *q; - ngx_quic_client_id_t *cid; - - if (pkt->level == ssl_encryption_application) { - return NGX_OK; - } - - for (q = ngx_queue_head(&qc->client_ids); - q != ngx_queue_sentinel(&qc->client_ids); - q = ngx_queue_next(q)) - { - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - if (pkt->scid.len == cid->len - && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) - { - return NGX_OK; - } - } - - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - u_char *end, *p; - ssize_t len; - ngx_buf_t buf; - ngx_uint_t do_close; - ngx_chain_t chain; - ngx_quic_frame_t frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->closing) { - /* - * 10.1 Closing and Draining Connection States - * ... delayed or reordered packets are properly discarded. - * - * An endpoint retains only enough information to generate - * a packet containing a CONNECTION_CLOSE frame and to identify - * packets as belonging to the connection. - */ - - qc->error_level = pkt->level; - qc->error = NGX_QUIC_ERR_NO_ERROR; - qc->error_reason = "connection is closing, packet discarded"; - qc->error_ftype = 0; - qc->error_app = 0; - - return ngx_quic_send_cc(c); - } - - p = pkt->payload.data; - end = p + pkt->payload.len; - - do_close = 0; - - while (p < end) { - - c->log->action = "parsing frames"; - - ngx_memzero(&buf, sizeof(ngx_buf_t)); - buf.temporary = 1; - - chain.buf = &buf; - chain.next = NULL; - frame.data = &chain; - - len = ngx_quic_parse_frame(pkt, p, end, &frame); - - if (len < 0) { - qc->error = pkt->error; - return NGX_ERROR; - } - - ngx_quic_log_frame(c->log, &frame, 0); - - c->log->action = "handling frames"; - - p += len; - - switch (frame.type) { - - case NGX_QUIC_FT_ACK: - if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { - return NGX_ERROR; - } - - continue; - - case NGX_QUIC_FT_PADDING: - /* no action required */ - continue; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE_APP: - do_close = 1; - continue; - } - - /* got there with ack-eliciting packet */ - pkt->need_ack = 1; - - switch (frame.type) { - - case NGX_QUIC_FT_CRYPTO: - - if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_PING: - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_MAX_DATA: - - if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_STREAMS_BLOCKED: - case NGX_QUIC_FT_STREAMS_BLOCKED2: - - if (ngx_quic_handle_streams_blocked_frame(c, pkt, - &frame.u.streams_blocked) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_STREAM_DATA_BLOCKED: - - if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, - &frame.u.stream_data_blocked) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - - if (ngx_quic_handle_max_stream_data_frame(c, pkt, - &frame.u.max_stream_data) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_RESET_STREAM: - - if (ngx_quic_handle_reset_stream_frame(c, pkt, - &frame.u.reset_stream) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_STOP_SENDING: - - if (ngx_quic_handle_stop_sending_frame(c, pkt, - &frame.u.stop_sending) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_MAX_STREAMS: - case NGX_QUIC_FT_MAX_STREAMS2: - - if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_PATH_CHALLENGE: - - if (ngx_quic_handle_path_challenge_frame(c, pkt, - &frame.u.path_challenge) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - - if (ngx_quic_handle_new_connection_id_frame(c, pkt, &frame.u.ncid) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - - if (ngx_quic_handle_retire_connection_id_frame(c, pkt, - &frame.u.retire_cid) - != NGX_OK) - { - return NGX_ERROR; - } - - break; - - case NGX_QUIC_FT_PATH_RESPONSE: - - /* TODO: handle */ - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic frame handler not implemented"); - break; - - default: - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic missing frame handler"); - return NGX_ERROR; - } - } - - if (p != end) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic trailing garbage in payload:%ui bytes", end - p); - - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - return NGX_ERROR; - } - - if (do_close) { - qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); - } - - if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - uint64_t base, largest, smallest, gs, ge, gap, range, pn; - uint64_t prev_pending; - ngx_uint_t i, nr; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_range_t *r; - ngx_quic_connection_t *qc; - - c->log->action = "preparing ack"; - - qc = ngx_quic_get_connection(c); - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" - " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, - ctx->first_range, ctx->nranges); - - prev_pending = ctx->pending_ack; - - if (pkt->need_ack) { - - ngx_post_event(&qc->push, &ngx_posted_events); - - if (ctx->send_ack == 0) { - ctx->ack_delay_start = ngx_current_msec; - } - - ctx->send_ack++; - - if (ctx->pending_ack == NGX_QUIC_UNSET_PN - || ctx->pending_ack < pkt->pn) - { - ctx->pending_ack = pkt->pn; - } - } - - base = ctx->largest_range; - pn = pkt->pn; - - if (base == NGX_QUIC_UNSET_PN) { - ctx->largest_range = pn; - ctx->largest_received = pkt->received; - return NGX_OK; - } - - if (base == pn) { - return NGX_OK; - } - - largest = base; - smallest = largest - ctx->first_range; - - if (pn > base) { - - if (pn - base == 1) { - ctx->first_range++; - ctx->largest_range = pn; - ctx->largest_received = pkt->received; - - return NGX_OK; - - } else { - /* new gap in front of current largest */ - - /* no place for new range, send current range as is */ - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - - if (prev_pending != NGX_QUIC_UNSET_PN) { - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - } - - if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - } - - gap = pn - base - 2; - range = ctx->first_range; - - ctx->first_range = 0; - ctx->largest_range = pn; - ctx->largest_received = pkt->received; - - /* packet is out of order, force send */ - if (pkt->need_ack) { - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } - - i = 0; - - goto insert; - } - } - - /* pn < base, perform lookup in existing ranges */ - - /* packet is out of order */ - if (pkt->need_ack) { - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } - - if (pn >= smallest && pn <= largest) { - return NGX_OK; - } - -#if (NGX_SUPPRESS_WARN) - r = NULL; -#endif - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - - ge = smallest - 1; - gs = ge - r->gap; - - if (pn >= gs && pn <= ge) { - - if (gs == ge) { - /* gap size is exactly one packet, now filled */ - - /* data moves to previous range, current is removed */ - - if (i == 0) { - ctx->first_range += r->range + 2; - - } else { - ctx->ranges[i - 1].range += r->range + 2; - } - - nr = ctx->nranges - i - 1; - if (nr) { - ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], - sizeof(ngx_quic_ack_range_t) * nr); - } - - ctx->nranges--; - - } else if (pn == gs) { - /* current gap shrinks from tail (current range grows) */ - r->gap--; - r->range++; - - } else if (pn == ge) { - /* current gap shrinks from head (previous range grows) */ - r->gap--; - - if (i == 0) { - ctx->first_range++; - - } else { - ctx->ranges[i - 1].range++; - } - - } else { - /* current gap is split into two parts */ - - gap = ge - pn - 1; - range = 0; - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - if (prev_pending != NGX_QUIC_UNSET_PN) { - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - } - - if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - } - - r->gap = pn - gs - 1; - goto insert; - } - - return NGX_OK; - } - - largest = smallest - r->gap - 2; - smallest = largest - r->range; - - if (pn >= smallest && pn <= largest) { - /* this packet number is already known */ - return NGX_OK; - } - - } - - if (pn == smallest - 1) { - /* extend first or last range */ - - if (i == 0) { - ctx->first_range++; - - } else { - r->range++; - } - - return NGX_OK; - } - - /* nothing found, add new range at the tail */ - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - /* packet is too old to keep it */ - - if (pkt->need_ack) { - return ngx_quic_send_ack_range(c, ctx, pn, pn); - } - - return NGX_OK; - } - - gap = smallest - 2 - pn; - range = 0; - -insert: - - if (ctx->nranges < NGX_QUIC_MAX_RANGES) { - ctx->nranges++; - } - - ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], - sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); - - ctx->ranges[i].gap = gap; - ctx->ranges[i].range = range; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t smallest, uint64_t largest) -{ - ngx_quic_frame_t *frame; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ctx->level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = largest; - frame->u.ack.delay = 0; - frame->u.ack.range_count = 0; - frame->u.ack.first_range = largest - smallest; - - return NGX_OK; -} - - -static void -ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t pn) -{ - uint64_t base; - ngx_uint_t i, smallest, largest; - ngx_quic_ack_range_t *r; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" - " fr:%uL nranges:%ui", pn, ctx->largest_range, - ctx->first_range, ctx->nranges); - - base = ctx->largest_range; - - if (base == NGX_QUIC_UNSET_PN) { - return; - } - - if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - - largest = base; - smallest = largest - ctx->first_range; - - if (pn >= largest) { - ctx->largest_range = NGX_QUIC_UNSET_PN; - ctx->first_range = 0; - ctx->nranges = 0; - return; - } - - if (pn >= smallest) { - ctx->first_range = largest - pn - 1; - ctx->nranges = 0; - return; - } - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - - largest = smallest - r->gap - 2; - smallest = largest - r->range; - - if (pn >= largest) { - ctx->nranges = i; - return; - } - if (pn >= smallest) { - r->range = largest - pn - 1; - ctx->nranges = i + 1; - return; - } - } -} - - -static ngx_int_t -ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t len, left; - uint64_t ack_delay; - ngx_buf_t *b; - ngx_uint_t i; - ngx_chain_t *cl, **ll; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ack_delay = ngx_current_msec - ctx->largest_received; - ack_delay *= 1000; - ack_delay >>= qc->ctp.ack_delay_exponent; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - ll = &frame->data; - b = NULL; - - for (i = 0; i < ctx->nranges; i++) { - len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, - ctx->ranges[i].range); - - left = b ? b->end - b->last : 0; - - if (left < len) { - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_ERROR; - } - - *ll = cl; - ll = &cl->next; - - b = cl->buf; - left = b->end - b->last; - - if (left < len) { - return NGX_ERROR; - } - } - - b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, - ctx->ranges[i].range); - - frame->u.ack.ranges_length += len; - } - - *ll = NULL; - - frame->level = ctx->level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = ctx->largest_range; - frame->u.ack.delay = ack_delay; - frame->u.ack.range_count = ctx->nranges; - frame->u.ack.first_range = ctx->first_range; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_cc(ngx_connection_t *c) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->draining) { - return NGX_OK; - } - - if (!qc->initialized) { - /* try to initialize secrets to send an early error */ - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_OK; - } - } - - if (qc->closing - && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) - { - /* dot not send CC too often */ - return NGX_OK; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = qc->error_level; - frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; - frame->u.close.error_code = qc->error; - frame->u.close.frame_type = qc->error_ftype; - frame->u.close.app = qc->error_app; - - if (qc->error_reason) { - frame->u.close.reason.len = ngx_strlen(qc->error_reason); - frame->u.close.reason.data = (u_char *) qc->error_reason; - } - - ngx_quic_queue_frame(qc, frame); - - qc->last_cc = ngx_current_msec; - - return ngx_quic_output(c); -} - - -static ngx_int_t -ngx_quic_send_new_token(ngx_connection_t *c) -{ - ngx_str_t token; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!qc->conf->retry) { - return NGX_OK; - } - - if (ngx_quic_new_token(c, &token) != NGX_OK) { - return NGX_ERROR; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_NEW_TOKEN; - frame->u.token.length = token.len; - frame->u.token.data = token.data; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *f) -{ - ssize_t n; - u_char *pos, *end; - uint64_t min, max, gap, range; - ngx_msec_t send_time; - ngx_uint_t i; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_frame_t *ack; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_handle_ack_frame level:%d", pkt->level); - - ack = &f->u.ack; - - /* - * If any computed packet number is negative, an endpoint MUST - * generate a connection error of type FRAME_ENCODING_ERROR. - * (19.3.1) - */ - - if (ack->first_range > ack->largest) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid first range in ack frame"); - return NGX_ERROR; - } - - min = ack->largest - ack->first_range; - max = ack->largest; - - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) - != NGX_OK) - { - return NGX_ERROR; - } - - /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { - ctx->largest_ack = max; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic updated largest received ack:%uL", max); - - /* - * An endpoint generates an RTT sample on receiving an - * ACK frame that meets the following two conditions: - * - * - the largest acknowledged packet number is newly acknowledged - * - at least one of the newly acknowledged packets was ack-eliciting. - */ - - if (send_time != NGX_TIMER_INFINITE) { - ngx_quic_rtt_sample(c, ack, pkt->level, send_time); - } - } - - if (f->data) { - pos = f->data->buf->pos; - end = f->data->buf->last; - - } else { - pos = NULL; - end = NULL; - } - - for (i = 0; i < ack->range_count; i++) { - - n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - pos += n; - - if (gap + 2 > min) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); - return NGX_ERROR; - } - - max = min - gap - 2; - - if (range > max) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); - return NGX_ERROR; - } - - min = max - range; - - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) - != NGX_OK) - { - return NGX_ERROR; - } - } - - return ngx_quic_detect_lost(c); -} - - -static ngx_int_t -ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t min, uint64_t max, ngx_msec_t *send_time) -{ - ngx_uint_t found; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - *send_time = NGX_TIMER_INFINITE; - found = 0; - - q = ngx_queue_last(&ctx->sent); - - while (q != ngx_queue_sentinel(&ctx->sent)) { - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - q = ngx_queue_prev(q); - - if (f->pnum >= min && f->pnum <= max) { - ngx_quic_congestion_ack(c, f); - - switch (f->type) { - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - ngx_quic_handle_stream_ack(c, f); - break; - } - - if (f->pnum == max) { - *send_time = f->last; - } - - ngx_queue_remove(&f->queue); - ngx_quic_free_frame(c, f); - found = 1; - } - } - - if (!found) { - - if (max < ctx->pnum) { - /* duplicate ACK or ACK for non-ack-eliciting frame */ - return NGX_OK; - } - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic ACK for the packet not sent"); - - qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - qc->error_ftype = NGX_QUIC_FT_ACK; - qc->error_reason = "unknown packet number"; - - return NGX_ERROR; - } - - if (!qc->push.timer_set) { - ngx_post_event(&qc->push, &ngx_posted_events); - } - - qc->pto_count = 0; - - return NGX_OK; -} - - -static void -ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, - enum ssl_encryption_level_t level, ngx_msec_t send_time) -{ - ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - latest_rtt = ngx_current_msec - send_time; - qc->latest_rtt = latest_rtt; - - if (qc->min_rtt == NGX_TIMER_INFINITE) { - qc->min_rtt = latest_rtt; - qc->avg_rtt = latest_rtt; - qc->rttvar = latest_rtt / 2; - - } else { - qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); - - ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; - - if (c->ssl->handshaked) { - ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); - } - - adjusted_rtt = latest_rtt; - - if (qc->min_rtt + ack_delay < latest_rtt) { - adjusted_rtt -= ack_delay; - } - - qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; - rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); - qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; - } - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic rtt sample latest:%M min:%M avg:%M var:%M", - latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); -} - - -static ngx_inline ngx_msec_t -ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - ngx_msec_t duration; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - /* PTO calculation: quic-recovery, Appendix 8 */ - duration = qc->avg_rtt; - - duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); - duration <<= qc->pto_count; - - if (qc->congestion.in_flight == 0) { /* no in-flight packets */ - return duration; - } - - if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { - duration += qc->ctp.max_ack_delay << qc->pto_count; - } - - return duration; -} - - -static void -ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - uint64_t sent, unacked; - ngx_event_t *wev; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (sn == NULL) { - return; - } - - wev = sn->c->write; - sent = sn->c->sent; - unacked = sent - sn->acked; - - if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } - - sn->acked += f->u.stream.length; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, - "quic stream ack len:%uL acked:%uL unacked:%uL", - f->u.stream.length, sn->acked, sent - sn->acked); -} - - -static ngx_int_t -ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) -{ - size_t full_len; - ngx_int_t rc; - ngx_queue_t *q; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - if (f->offset > fs->received) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic out-of-order frame: expecting:%uL got:%uL", - fs->received, f->offset); - - return ngx_quic_buffer_frame(c, fs, frame); - } - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; - } - - /* intersecting data range, frame modified */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; - } - - /* rc == NGX_OK */ - - fs->received += f->length; - - /* now check the queue if we can continue with buffered frames */ - - do { - q = ngx_queue_head(&fs->frames); - if (q == ngx_queue_sentinel(&fs->frames)) { - break; - } - - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &frame->u.ord; - - if (f->offset > fs->received) { - /* gap found, nothing more to do */ - break; - } - - full_len = f->length; - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - ngx_queue_remove(q); - fs->total -= f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic skipped buffered frame, total:%ui", - fs->total); - ngx_quic_free_frame(c, frame); - continue; - } - - /* frame was adjusted, proceed to input */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; - } - - fs->received += f->length; - fs->total -= full_len; - - ngx_queue_remove(q); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic consumed buffered frame, total:%ui", fs->total); - - ngx_quic_free_frame(c, frame); - - } while (1); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, - uint64_t offset_in) -{ - size_t tail, n; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - tail = offset_in - f->offset; - - if (tail >= f->length) { - /* range preceeding already received data or duplicate, ignore */ - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic old or duplicate data in ordered frame, ignored"); - return NGX_DONE; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic adjusted ordered frame data start to expected offset"); - - /* intersecting range: adjust data size */ - - f->offset += tail; - f->length -= tail; - - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - n = ngx_buf_size(b); - - if (n >= tail) { - b->pos += tail; - break; - } - - cl->buf->pos = cl->buf->last; - tail -= n; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame) -{ - ngx_queue_t *q; - ngx_quic_frame_t *dst, *item; - ngx_quic_ordered_frame_t *f, *df; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_buffer_frame"); - - f = &frame->u.ord; - - /* frame start offset is in the future, buffer it */ - - dst = ngx_quic_alloc_frame(c); - if (dst == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); - - dst->data = ngx_quic_copy_chain(c, frame->data, 0); - if (dst->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - df = &dst->u.ord; - - fs->total += f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ordered frame with unexpected offset:" - " buffered total:%ui", fs->total); - - if (ngx_queue_empty(&fs->frames)) { - ngx_queue_insert_after(&fs->frames, &dst->queue); - return NGX_OK; - } - - for (q = ngx_queue_last(&fs->frames); - q != ngx_queue_sentinel(&fs->frames); - q = ngx_queue_prev(q)) - { - item = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &item->u.ord; - - if (f->offset < df->offset) { - ngx_queue_insert_after(q, &dst->queue); - return NGX_OK; - } - } - - ngx_queue_insert_after(&fs->frames, &dst->queue); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) -{ - uint64_t last; - ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - ngx_quic_crypto_frame_t *f; - ngx_quic_frames_stream_t *fs; - - qc = ngx_quic_get_connection(c); - fs = &qc->crypto[pkt->level]; - f = &frame->u.crypto; - - /* no overflow since both values are 62-bit */ - last = f->offset + f->length; - - if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { - qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; - return NGX_ERROR; - } - - rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, - NULL); - if (rc != NGX_DECLINED) { - return rc; - } - - /* speeding up handshake completion */ - - if (pkt->level == ssl_encryption_initial) { - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - if (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - while (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - } - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - int n, sslerr; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ssl_conn = c->ssl->connection; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - b->pos, b->last - b->pos)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; - } - } - - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n <= 0) { - sslerr = SSL_get_error(ssl_conn, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - - if (sslerr != SSL_ERROR_WANT_READ) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - return NGX_ERROR; - } - - return NGX_OK; - } - - if (SSL_in_init(ssl_conn)) { - return NGX_OK; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake completed successfully"); - - c->ssl->handshaked = 1; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - /* 12.4 Frames and frame types, figure 8 */ - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(qc, frame); - - if (ngx_quic_send_new_token(c) != NGX_OK) { - return NGX_ERROR; - } - - /* - * Generating next keys before a key update is received. - * See quic-tls 9.4 Header Protection Timing Side-Channels. - */ - - if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { - return NGX_ERROR; - } - - /* - * 4.10.2 An endpoint MUST discard its handshake keys - * when the TLS handshake is confirmed - */ - ngx_quic_discard_ctx(c, ssl_encryption_handshake); - - if (ngx_quic_issue_server_ids(c) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) -{ - size_t window; - uint64_t last; - ngx_buf_t *b; - ngx_pool_t *pool; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; - ngx_quic_frames_stream_t *fs; - - qc = ngx_quic_get_connection(c); - f = &frame->u.stream; - - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - /* no overflow since both values are 62-bit */ - last = f->offset + f->length; - - sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->stream_id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = sn->c; - fs = &sn->fs; - b = sn->b; - window = b->end - b->last; - - if (last > window) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; - goto cleanup; - } - - if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn) - != NGX_OK) - { - goto cleanup; - } - - sc->listening->handler(sc); - - return NGX_OK; - } - - fs = &sn->fs; - b = sn->b; - window = (b->pos - b->start) + (b->end - b->last); - - if (last > fs->received && last - fs->received > window) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; - return NGX_ERROR; - } - - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn); - -cleanup: - - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - uint64_t id; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_chain_t *cl; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; - - qc = ngx_quic_get_connection(c); - sn = data; - - f = &frame->u.stream; - id = f->stream_id; - - b = sn->b; - - if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no space in stream buffer"); - return NGX_ERROR; - } - - if ((size_t) (b->end - b->last) < f->length) { - b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); - b->pos = b->start; - } - - for (cl = frame->data; cl; cl = cl->next) { - b->last = ngx_cpymem(b->last, cl->buf->pos, - cl->buf->last - cl->buf->pos); - } - - rev = sn->c->read; - rev->ready = 1; - - if (f->fin) { - rev->pending_eof = 1; - } - - if (rev->active) { - rev->handler(rev); - } - - /* check if stream was destroyed by handler */ - if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { - return NGX_DONE; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_max_data_frame(ngx_connection_t *c, - ngx_quic_max_data_frame_t *f) -{ - ngx_event_t *wev; - 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 (f->max_data <= qc->streams.send_max_data) { - return NGX_OK; - } - - if (qc->streams.sent >= qc->streams.send_max_data) { - - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - wev = qs->c->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } - } - } - - qc->streams.send_max_data = f->max_data; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) -{ - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) -{ - size_t n; - ngx_buf_t *b; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - b = sn->b; - n = b->end - b->last; - - sn->c->listening->handler(sn->c); - - } else { - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = f->id; - frame->u.max_stream_data.limit = n; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) -{ - uint64_t sent; - ngx_event_t *wev; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - if (f->limit > sn->send_max_data) { - sn->send_max_data = f->limit; - } - - sn->c->listening->handler(sn->c); - - return NGX_OK; - } - - if (f->limit <= sn->send_max_data) { - return NGX_OK; - } - - sent = sn->c->sent; - - if (sent >= sn->send_max_data) { - wev = sn->c->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } - } - - sn->send_max_data = f->limit; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) -{ - ngx_event_t *rev; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = sn->c; - - rev = sc->read; - rev->error = 1; - rev->ready = 1; - - sc->listening->handler(sc); - - return NGX_OK; - } - - rev = sn->c->read; - rev->error = 1; - rev->ready = 1; - - if (rev->active) { - rev->handler(rev); - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) -{ - ngx_event_t *wev; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = sn->c; - - wev = sc->write; - wev->error = 1; - wev->ready = 1; - - sc->listening->handler(sc); - - return NGX_OK; - } - - wev = sn->c->write; - wev->error = 1; - wev->ready = 1; - - if (wev->active) { - wev->handler(wev); - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_max_streams_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) -{ - ngx_quic_connection_t *qc; - - 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; - - 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; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_uni:%uL", f->limit); - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_PATH_RESPONSE; - frame->u.path_response = *f; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) -{ - ngx_queue_t *q; - ngx_quic_client_id_t *cid, *item; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (f->seqnum < qc->max_retired_seqnum) { - /* - * An endpoint that receives a NEW_CONNECTION_ID frame with - * a sequence number smaller than the Retire Prior To field - * of a previously received NEW_CONNECTION_ID frame MUST send - * a corresponding RETIRE_CONNECTION_ID frame that retires - * the newly received connection ID, unless it has already - * done so for that sequence number. - */ - - if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { - return NGX_ERROR; - } - - goto retire; - } - - cid = NULL; - - for (q = ngx_queue_head(&qc->client_ids); - q != ngx_queue_sentinel(&qc->client_ids); - q = ngx_queue_next(q)) - { - item = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - if (item->seqnum == f->seqnum) { - cid = item; - break; - } - } - - if (cid) { - /* - * Transmission errors, timeouts and retransmissions might cause the - * same NEW_CONNECTION_ID frame to be received multiple times - */ - - if (cid->len != f->len - || ngx_strncmp(cid->id, f->cid, f->len) != 0 - || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) - { - /* - * ..a sequence number is used for different connection IDs, - * the endpoint MAY treat that receipt as a connection error - * of type PROTOCOL_VIOLATION. - */ - qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - qc->error_reason = "seqnum refers to different connection id/token"; - return NGX_ERROR; - } - - } else { - - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NGX_ERROR; - } - - cid->seqnum = f->seqnum; - cid->len = f->len; - ngx_memcpy(cid->id, f->cid, f->len); - - ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); - - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - - /* always use latest available connection id */ - if (f->seqnum > qc->client_seqnum) { - qc->scid.len = cid->len; - qc->scid.data = cid->id; - qc->client_seqnum = f->seqnum; - } - } - -retire: - - if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { - /* - * Once a sender indicates a Retire Prior To value, smaller values sent - * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver - * MUST ignore any Retire Prior To fields that do not increase the - * largest received Retire Prior To value. - */ - goto done; - } - - qc->max_retired_seqnum = f->retire; - - q = ngx_queue_head(&qc->client_ids); - - while (q != ngx_queue_sentinel(&qc->client_ids)) { - - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - q = ngx_queue_next(q); - - if (cid->seqnum >= f->retire) { - continue; - } - - /* this connection id must be retired */ - - if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_queue_remove(&cid->queue); - ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); - qc->nclient_ids--; - } - -done: - - if (qc->nclient_ids > qc->tp.active_connection_id_limit) { - /* - * After processing a NEW_CONNECTION_ID frame and - * adding and retiring active connection IDs, if the number of active - * connection IDs exceeds the value advertised in its - * active_connection_id_limit transport parameter, an endpoint MUST - * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. - */ - qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; - qc->error_reason = "too many connection ids received"; - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = level; - frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; - frame->u.retire_cid.sequence_number = seqnum; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) -{ - ngx_queue_t *q; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); - q = ngx_queue_next(q)) - { - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - if (sid->seqnum == f->sequence_number) { - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - break; - } - } - - return ngx_quic_issue_server_ids(c); -} - - -static ngx_int_t -ngx_quic_issue_server_ids(ngx_connection_t *c) -{ - ngx_str_t dcid; - ngx_uint_t n; - ngx_quic_frame_t *frame; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - u_char id[NGX_QUIC_SERVER_CID_LEN]; - - qc = ngx_quic_get_connection(c); - - n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); - - while (qc->nserver_ids < n) { - if (ngx_quic_create_server_id(c, id) != NGX_OK) { - return NGX_ERROR; - } - - dcid.len = NGX_QUIC_SERVER_CID_LEN; - dcid.data = id; - - sid = ngx_quic_insert_server_id(c, &dcid); - if (sid == NULL) { - return NGX_ERROR; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; - frame->u.ncid.seqnum = sid->seqnum; - frame->u.ncid.retire = 0; - frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; - ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); - - if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key, - frame->u.ncid.srt) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_quic_queue_frame(qc, frame); - } - - return NGX_OK; -} - - -static void -ngx_quic_clear_temp_server_ids(ngx_connection_t *c) -{ - ngx_queue_t *q, *next; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear temp server ids"); - - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); - q = next) - { - next = ngx_queue_next(q); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - if (sid->seqnum != NGX_QUIC_UNSET_PN) { - continue; - } - - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - } -} - - -static ngx_quic_server_id_t * -ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) -{ - ngx_str_t dcid; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - sid = ngx_quic_alloc_server_id(c, qc); - if (sid == NULL) { - return NULL; - } - - sid->seqnum = qc->server_seqnum; - - if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { - qc->server_seqnum++; - } - - sid->len = id->len; - ngx_memcpy(sid->id, id->data, id->len); - - ngx_queue_insert_tail(&qc->server_ids, &sid->queue); - qc->nserver_ids++; - - dcid.data = sid->id; - dcid.len = sid->len; - - ngx_insert_udp_connection(c, &sid->udp, &dcid); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic insert server id seqnum:%uL id len:%uz %xV", - sid->seqnum, id->len, id); - - return sid; -} - - -static ngx_quic_client_id_t * -ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_queue_t *q; - ngx_quic_client_id_t *cid; - - if (!ngx_queue_empty(&qc->free_client_ids)) { - - q = ngx_queue_head(&qc->free_client_ids); - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - ngx_queue_remove(&cid->queue); - - ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); - - } else { - - cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); - if (cid == NULL) { - return NULL; - } - } - - return cid; -} - - -static ngx_quic_server_id_t * -ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_queue_t *q; - ngx_quic_server_id_t *sid; - - if (!ngx_queue_empty(&qc->free_server_ids)) { - - q = ngx_queue_head(&qc->free_server_ids); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - ngx_queue_remove(&sid->queue); - - ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); - - } else { - - sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); - if (sid == NULL) { - return NULL; - } - } - - return sid; -} - - -static void -ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) -{ - ngx_quic_send_ctx_t *ctx; - - ctx = ngx_quic_get_send_ctx(qc, frame->level); - - ngx_queue_insert_tail(&ctx->frames, &frame->queue); - - frame->len = ngx_quic_create_frame(NULL, frame); - /* always succeeds */ - - if (qc->closing) { - return; - } - - ngx_post_event(&qc->push, &ngx_posted_events); -} - - -static ngx_int_t -ngx_quic_output(ngx_connection_t *c) -{ - off_t max; - size_t len, min; - ssize_t n; - u_char *p; - ngx_uint_t i, pad; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "sending frames"; - - qc = ngx_quic_get_connection(c); - - for ( ;; ) { - p = dst; - - len = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - if (!qc->validated) { - max = qc->received * 3; - max = (c->sent >= max) ? 0 : max - c->sent; - len = ngx_min(len, (size_t) max); - } - - pad = ngx_quic_get_padding_level(c); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) - ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - - n = ngx_quic_output_packet(c, ctx, p, len, min); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - - p += n; - len -= n; - } - - len = p - dst; - if (len == 0) { - break; - } - - n = ngx_quic_send(c, dst, len); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - - if (!qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - } - } - - return NGX_OK; -} - - -static ngx_uint_t -ngx_quic_get_padding_level(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - /* - * 14.1. Initial Datagram Size - * - * Similarly, a server MUST expand the payload of all UDP datagrams - * carrying ack-eliciting Initial packets to at least the smallest - * allowed maximum datagram size of 1200 bytes - */ - - qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->need_ack) { - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - return 1; - } - } - - return NGX_QUIC_SEND_CTX_LAST; -} - - -static ngx_int_t -ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - ngx_msec_t delay; - ngx_quic_connection_t *qc; - - if (!ctx->send_ack) { - return NGX_OK; - } - - if (ctx->level == ssl_encryption_application) { - - delay = ngx_current_msec - ctx->ack_delay_start; - qc = ngx_quic_get_connection(c); - - if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP - && delay < qc->tp.max_ack_delay) - { - if (!qc->push.timer_set && !qc->closing) { - ngx_add_timer(&qc->push, - qc->tp.max_ack_delay - delay); - } - - return NGX_OK; - } - } - - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - ctx->send_ack = 0; - - return NGX_OK; -} - - -static ssize_t -ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min) -{ - size_t len, hlen, pad_len; - u_char *p; - ssize_t flen; - ngx_str_t out, res; - ngx_int_t rc; - ngx_uint_t nframes; - ngx_msec_t now; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_header_t pkt; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output %s packet max:%uz min:%uz", - ngx_quic_level_name(ctx->level), max, min); - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - hlen = (ctx->level == ssl_encryption_application) - ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; - - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - now = ngx_current_msec; - nframes = 0; - p = src; - len = 0; - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (!pkt.need_ack && f->need_ack && max > cg->window) { - max = cg->window; - } - - if (hlen + len >= max) { - break; - } - - if (hlen + len + f->len > max) { - rc = ngx_quic_split_frame(c, f, max - hlen - len); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - break; - } - } - - if (f->need_ack) { - pkt.need_ack = 1; - } - - ngx_quic_log_frame(c->log, f, 1); - - flen = ngx_quic_create_frame(p, f); - if (flen == -1) { - return NGX_ERROR; - } - - len += flen; - p += flen; - - f->pnum = ctx->pnum; - f->first = now; - f->last = now; - f->plen = 0; - - nframes++; - } - - if (nframes == 0) { - return 0; - } - - out.data = src; - out.len = len; - - pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - - if (ctx->level == ssl_encryption_initial) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - - } else if (ctx->level == ssl_encryption_handshake) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; - - } else { - if (qc->key_phase) { - pkt.flags |= NGX_QUIC_PKT_KPHASE; - } - } - - ngx_quic_set_packet_number(&pkt, ctx); - - pkt.version = qc->version; - pkt.log = c->log; - pkt.level = ctx->level; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; - - pad_len = 4; - - if (min) { - hlen = EVP_GCM_TLS_TAG_LEN - + ngx_quic_create_header(&pkt, NULL, out.len, NULL); - - if (min > hlen + pad_len) { - pad_len = min - hlen; - } - } - - if (out.len < pad_len) { - ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); - out.len = pad_len; - } - - pkt.payload = out; - - res.data = data; - - ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet tx %s bytes:%ui" - " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, - pkt.number, pkt.num_len, pkt.trunc); - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - - ctx->pnum++; - - if (pkt.need_ack) { - /* move frames into the sent queue to wait for ack */ - - if (!qc->closing) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - f->plen = res.len; - - do { - q = ngx_queue_head(&ctx->frames); - ngx_queue_remove(q); - ngx_queue_insert_tail(&ctx->sent, q); - } while (--nframes); - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); - } - - cg->in_flight += res.len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", cg->in_flight); - } - - while (nframes--) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - return res.len; -} - - -static ngx_int_t -ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) -{ - size_t shrink; - ngx_quic_frame_t *nf; - ngx_quic_ordered_frame_t *of, *onf; - - switch (f->type) { - case NGX_QUIC_FT_CRYPTO: - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - break; - - default: - return NGX_DECLINED; - } - - if ((size_t) f->len <= len) { - return NGX_OK; - } - - shrink = f->len - len; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic split frame now:%uz need:%uz shrink:%uz", - f->len, len, shrink); - - of = &f->u.ord; - - if (of->length <= shrink) { - return NGX_DECLINED; - } - - of->length -= shrink; - f->len = ngx_quic_create_frame(NULL, f); - - if ((size_t) f->len > len) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); - return NGX_ERROR; - } - - nf = ngx_quic_alloc_frame(c); - if (nf == NULL) { - return NGX_ERROR; - } - - *nf = *f; - onf = &nf->u.ord; - onf->offset += of->length; - onf->length = shrink; - nf->len = ngx_quic_create_frame(NULL, nf); - - nf->data = ngx_quic_split_bufs(c, f->data, of->length); - if (nf->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - ngx_queue_insert_after(&f->queue, &nf->queue); - - return NGX_OK; -} - - -static void -ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - - do { - q = ngx_queue_head(frames); - - if (q == ngx_queue_sentinel(frames)) { - break; - } - - ngx_queue_remove(q); - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_quic_free_frame(c, f); - } while (1); -} - - -static ssize_t -ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) -{ - ngx_buf_t b; - ngx_chain_t cl, *res; - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - b.pos = b.start = buf; - b.last = b.end = buf + len; - b.last_buf = 1; - b.temporary = 1; - - cl.buf = &b; - cl.next= NULL; - - res = c->send_chain(c, &cl, 0); - if (res == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - return len; -} - - -static void -ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) -{ - uint64_t delta; - - delta = ctx->pnum - ctx->largest_ack; - pkt->number = ctx->pnum; - - if (delta <= 0x7F) { - pkt->num_len = 1; - pkt->trunc = ctx->pnum & 0xff; - - } else if (delta <= 0x7FFF) { - pkt->num_len = 2; - pkt->flags |= 0x1; - pkt->trunc = ctx->pnum & 0xffff; - - } else if (delta <= 0x7FFFFF) { - pkt->num_len = 3; - pkt->flags |= 0x2; - pkt->trunc = ctx->pnum & 0xffffff; - - } else { - pkt->num_len = 4; - pkt->flags |= 0x3; - pkt->trunc = ctx->pnum & 0xffffffff; - } -} - - -static void -ngx_quic_pto_handler(ngx_event_t *ev) -{ - ngx_uint_t i; - ngx_queue_t *q; - ngx_connection_t *c; - ngx_quic_frame_t *start; - 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); - - qc->pto_count++; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (start->pnum <= ctx->largest_ack - && ctx->largest_ack != NGX_QUIC_UNSET_PN) - { - continue; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pto pnum:%uL pto_count:%ui level:%d", - start->pnum, qc->pto_count, start->level); - - ngx_quic_resend_frames(c, ctx); - } - - ngx_quic_connstate_dbg(c); -} - - -static void -ngx_quic_push_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); - - c = ev->data; - - if (ngx_quic_output(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - ngx_quic_connstate_dbg(c); -} - - -static -void ngx_quic_lost_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); - - c = ev->data; - - if (ngx_quic_detect_lost(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - } - - ngx_quic_connstate_dbg(c); -} - - -static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c) -{ - ngx_uint_t i; - ngx_msec_t now, wait, min_wait, thr; - ngx_queue_t *q; - ngx_quic_frame_t *start; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - now = ngx_current_msec; - - min_wait = 0; - - thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); - thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { - continue; - } - - while (!ngx_queue_empty(&ctx->sent)) { - - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (start->pnum > ctx->largest_ack) { - break; - } - - wait = start->last + thr - now; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", - start->pnum, thr, (ngx_int_t) wait, start->level); - - if ((ngx_msec_int_t) wait > 0 - && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) - { - - if (min_wait == 0 || wait < min_wait) { - min_wait = wait; - } - - break; - } - - ngx_quic_resend_frames(c, ctx); - } - } - - /* no more preceeding packets */ - - if (min_wait == 0) { - qc->pto.handler = ngx_quic_pto_handler; - return NGX_OK; - } - - qc->pto.handler = ngx_quic_lost_handler; - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - ngx_add_timer(&qc->pto, min_wait); - - return NGX_OK; -} - - -static void -ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t n; - ngx_buf_t *b; - ngx_queue_t *q; - ngx_quic_frame_t *f, *start; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic resend packet pnum:%uL", start->pnum); - - ngx_quic_congestion_lost(c, start); - - do { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->pnum != start->pnum) { - break; - } - - q = ngx_queue_next(q); - - ngx_queue_remove(&f->queue); - - switch (f->type) { - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - /* force generation of most recent acknowledgment */ - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_PING: - case NGX_QUIC_FT_PATH_RESPONSE: - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_MAX_DATA: - f->u.max_data.max_data = qc->streams.recv_max_data; - ngx_quic_queue_frame(qc, f); - break; - - 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; - ngx_quic_queue_frame(qc, f); - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - sn = ngx_quic_find_stream(&qc->streams.tree, - f->u.max_stream_data.id); - if (sn == NULL) { - ngx_quic_free_frame(c, f); - break; - } - - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); - - if (f->u.max_stream_data.limit < n) { - f->u.max_stream_data.limit = n; - } - - ngx_quic_queue_frame(qc, f); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - - if (sn && sn->c->write->error) { - /* RESET_STREAM was sent */ - ngx_quic_free_frame(c, f); - break; - } - - /* fall through */ - - default: - ngx_queue_insert_tail(&ctx->frames, &f->queue); - } - - } while (q != ngx_queue_sentinel(&ctx->sent)); - - if (qc->closing) { - return; - } - - ngx_post_event(&qc->push, &ngx_posted_events); -} - - -ngx_connection_t * -ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) -{ - size_t rcvbuf_size; - uint64_t id; - ngx_quic_stream_t *qs, *sn; - ngx_quic_connection_t *qc; - - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); - - if (bidi) { - if (qc->streams.server_streams_bidi - >= qc->streams.server_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); - return NULL; - } - - id = (qc->streams.server_streams_bidi << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_bidi, - qc->streams.server_max_streams_bidi, id); - - qc->streams.server_streams_bidi++; - rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; - - } else { - if (qc->streams.server_streams_uni - >= qc->streams.server_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); - return NULL; - } - - id = (qc->streams.server_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" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_uni, - qc->streams.server_max_streams_uni, id); - - qc->streams.server_streams_uni++; - rcvbuf_size = 0; - } - - sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); - if (sn == NULL) { - return NULL; - } - - return sn->c; -} - - -static void -ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) -{ - ngx_rbtree_node_t **p; - ngx_quic_stream_t *qn, *qnt; - - for ( ;; ) { - qn = (ngx_quic_stream_t *) node; - qnt = (ngx_quic_stream_t *) temp; - - p = (qn->id < qnt->id) ? &temp->left : &temp->right; - - if (*p == sentinel) { - break; - } - - temp = *p; - } - - *p = node; - node->parent = temp; - node->left = sentinel; - node->right = sentinel; - ngx_rbt_red(node); -} - - -static ngx_quic_stream_t * -ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) -{ - ngx_rbtree_node_t *node, *sentinel; - ngx_quic_stream_t *qn; - - node = rbtree->root; - sentinel = rbtree->sentinel; - - while (node != sentinel) { - qn = (ngx_quic_stream_t *) node; - - if (id == qn->id) { - return qn; - } - - node = (id < qn->id) ? node->left : node->right; - } - - return NULL; -} - - -static ngx_quic_stream_t * -ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) -{ - size_t n; - uint64_t min_id; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL is new", id); - - qc = ngx_quic_get_connection(c); - - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_uni) { - return NGX_QUIC_STREAM_GONE; - } - - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } - - if ((id >> 2) < qc->streams.client_streams_uni) { - return NGX_QUIC_STREAM_GONE; - } - - if ((id >> 2) >= qc->streams.client_max_streams_uni) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } - - min_id = (qc->streams.client_streams_uni << 2) - | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client_streams_uni = (id >> 2) + 1; - n = qc->tp.initial_max_stream_data_uni; - - } else { - - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_bidi) { - return NGX_QUIC_STREAM_GONE; - } - - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } - - if ((id >> 2) < qc->streams.client_streams_bidi) { - return NGX_QUIC_STREAM_GONE; - } - - if ((id >> 2) >= qc->streams.client_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; - n = qc->tp.initial_max_stream_data_bidi_remote; - } - - if (n < NGX_QUIC_STREAM_BUFSIZE) { - n = NGX_QUIC_STREAM_BUFSIZE; - } - - /* - * 2.1. Stream Types and Identifiers - * - * Within each type, streams are created with numerically increasing - * stream IDs. A stream ID that is used out of order results in all - * streams of that type with lower-numbered stream IDs also being - * opened. - */ - - for ( /* void */ ; min_id < id; min_id += 0x04) { - - sn = ngx_quic_create_stream(c, min_id, n); - if (sn == NULL) { - return NULL; - } - - sn->c->listening->handler(sn->c); - } - - return ngx_quic_create_stream(c, id, n); -} - - -static ngx_quic_stream_t * -ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) -{ - ngx_log_t *log; - ngx_pool_t *pool; - ngx_quic_stream_t *sn; - ngx_pool_cleanup_t *cln; - ngx_quic_connection_t *qc; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL create", id); - - qc = ngx_quic_get_connection(c); - - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - return NULL; - } - - sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); - if (sn == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - - sn->node.key = id; - sn->parent = c; - sn->id = id; - - sn->b = ngx_create_temp_buf(pool, rcvbuf_size); - if (sn->b == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - - ngx_queue_init(&sn->fs.frames); - - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - - *log = *c->log; - pool->log = log; - - sn->c = ngx_get_connection(-1, log); - if (sn->c == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - - sn->c->quic = sn; - sn->c->type = SOCK_STREAM; - sn->c->pool = pool; - sn->c->ssl = c->ssl; - sn->c->sockaddr = c->sockaddr; - sn->c->listening = c->listening; - sn->c->addr_text = c->addr_text; - sn->c->local_sockaddr = c->local_sockaddr; - sn->c->local_socklen = c->local_socklen; - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; - - sn->c->read->log = log; - sn->c->write->log = log; - - log->connection = sn->c->number; - - if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - sn->c->write->ready = 1; - } - - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_uni; - } - - } else { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; - } else { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; - } - } - - cln = ngx_pool_cleanup_add(pool, 0); - if (cln == NULL) { - ngx_close_connection(sn->c); - ngx_destroy_pool(pool); - return NULL; - } - - cln->handler = ngx_quic_stream_cleanup_handler; - cln->data = sn->c; - - ngx_rbtree_insert(&qc->streams.tree, &sn->node); - - return sn; -} - - -static ssize_t -ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) -{ - ssize_t len; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - b = qs->b; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - rev = c->read; - - if (rev->error) { - return NGX_ERROR; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream recv id:0x%xL eof:%d avail:%z", - qs->id, rev->pending_eof, b->last - b->pos); - - if (b->pos == b->last) { - rev->ready = 0; - - if (rev->pending_eof) { - rev->eof = 1; - return 0; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv() not ready", qs->id); - return NGX_AGAIN; - } - - len = ngx_min(b->last - b->pos, (ssize_t) size); - - ngx_memcpy(buf, b->pos, len); - - b->pos += len; - qc->streams.received += len; - - if (b->pos == b->last) { - b->pos = b->start; - b->last = b->start; - rev->ready = rev->pending_eof; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv len:%z of size:%uz", - qs->id, len, size); - - if (!rev->pending_eof) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) - + (b->end - b->last); - - ngx_quic_queue_frame(qc, frame); - } - - if ((qc->streams.recv_max_data / 2) < qc->streams.received) { - - frame = ngx_quic_alloc_frame(pc); - - if (frame == NULL) { - return NGX_ERROR; - } - - qc->streams.recv_max_data *= 2; - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_DATA; - frame->u.max_data.max_data = qc->streams.recv_max_data; - - ngx_quic_queue_frame(qc, frame); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv: increased max_data:%uL", - qs->id, qc->streams.recv_max_data); - } - - return len; -} - - -static ssize_t -ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) -{ - ngx_buf_t b; - ngx_chain_t cl; - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - b.memory = 1; - b.pos = buf; - b.last = buf + size; - - cl.buf = &b; - cl.next = NULL; - - if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - if (b.pos == buf) { - return NGX_AGAIN; - } - - return b.pos - buf; -} - - -static ngx_chain_t * -ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) -{ - size_t n, flow; - ngx_event_t *wev; - ngx_chain_t *cl; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - wev = c->write; - - if (wev->error) { - return NGX_CHAIN_ERROR; - } - - flow = ngx_quic_max_stream_flow(c); - if (flow == 0) { - wev->ready = 0; - return in; - } - - n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_CHAIN_ERROR; - } - - frame->data = ngx_quic_copy_chain(pc, in, n); - if (frame->data == NGX_CHAIN_ERROR) { - return NGX_CHAIN_ERROR; - } - - for (n = 0, cl = frame->data; cl; cl = cl->next) { - n += ngx_buf_size(cl->buf); - } - - while (in && ngx_buf_size(in->buf) == 0) { - in = in->next; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; - - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = n; - - c->sent += n; - qc->streams.sent += n; - - ngx_quic_queue_frame(qc, frame); - - wev->ready = (n < flow) ? 1 : 0; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz", n); - - return in; -} - - -static size_t -ngx_quic_max_stream_flow(ngx_connection_t *c) -{ - size_t size; - uint64_t sent, unacked; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); - - size = NGX_QUIC_STREAM_BUFSIZE; - sent = c->sent; - unacked = sent - qs->acked; - - if (qc->streams.send_max_data == 0) { - qc->streams.send_max_data = qc->ctp.initial_max_data; - } - - if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit buffer size"); - return 0; - } - - if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { - size = NGX_QUIC_STREAM_BUFSIZE - unacked; - } - - if (qc->streams.sent >= qc->streams.send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_DATA"); - return 0; - } - - if (qc->streams.sent + size > qc->streams.send_max_data) { - size = qc->streams.send_max_data - qc->streams.sent; - } - - if (sent >= qs->send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_STREAM_DATA"); - return 0; - } - - if (sent + size > qs->send_max_data) { - size = qs->send_max_data - sent; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow:%uz", size); - - return size; -} - - -static void -ngx_quic_stream_cleanup_handler(void *data) -{ - ngx_connection_t *c = data; - - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL cleanup", qs->id); - - ngx_rbtree_delete(&qc->streams.tree, &qs->node); - ngx_quic_free_frames(pc, &qs->fs.frames); - - if (qc->closing) { - /* schedule handler call to continue ngx_quic_close_connection() */ - ngx_post_event(pc->read, &ngx_posted_events); - return; - } - - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 - || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) - { - if (!c->read->pending_eof && !c->read->error) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STOP_SENDING; - frame->u.stop_sending.id = qs->id; - frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ - - ngx_quic_queue_frame(qc, frame); - } - } - - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAMS; - - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - frame->u.max_streams.limit = ++qc->streams.client_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.bidi = 1; - } - - ngx_quic_queue_frame(qc, frame); - - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - /* do not send fin for client unidirectional streams */ - return; - } - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL send fin", qs->id); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 1; - - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = 0; - - ngx_quic_queue_frame(qc, frame); - - (void) ngx_quic_output(pc); -} - - -static ngx_quic_frame_t * -ngx_quic_alloc_frame(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!ngx_queue_empty(&qc->free_frames)) { - - q = ngx_queue_head(&qc->free_frames); - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_queue_remove(&frame->queue); - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic reuse frame n:%ui", qc->nframes); -#endif - - } else { - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return NULL; - } - -#ifdef NGX_QUIC_DEBUG_ALLOC - ++qc->nframes; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic alloc frame n:%ui", qc->nframes); -#endif - } - - ngx_memzero(frame, sizeof(ngx_quic_frame_t)); - - return frame; -} - - -static void -ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - ngx_msec_t timer; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - - if (f->plen == 0) { - return; - } - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - cg->in_flight -= f->plen; - - timer = f->last - cg->recovery_start; - - if ((ngx_msec_int_t) timer <= 0) { - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion ack recovery win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - return; - } - - if (cg->window < cg->ssthresh) { - cg->window += f->plen; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion slow start win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - } else { - cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion avoidance win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - } - - /* prevent recovery_start from wrapping */ - - timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; - - if ((ngx_msec_int_t) timer < 0) { - cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; - } -} - - -static void -ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - ngx_msec_t timer; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - - if (f->plen == 0) { - return; - } - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - cg->in_flight -= f->plen; - f->plen = 0; - - timer = f->last - cg->recovery_start; - - if ((ngx_msec_int_t) timer <= 0) { - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost recovery win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - return; - } - - cg->recovery_start = ngx_current_msec; - cg->window /= 2; - - if (cg->window < qc->tp.max_udp_payload_size * 2) { - cg->window = qc->tp.max_udp_payload_size * 2; - } - - cg->ssthresh = cg->window; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); -} - - -static void -ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) -{ - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (frame->data) { - ngx_quic_free_bufs(c, frame->data); - } - - ngx_queue_insert_head(&qc->free_frames, &frame->queue); - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic free frame n:%ui", qc->nframes); -#endif -} - - -uint32_t -ngx_quic_version(ngx_connection_t *c) -{ - uint32_t version; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - version = qc->version; - - return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; -} - - -static ngx_chain_t * -ngx_quic_alloc_buf(ngx_connection_t *c) -{ - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->free_bufs) { - cl = qc->free_bufs; - qc->free_bufs = cl->next; - - b = cl->buf; - b->pos = b->start; - b->last = b->start; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic reuse buffer n:%ui", qc->nbufs); -#endif - - return cl; - } - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); - if (b == NULL) { - return NULL; - } - - b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; - - cl->buf = b; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ++qc->nbufs; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic alloc buffer n:%ui", qc->nbufs); -#endif - - return cl; -} - - -static void -ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) -{ - ngx_buf_t *b, *shadow; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - while (in) { -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic free buffer n:%ui", qc->nbufs); -#endif - - cl = in; - in = in->next; - b = cl->buf; - - if (b->shadow) { - if (!b->last_shadow) { - b->recycled = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - do { - shadow = b->shadow; - b->shadow = qc->free_shadow_bufs; - qc->free_shadow_bufs = b; - b = shadow; - } while (b->recycled); - - if (b->shadow) { - b->last_shadow = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - cl->buf = b; - } - - cl->next = qc->free_bufs; - qc->free_bufs = cl; - } -} - - -static ngx_chain_t * -ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (len) { - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - b = cl->buf; - n = ngx_min((size_t) (b->end - b->last), len); - - b->last = ngx_cpymem(b->last, data, n); - - data += n; - len -= n; - - *ll = cl; - ll = &cl->next; - } - - *ll = NULL; - - return out; -} - - -static ngx_chain_t * -ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (in) { - if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { - in = in->next; - continue; - } - - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - *ll = cl; - ll = &cl->next; - - b = cl->buf; - - while (in && b->last != b->end) { - - n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); - - if (limit > 0 && n > limit) { - n = limit; - } - - b->last = ngx_cpymem(b->last, in->buf->pos, n); - - in->buf->pos += n; - if (in->buf->pos == in->buf->last) { - in = in->next; - } - - if (limit > 0) { - if (limit == n) { - goto done; - } - - limit -= n; - } - } - - } - -done: - - *ll = NULL; - - return out; -} - - -static ngx_chain_t * -ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *out; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - while (in) { - n = ngx_buf_size(in->buf); - - if (n == len) { - out = in->next; - in->next = NULL; - return out; - } - - if (n > len) { - break; - } - - len -= n; - in = in->next; - } - - if (in == NULL) { - return NULL; - } - - /* split in->buf by creating shadow bufs which reference it */ - - if (in->buf->shadow == NULL) { - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - return NGX_CHAIN_ERROR; - } - } - - *b = *in->buf; - b->shadow = in->buf; - b->last_shadow = 1; - in->buf = b; - } - - out = ngx_alloc_chain_link(c->pool); - if (out == NULL) { - return NGX_CHAIN_ERROR; - } - - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - ngx_free_chain(c->pool, out); - return NGX_CHAIN_ERROR; - } - } - - out->buf = b; - out->next = in->next; - in->next = NULL; - - *b = *in->buf; - b->last_shadow = 0; - b->pos = b->pos + len; - - in->buf->shadow = b; - in->buf->last = in->buf->pos + len; - - return out; -} diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h deleted file mode 100644 index 59578feea..000000000 --- a/src/event/ngx_event_quic.h +++ /dev/null @@ -1,143 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ -#define _NGX_EVENT_QUIC_H_INCLUDED_ - - -#include -#include - - -/* Supported drafts: 27, 28, 29 */ -#ifndef NGX_QUIC_DRAFT_VERSION -#define NGX_QUIC_DRAFT_VERSION 29 -#endif - -#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -#define NGX_QUIC_MAX_LONG_HEADER 56 - /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ - -#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 - -#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 -#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 -#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 - -#define NGX_QUIC_RETRY_TIMEOUT 3000 -#define NGX_QUIC_RETRY_LIFETIME 30000 -#define NGX_QUIC_RETRY_BUFFER_SIZE 128 - /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */ -#define NGX_QUIC_MAX_TOKEN_SIZE 32 - /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ - -/* quic-recovery, section 6.2.2, kInitialRtt */ -#define NGX_QUIC_INITIAL_RTT 333 /* ms */ - -/* quic-recovery, section 6.1.1, Packet Threshold */ -#define NGX_QUIC_PKT_THR 3 /* packets */ -/* quic-recovery, section 6.1.2, Time Threshold */ -#define NGX_QUIC_TIME_THR 1.125 -#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ - -#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ - -#define NGX_QUIC_MIN_INITIAL_SIZE 1200 - -#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 -#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 - -#define NGX_QUIC_STREAM_BUFSIZE 65536 - -#define NGX_QUIC_MAX_CID_LEN 20 -#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN - -#define NGX_QUIC_SR_TOKEN_LEN 16 - -#define NGX_QUIC_MAX_SERVER_IDS 8 - -#define NGX_QUIC_BUFFER_SIZE 4096 - -#define ngx_quic_get_connection(c) ((ngx_quic_connection_t *)(c)->udp) - - -typedef struct { - /* configurable */ - ngx_msec_t max_idle_timeout; - ngx_msec_t max_ack_delay; - - size_t max_udp_payload_size; - size_t initial_max_data; - size_t initial_max_stream_data_bidi_local; - size_t initial_max_stream_data_bidi_remote; - size_t initial_max_stream_data_uni; - ngx_uint_t initial_max_streams_bidi; - ngx_uint_t initial_max_streams_uni; - ngx_uint_t ack_delay_exponent; - ngx_uint_t active_connection_id_limit; - ngx_flag_t disable_active_migration; - ngx_str_t original_dcid; - ngx_str_t initial_scid; - ngx_str_t retry_scid; - u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; - - /* TODO */ - void *preferred_address; -} ngx_quic_tp_t; - - -typedef struct { - ngx_ssl_t *ssl; - ngx_quic_tp_t tp; - ngx_flag_t retry; - ngx_flag_t require_alpn; - u_char token_key[32]; /* AES 256 */ - ngx_str_t sr_token_key; /* stateless reset token key */ -} ngx_quic_conf_t; - - -typedef struct { - uint64_t sent; - uint64_t received; - ngx_queue_t frames; /* reorder queue */ - size_t total; /* size of buffered data */ -} ngx_quic_frames_stream_t; - - -struct ngx_quic_stream_s { - ngx_rbtree_node_t node; - ngx_connection_t *parent; - ngx_connection_t *c; - uint64_t id; - uint64_t acked; - uint64_t send_max_data; - ngx_buf_t *b; - ngx_quic_frames_stream_t fs; -}; - - -typedef struct ngx_quic_keys_s ngx_quic_keys_t; - - -void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); -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); -uint32_t ngx_quic_version(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); - - -/********************************* DEBUG *************************************/ - -/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ -/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ -/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ -/* #define NGX_QUIC_DEBUG_CRYPTO */ - -#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c deleted file mode 100644 index 401b71121..000000000 --- a/src/event/ngx_event_quic_protection.c +++ /dev/null @@ -1,1188 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include -#include -#include - - -#define NGX_QUIC_IV_LEN 12 - -#define NGX_AES_128_GCM_SHA256 0x1301 -#define NGX_AES_256_GCM_SHA384 0x1302 -#define NGX_CHACHA20_POLY1305_SHA256 0x1303 - - -#ifdef OPENSSL_IS_BORINGSSL -#define ngx_quic_cipher_t EVP_AEAD -#else -#define ngx_quic_cipher_t EVP_CIPHER -#endif - - -typedef struct { - const ngx_quic_cipher_t *c; - const EVP_CIPHER *hp; - const EVP_MD *d; -} ngx_quic_ciphers_t; - - -typedef struct ngx_quic_secret_s { - ngx_str_t secret; - ngx_str_t key; - ngx_str_t iv; - ngx_str_t hp; -} ngx_quic_secret_t; - - -typedef struct { - ngx_quic_secret_t client; - ngx_quic_secret_t server; -} ngx_quic_secrets_t; - - -struct ngx_quic_keys_s { - ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; - ngx_quic_secrets_t next_key; - ngx_uint_t cipher; -}; - - -static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, - const EVP_MD *digest, const u_char *prk, size_t prk_len, - const u_char *info, size_t info_len); -static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, - const EVP_MD *digest, const u_char *secret, size_t secret_len, - const u_char *salt, size_t salt_len); - -static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, - uint64_t *largest_pn); -static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); -static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, - ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); - -static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad, ngx_log_t *log); -static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad, ngx_log_t *log); -static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in); -static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, - ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); - -static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, - ngx_str_t *res); -static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, - ngx_str_t *res); - - -static ngx_int_t -ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, - enum ssl_encryption_level_t level) -{ - ngx_int_t len; - - if (level == ssl_encryption_initial) { - id = NGX_AES_128_GCM_SHA256; - } - - switch (id) { - - case NGX_AES_128_GCM_SHA256: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_aes_128_gcm(); -#else - ciphers->c = EVP_aes_128_gcm(); -#endif - ciphers->hp = EVP_aes_128_ctr(); - ciphers->d = EVP_sha256(); - len = 16; - break; - - case NGX_AES_256_GCM_SHA384: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_aes_256_gcm(); -#else - ciphers->c = EVP_aes_256_gcm(); -#endif - ciphers->hp = EVP_aes_256_ctr(); - ciphers->d = EVP_sha384(); - len = 32; - break; - - case NGX_CHACHA20_POLY1305_SHA256: -#ifdef OPENSSL_IS_BORINGSSL - ciphers->c = EVP_aead_chacha20_poly1305(); -#else - ciphers->c = EVP_chacha20_poly1305(); -#endif -#ifdef OPENSSL_IS_BORINGSSL - ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); -#else - ciphers->hp = EVP_chacha20(); -#endif - ciphers->d = EVP_sha256(); - len = 32; - break; - - default: - return NGX_ERROR; - } - - return len; -} - - -ngx_int_t -ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, - ngx_str_t *secret) -{ - size_t is_len; - uint8_t is[SHA256_DIGEST_LENGTH]; - ngx_uint_t i; - const EVP_MD *digest; - const EVP_CIPHER *cipher; - ngx_quic_secret_t *client, *server; - - static const uint8_t salt[20] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) - "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" - "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; -#else - "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" - "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; -#endif - - client = &keys->secrets[ssl_encryption_initial].client; - server = &keys->secrets[ssl_encryption_initial].server; - - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - - cipher = EVP_aes_128_gcm(); - digest = EVP_sha256(); - - if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - salt, sizeof(salt)) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_str_t iss = { - .data = is, - .len = is_len - }; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic ngx_quic_set_initial_secret"); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic initial secret len:%uz %*xs", is_len, is_len, is); -#endif - - /* draft-ietf-quic-tls-23#section-5.2 */ - client->secret.len = SHA256_DIGEST_LENGTH; - server->secret.len = SHA256_DIGEST_LENGTH; - - client->key.len = EVP_CIPHER_key_length(cipher); - server->key.len = EVP_CIPHER_key_length(cipher); - - client->hp.len = EVP_CIPHER_key_length(cipher); - server->hp.len = EVP_CIPHER_key_length(cipher); - - client->iv.len = EVP_CIPHER_iv_length(cipher); - server->iv.len = EVP_CIPHER_iv_length(cipher); - - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *prk; - } seq[] = { - - /* draft-ietf-quic-tls-23#section-5.2 */ - { ngx_string("tls13 client in"), &client->secret, &iss }, - { - ngx_string("tls13 quic key"), - &client->key, - &client->secret, - }, - { - ngx_string("tls13 quic iv"), - &client->iv, - &client->secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &client->hp, - &client->secret, - }, - { ngx_string("tls13 server in"), &server->secret, &iss }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ - ngx_string("tls13 quic key"), - &server->key, - &server->secret, - }, - { - ngx_string("tls13 quic iv"), - &server->iv, - &server->secret, - }, - { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ - ngx_string("tls13 quic hp"), - &server->hp, - &server->secret, - }, - - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label, - seq[i].prk->data, seq[i].prk->len) - != NGX_OK) - { - return NGX_ERROR; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, - ngx_str_t *label, const uint8_t *prk, size_t prk_len) -{ - size_t info_len; - uint8_t *p; - uint8_t info[20]; - - if (out->data == NULL) { - out->data = ngx_pnalloc(pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } - } - - info_len = 2 + 1 + label->len + 1; - - info[0] = 0; - info[1] = out->len; - info[2] = label->len; - p = ngx_cpymem(&info[3], label->data, label->len); - *p = '\0'; - - if (ngx_hkdf_expand(out->data, out->len, digest, - prk, prk_len, info, info_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, - "ngx_hkdf_expand(%V) failed", label); - return NGX_ERROR; - } - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic expand %V key len:%uz %xV", label, out->len, out); -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, - const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) -{ -#ifdef OPENSSL_IS_BORINGSSL - if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) - == 0) - { - return NGX_ERROR; - } -#else - - EVP_PKEY_CTX *pctx; - - pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); - - if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { - return NGX_ERROR; - } - -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, - const u_char *secret, size_t secret_len, const u_char *salt, - size_t salt_len) -{ -#ifdef OPENSSL_IS_BORINGSSL - if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, - salt_len) - == 0) - { - return NGX_ERROR; - } -#else - - EVP_PKEY_CTX *pctx; - - pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); - - if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { - return NGX_ERROR; - } - - if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { - return NGX_ERROR; - } - -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, - ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, - ngx_log_t *log) -{ - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *ctx; - - ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) - != 1) - { - EVP_AEAD_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); - return NGX_ERROR; - } - - EVP_AEAD_CTX_free(ctx); -#else - int len; - u_char *tag; - EVP_CIPHER_CTX *ctx; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - return NGX_ERROR; - } - - if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); - return NGX_ERROR; - } - - if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, - in->len - EVP_GCM_TLS_TAG_LEN) - != 1) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); - return NGX_ERROR; - } - - out->len = len; - tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); - return NGX_ERROR; - } - - if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); - return NGX_ERROR; - } - - out->len += len; - - EVP_CIPHER_CTX_free(ctx); -#endif - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, - ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) -{ - -#ifdef OPENSSL_IS_BORINGSSL - EVP_AEAD_CTX *ctx; - - ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, - EVP_AEAD_DEFAULT_TAG_LENGTH); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, - in->data, in->len, ad->data, ad->len) - != 1) - { - EVP_AEAD_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); - return NGX_ERROR; - } - - EVP_AEAD_CTX_free(ctx); -#else - int len; - EVP_CIPHER_CTX *ctx; - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); - return NGX_ERROR; - } - - if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); - return NGX_ERROR; - } - - out->len = len; - - if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); - return NGX_ERROR; - } - - out->len += len; - - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, - out->data + in->len) - == 0) - { - EVP_CIPHER_CTX_free(ctx); - ngx_ssl_error(NGX_LOG_INFO, log, 0, - "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); - return NGX_ERROR; - } - - EVP_CIPHER_CTX_free(ctx); - - out->len += EVP_GCM_TLS_TAG_LEN; -#endif - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, - ngx_quic_secret_t *s, u_char *out, u_char *in) -{ - int outlen; - EVP_CIPHER_CTX *ctx; - u_char zero[5] = {0}; - -#ifdef OPENSSL_IS_BORINGSSL - uint32_t counter; - - ngx_memcpy(&counter, in, sizeof(uint32_t)); - - if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { - CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); - return NGX_OK; - } -#endif - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); - goto failed; - } - - if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); - goto failed; - } - - if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { - ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); - goto failed; - } - - EVP_CIPHER_CTX_free(ctx); - - return NGX_OK; - -failed: - - EVP_CIPHER_CTX_free(ctx); - - return NGX_ERROR; -} - - -int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, - ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, - const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) -{ - ngx_int_t key_len; - ngx_uint_t i; - ngx_quic_secret_t *peer_secret; - ngx_quic_ciphers_t ciphers; - - peer_secret = is_write ? &keys->secrets[level].server - : &keys->secrets[level].client; - - keys->cipher = SSL_CIPHER_get_protocol_id(cipher); - - key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); - - if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); - return 0; - } - - if (level == ssl_encryption_initial) { - return 0; - } - - peer_secret->secret.data = ngx_pnalloc(pool, secret_len); - if (peer_secret->secret.data == NULL) { - return NGX_ERROR; - } - - peer_secret->secret.len = secret_len; - ngx_memcpy(peer_secret->secret.data, secret, secret_len); - - peer_secret->key.len = key_len; - peer_secret->iv.len = NGX_QUIC_IV_LEN; - peer_secret->hp.len = key_len; - - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &peer_secret->key, secret }, - { ngx_string("tls13 quic iv"), &peer_secret->iv, secret }, - { ngx_string("tls13 quic hp"), &peer_secret->hp, secret }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { - return 0; - } - } - - return 1; -} - - -ngx_quic_keys_t * -ngx_quic_keys_new(ngx_pool_t *pool) -{ - return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t)); -} - - -ngx_uint_t -ngx_quic_keys_available(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level) -{ - return keys->secrets[level].client.key.len != 0; -} - - -void -ngx_quic_keys_discard(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level) -{ - keys->secrets[level].client.key.len = 0; -} - - -void -ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) -{ - ngx_quic_secrets_t *current, *next, tmp; - - current = &keys->secrets[ssl_encryption_application]; - next = &keys->next_key; - - tmp = *current; - *current = *next; - *next = tmp; -} - - -ngx_int_t -ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) -{ - ngx_uint_t i; - ngx_quic_ciphers_t ciphers; - ngx_quic_secrets_t *current, *next; - - current = &keys->secrets[ssl_encryption_application]; - next = &keys->next_key; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); - - if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) - == NGX_ERROR) - { - return NGX_ERROR; - } - - next->client.secret.len = current->client.secret.len; - next->client.key.len = current->client.key.len; - next->client.iv.len = current->client.iv.len; - next->client.hp = current->client.hp; - - next->server.secret.len = current->server.secret.len; - next->server.key.len = current->server.key.len; - next->server.iv.len = current->server.iv.len; - next->server.hp = current->server.hp; - - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *secret; - } seq[] = { - { - ngx_string("tls13 quic ku"), - &next->client.secret, - ¤t->client.secret, - }, - { - ngx_string("tls13 quic key"), - &next->client.key, - &next->client.secret, - }, - { - ngx_string("tls13 quic iv"), - &next->client.iv, - &next->client.secret, - }, - { - ngx_string("tls13 quic ku"), - &next->server.secret, - ¤t->server.secret, - }, - { - ngx_string("tls13 quic key"), - &next->server.key, - &next->server.secret, - }, - { - ngx_string("tls13 quic iv"), - &next->server.iv, - &next->server.secret, - }, - }; - - for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret->data, seq[i].secret->len) - != NGX_OK) - { - return NGX_ERROR; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) -{ - u_char *pnp, *sample; - ngx_str_t ad, out; - ngx_uint_t i; - ngx_quic_secret_t *secret; - ngx_quic_ciphers_t ciphers; - u_char nonce[12], mask[16]; - - out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; - - ad.data = res->data; - ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); - - out.data = res->data + ad.len; - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ad len:%uz %xV", ad.len, &ad); -#endif - - if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) - { - return NGX_ERROR; - } - - secret = &pkt->keys->secrets[pkt->level].server; - - ngx_memcpy(nonce, secret->iv.data, secret->iv.len); - ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); - - if (ngx_quic_tls_seal(ciphers.c, secret, &out, - nonce, &pkt->payload, &ad, pkt->log) - != NGX_OK) - { - return NGX_ERROR; - } - - sample = &out.data[4 - pkt->num_len]; - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) - != NGX_OK) - { - return NGX_ERROR; - } - - /* quic-tls: 5.4.1. Header Protection Application */ - ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); - - for (i = 0; i < pkt->num_len; i++) { - pnp[i] ^= mask[i + 1]; - } - - res->len = ad.len + out.len; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) -{ - u_char *start; - ngx_str_t ad, itag; - ngx_quic_secret_t secret; - ngx_quic_ciphers_t ciphers; - - /* 5.8. Retry Packet Integrity */ - static u_char key[16] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) - "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; -#else - "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; -#endif - static u_char nonce[12] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) - "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; -#else - "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75"; -#endif - static ngx_str_t in = ngx_string(""); - - ad.data = res->data; - ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); - - itag.data = ad.data + ad.len; - itag.len = EVP_GCM_TLS_TAG_LEN; - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic retry itag len:%uz %xV", ad.len, &ad); -#endif - - if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { - return NGX_ERROR; - } - - secret.key.len = sizeof(key); - secret.key.data = key; - secret.iv.len = sizeof(nonce); - - if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) - != NGX_OK) - { - return NGX_ERROR; - } - - res->len = itag.data + itag.len - start; - res->data = start; - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, - u_char *token) -{ - uint8_t *p; - size_t is_len, key_len, info_len; - ngx_str_t label; - const EVP_MD *digest; - uint8_t info[20]; - uint8_t is[SHA256_DIGEST_LENGTH]; - uint8_t key[SHA256_DIGEST_LENGTH]; - - /* 10.4.2. Calculating a Stateless Reset Token */ - - digest = EVP_sha256(); - ngx_str_set(&label, "sr_token_key"); - - if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - cid->data, cid->len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_extract(%V) failed", &label); - return NGX_ERROR; - } - - key_len = SHA256_DIGEST_LENGTH; - - info_len = 2 + 1 + label.len + 1; - - info[0] = 0; - info[1] = key_len; - info[2] = label.len; - - p = ngx_cpymem(&info[3], label.data, label.len); - *p = '\0'; - - if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(%V) failed", &label); - return NGX_ERROR; - } - - ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); - -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif - - return NGX_OK; -} - - -static uint64_t -ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, - uint64_t *largest_pn) -{ - u_char *p; - uint64_t truncated_pn, expected_pn, candidate_pn; - uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; - - pn_nbits = ngx_min(len * 8, 62); - - p = *pos; - truncated_pn = *p++ ^ *mask++; - - while (--len) { - truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); - } - - *pos = p; - - expected_pn = *largest_pn + 1; - pn_win = 1ULL << pn_nbits; - pn_hwin = pn_win / 2; - pn_mask = pn_win - 1; - - candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; - - if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) - && candidate_pn < (1ULL << 62) - pn_win) - { - candidate_pn += pn_win; - - } else if (candidate_pn > expected_pn + pn_hwin - && candidate_pn >= pn_win) - { - candidate_pn -= pn_win; - } - - *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); - - return candidate_pn; -} - - -static void -ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) -{ - nonce[len - 4] ^= (pn & 0xff000000) >> 24; - nonce[len - 3] ^= (pn & 0x00ff0000) >> 16; - nonce[len - 2] ^= (pn & 0x0000ff00) >> 8; - nonce[len - 1] ^= (pn & 0x000000ff); -} - - -ngx_int_t -ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) -{ - if (ngx_quic_pkt_retry(pkt->flags)) { - return ngx_quic_create_retry_packet(pkt, res); - } - - return ngx_quic_create_packet(pkt, res); -} - - -ngx_int_t -ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) -{ - u_char *p, *sample; - size_t len; - uint64_t pn, lpn; - ngx_int_t pnl, rc, key_phase; - ngx_str_t in, ad; - ngx_quic_secret_t *secret; - ngx_quic_ciphers_t ciphers; - uint8_t mask[16], nonce[12]; - - if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) - { - return NGX_ERROR; - } - - secret = &pkt->keys->secrets[pkt->level].client; - - p = pkt->raw->pos; - len = pkt->data + pkt->len - p; - - /* draft-ietf-quic-tls-23#section-5.4.2: - * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.[34]: - * AES-Based and ChaCha20-Based header protections sample 16 bytes - */ - - if (len < EVP_GCM_TLS_TAG_LEN + 4) { - return NGX_DECLINED; - } - - sample = p + 4; - - /* header protection */ - - if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) - != NGX_OK) - { - return NGX_DECLINED; - } - - pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); - - if (ngx_quic_short_pkt(pkt->flags)) { - key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; - - if (key_phase != pkt->key_phase) { - secret = &pkt->keys->next_key.client; - pkt->key_update = 1; - } - } - - lpn = *largest_pn; - - pnl = (pkt->flags & 0x03) + 1; - pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); - - pkt->pn = pn; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx clearflags:%xd", pkt->flags); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx number:%uL len:%xi", pn, pnl); - - /* packet protection */ - - in.data = p; - in.len = len - pnl; - - ad.len = p - pkt->data; - ad.data = pkt->plaintext; - - ngx_memcpy(ad.data, pkt->data, ad.len); - ad.data[0] = pkt->flags; - - do { - ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; - } while (--pnl); - - ngx_memcpy(nonce, secret->iv.data, secret->iv.len); - ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ad len:%uz %xV", ad.len, &ad); -#endif - - pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; - pkt->payload.data = pkt->plaintext + ad.len; - - rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, - nonce, &in, &ad, pkt->log); - if (rc != NGX_OK) { - return NGX_DECLINED; - } - - if (pkt->payload.len == 0) { - /* - * An endpoint MUST treat receipt of a packet containing no - * frames as a connection error of type PROTOCOL_VIOLATION. - */ - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); - pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - return NGX_ERROR; - } - - if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { - /* - * An endpoint MUST treat receipt of a packet that has - * a non-zero value for these bits, after removing both - * packet and header protection, as a connection error - * of type PROTOCOL_VIOLATION. - */ - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic reserved bit set in packet"); - pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - return NGX_ERROR; - } - -#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet payload len:%uz %xV", - pkt->payload.len, &pkt->payload); -#endif - - *largest_pn = lpn; - - return NGX_OK; -} - diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h deleted file mode 100644 index 4e39ea57a..000000000 --- a/src/event/ngx_event_quic_protection.h +++ /dev/null @@ -1,38 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ -#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ - - -#include -#include - - -#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) - - -ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); -ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, - ngx_quic_keys_t *keys, ngx_str_t *secret); -int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, - ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, - const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); -ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level); -void ngx_quic_keys_discard(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level); -void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); -ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); - -ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - ngx_str_t *key, u_char *token); - -ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); -ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); - - -#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/ngx_event_quic_transport.c b/src/event/ngx_event_quic_transport.c deleted file mode 100644 index 45c60c255..000000000 --- a/src/event/ngx_event_quic_transport.c +++ /dev/null @@ -1,1983 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include -#include - - -#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 -#define NGX_QUIC_LONG_DCID_OFFSET 6 -#define NGX_QUIC_SHORT_DCID_OFFSET 1 - - -#if (NGX_HAVE_NONALIGNED) - -#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) -#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) - -#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned -#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned - -#else - -#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) -#define ngx_quic_parse_uint32(p) \ - ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) - -#define ngx_quic_write_uint16(p, s) \ - ((p)[0] = (u_char) ((s) >> 8), \ - (p)[1] = (u_char) (s), \ - (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32(p, s) \ - ((p)[0] = (u_char) ((s) >> 24), \ - (p)[1] = (u_char) ((s) >> 16), \ - (p)[2] = (u_char) ((s) >> 8), \ - (p)[3] = (u_char) (s), \ - (p) + sizeof(uint32_t)) - -#endif - -#define ngx_quic_write_uint24(p, s) \ - ((p)[0] = (u_char) ((s) >> 16), \ - (p)[1] = (u_char) ((s) >> 8), \ - (p)[2] = (u_char) (s), \ - (p) + 3) - -#define ngx_quic_write_uint16_aligned(p, s) \ - (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) - -#define ngx_quic_write_uint32_aligned(p, s) \ - (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) - -#define NGX_QUIC_VERSION(c) (0xff000000 + (c)) - - -static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); -static ngx_uint_t ngx_quic_varint_len(uint64_t value); -static void ngx_quic_build_int(u_char **pos, uint64_t value); - -static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); -static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); -static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, - u_char **out); -static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, - u_char *dst); - -static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, - size_t dcid_len); -static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_supported_version(uint32_t version); -static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); - -static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); -static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); - -static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, - ngx_uint_t frame_type); -static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, - ngx_chain_t *ranges); -static size_t ngx_quic_create_stop_sending(u_char *p, - ngx_quic_stop_sending_frame_t *ss); -static size_t ngx_quic_create_crypto(u_char *p, - ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); -static size_t ngx_quic_create_hs_done(u_char *p); -static size_t ngx_quic_create_new_token(u_char *p, - ngx_quic_new_token_frame_t *token); -static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, - ngx_chain_t *data); -static size_t ngx_quic_create_max_streams(u_char *p, - ngx_quic_max_streams_frame_t *ms); -static size_t ngx_quic_create_max_stream_data(u_char *p, - ngx_quic_max_stream_data_frame_t *ms); -static size_t ngx_quic_create_max_data(u_char *p, - ngx_quic_max_data_frame_t *md); -static size_t ngx_quic_create_path_response(u_char *p, - ngx_quic_path_challenge_frame_t *pc); -static size_t ngx_quic_create_new_connection_id(u_char *p, - ngx_quic_new_conn_id_frame_t *rcid); -static size_t ngx_quic_create_retire_connection_id(u_char *p, - ngx_quic_retire_cid_frame_t *rcid); -static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); - -static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, - uint16_t id, ngx_quic_tp_t *dst); - - -uint32_t ngx_quic_versions[] = { -#if (NGX_QUIC_DRAFT_VERSION >= 29) - /* pretend we support all versions in range draft-29..v1 */ - NGX_QUIC_VERSION(29), - NGX_QUIC_VERSION(30), - NGX_QUIC_VERSION(31), - NGX_QUIC_VERSION(32), - /* QUICv1 */ - 0x00000001 -#else - NGX_QUIC_VERSION(NGX_QUIC_DRAFT_VERSION) -#endif -}; - -#define NGX_QUIC_NVERSIONS \ - (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) - - -/* literal errors indexed by corresponding value */ -static char *ngx_quic_errors[] = { - "NO_ERROR", - "INTERNAL_ERROR", - "CONNECTION_REFUSED", - "FLOW_CONTROL_ERROR", - "STREAM_LIMIT_ERROR", - "STREAM_STATE_ERROR", - "FINAL_SIZE_ERROR", - "FRAME_ENCODING_ERROR", - "TRANSPORT_PARAMETER_ERROR", - "CONNECTION_ID_LIMIT_ERROR", - "PROTOCOL_VIOLATION", - "INVALID_TOKEN", - "APPLICATION_ERROR", - "CRYPTO_BUFFER_EXCEEDED", - "KEY_UPDATE_ERROR", -}; - - -static ngx_inline u_char * -ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) -{ - u_char *p; - uint64_t value; - ngx_uint_t len; - - if (pos >= end) { - return NULL; - } - - p = pos; - len = 1 << (*p >> 6); - - value = *p++ & 0x3f; - - if ((size_t)(end - p) < (len - 1)) { - return NULL; - } - - while (--len) { - value = (value << 8) + *p++; - } - - *out = value; - - return p; -} - - -static ngx_inline u_char * -ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) -{ - if ((size_t)(end - pos) < 1) { - return NULL; - } - - *value = *pos; - - return pos + 1; -} - - -static ngx_inline u_char * -ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) -{ - if ((size_t)(end - pos) < sizeof(uint32_t)) { - return NULL; - } - - *value = ngx_quic_parse_uint32(pos); - - return pos + sizeof(uint32_t); -} - - -static ngx_inline u_char * -ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) -{ - if ((size_t)(end - pos) < len) { - return NULL; - } - - *out = pos; - - return pos + len; -} - - -static u_char * -ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) -{ - if ((size_t)(end - pos) < len) { - return NULL; - } - - ngx_memcpy(dst, pos, len); - - return pos + len; -} - - -static ngx_uint_t -ngx_quic_varint_len(uint64_t value) -{ - ngx_uint_t bits; - - bits = 0; - while (value >> ((8 << bits) - 2)) { - bits++; - } - - return 1 << bits; -} - - -static void -ngx_quic_build_int(u_char **pos, uint64_t value) -{ - u_char *p; - ngx_uint_t bits, len; - - p = *pos; - bits = 0; - - while (value >> ((8 << bits) - 2)) { - bits++; - } - - len = (1 << bits); - - while (len--) { - *p++ = value >> (len * 8); - } - - **pos |= bits << 6; - *pos = p; -} - - -u_char * -ngx_quic_error_text(uint64_t error_code) -{ - if (error_code >= NGX_QUIC_ERR_CRYPTO_ERROR) { - return (u_char *) "handshake error"; - } - - if (error_code >= NGX_QUIC_ERR_LAST) { - return (u_char *) "unknown error"; - } - - return (u_char *) ngx_quic_errors[error_code]; -} - - -ngx_int_t -ngx_quic_parse_packet(ngx_quic_header_t *pkt) -{ - if (!ngx_quic_long_pkt(pkt->flags)) { - pkt->level = ssl_encryption_application; - - if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) - { - return NGX_DECLINED; - } - - return NGX_OK; - } - - if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; - } - - if (!ngx_quic_supported_version(pkt->version)) { - return NGX_ABORT; - } - - if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { - return NGX_DECLINED; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) -{ - u_char *p, *end; - - p = pkt->raw->pos; - end = pkt->data + pkt->len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx short flags:%xd", pkt->flags); - - if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); - return NGX_ERROR; - } - - pkt->dcid.len = dcid_len; - - p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read dcid"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_parse_long_header(ngx_quic_header_t *pkt) -{ - u_char *p, *end; - uint8_t idlen; - - p = pkt->raw->pos; - end = pkt->data + pkt->len; - - p = ngx_quic_read_uint32(p, end, &pkt->version); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read version"); - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx long flags:%xd version:%xD", - pkt->flags, pkt->version); - - if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); - return NGX_ERROR; - } - - p = ngx_quic_read_uint8(p, end, &idlen); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read dcid len"); - return NGX_ERROR; - } - - if (idlen > NGX_QUIC_CID_LEN_MAX) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet dcid is too long"); - return NGX_ERROR; - } - - pkt->dcid.len = idlen; - - p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read dcid"); - return NGX_ERROR; - } - - p = ngx_quic_read_uint8(p, end, &idlen); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read scid len"); - return NGX_ERROR; - } - - if (idlen > NGX_QUIC_CID_LEN_MAX) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet scid is too long"); - return NGX_ERROR; - } - - pkt->scid.len = idlen; - - p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet is too small to read scid"); - return NGX_ERROR; - } - - pkt->raw->pos = p; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_supported_version(uint32_t version) -{ - ngx_uint_t i; - - for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { - if (ngx_quic_versions[i] == version) { - return 1; - } - } - - return 0; -} - - -static ngx_int_t -ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) -{ - u_char *p, *end; - uint64_t varint; - - p = pkt->raw->pos; - end = pkt->raw->last; - - pkt->log->action = "parsing quic long header"; - - if (ngx_quic_pkt_in(pkt->flags)) { - - if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic UDP datagram is too small for initial packet"); - return NGX_DECLINED; - } - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse token length"); - return NGX_ERROR; - } - - pkt->token.len = varint; - - p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic packet too small to read token data"); - return NGX_ERROR; - } - - pkt->level = ssl_encryption_initial; - - } else if (ngx_quic_pkt_zrtt(pkt->flags)) { - pkt->level = ssl_encryption_early_data; - - } else if (ngx_quic_pkt_hs(pkt->flags)) { - pkt->level = ssl_encryption_handshake; - - } else { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic bad packet type"); - return NGX_DECLINED; - } - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic packet rx %s len:%uL", - ngx_quic_level_name(pkt->level), varint); - - if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", - ngx_quic_level_name(pkt->level)); - return NGX_ERROR; - } - - pkt->raw->pos = p; - pkt->len = p + varint - pkt->data; - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, - ngx_str_t *dcid) -{ - size_t len, offset; - - if (n == 0) { - goto failed; - } - - if (ngx_quic_long_pkt(*data)) { - if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { - goto failed; - } - - len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; - offset = NGX_QUIC_LONG_DCID_OFFSET; - - } else { - len = NGX_QUIC_SERVER_CID_LEN; - offset = NGX_QUIC_SHORT_DCID_OFFSET; - } - - if (n < len + offset) { - goto failed; - } - - dcid->len = len; - dcid->data = &data[offset]; - - return NGX_OK; - -failed: - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); - - return NGX_ERROR; -} - - -size_t -ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) -{ - u_char *p, *start; - ngx_uint_t i; - - p = start = out; - - *p++ = pkt->flags; - - /* - * The Version field of a Version Negotiation packet - * MUST be set to 0x00000000 - */ - p = ngx_quic_write_uint32(p, 0); - - *p++ = pkt->dcid.len; - p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); - - *p++ = pkt->scid.len; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); - - for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { - p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); - } - - return p - start; -} - - -size_t -ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, - u_char **pnp) -{ - return ngx_quic_short_pkt(pkt->flags) - ? ngx_quic_create_short_header(pkt, out, pkt_len, pnp) - : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); -} - - -static size_t -ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp) -{ - u_char *p, *start; - - if (out == NULL) { - return 5 + 2 + pkt->dcid.len + pkt->scid.len - + ngx_quic_varint_len(pkt_len + pkt->num_len) + pkt->num_len - + (pkt->level == ssl_encryption_initial ? 1 : 0); - } - - p = start = out; - - *p++ = pkt->flags; - - p = ngx_quic_write_uint32(p, pkt->version); - - *p++ = pkt->dcid.len; - p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); - - *p++ = pkt->scid.len; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); - - if (pkt->level == ssl_encryption_initial) { - ngx_quic_build_int(&p, 0); - } - - ngx_quic_build_int(&p, pkt_len + pkt->num_len); - - *pnp = p; - - switch (pkt->num_len) { - case 1: - *p++ = pkt->trunc; - break; - case 2: - p = ngx_quic_write_uint16(p, pkt->trunc); - break; - case 3: - p = ngx_quic_write_uint24(p, pkt->trunc); - break; - case 4: - p = ngx_quic_write_uint32(p, pkt->trunc); - break; - } - - return p - start; -} - - -static size_t -ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp) -{ - u_char *p, *start; - - if (out == NULL) { - return 1 + pkt->dcid.len + pkt->num_len; - } - - p = start = out; - - *p++ = pkt->flags; - - p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); - - *pnp = p; - - switch (pkt->num_len) { - case 1: - *p++ = pkt->trunc; - break; - case 2: - p = ngx_quic_write_uint16(p, pkt->trunc); - break; - case 3: - p = ngx_quic_write_uint24(p, pkt->trunc); - break; - case 4: - p = ngx_quic_write_uint32(p, pkt->trunc); - break; - } - - return p - start; -} - - -size_t -ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, - u_char **start) -{ - u_char *p; - - p = out; - - *p++ = pkt->odcid.len; - p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); - - *start = p; - - *p++ = 0xff; - - p = ngx_quic_write_uint32(p, pkt->version); - - *p++ = pkt->dcid.len; - p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); - - *p++ = pkt->scid.len; - p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); - - p = ngx_cpymem(p, pkt->token.data, pkt->token.len); - - return p - out; -} - - -#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) -#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) -#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) - -ssize_t -ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, - ngx_quic_frame_t *f) -{ - u_char *p; - uint64_t varint; - ngx_buf_t *b; - ngx_uint_t i; - - b = f->data->buf; - - p = start; - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to obtain quic frame type"); - return NGX_ERROR; - } - - f->type = varint; - - if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { - pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - return NGX_ERROR; - } - - switch (f->type) { - - case NGX_QUIC_FT_CRYPTO: - - p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); - if (p == NULL) { - goto error; - } - - p = ngx_quic_parse_int(p, end, &f->u.crypto.length); - if (p == NULL) { - goto error; - } - - p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); - if (p == NULL) { - goto error; - } - - b->last = p; - - break; - - case NGX_QUIC_FT_PADDING: - - while (p < end && *p == NGX_QUIC_FT_PADDING) { - p++; - } - - break; - - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - - if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) - { - goto error; - } - - b->pos = p; - - /* process all ranges to get bounds, values are ignored */ - for (i = 0; i < f->u.ack.range_count; i++) { - - p = ngx_quic_parse_int(p, end, &varint); - if (p) { - p = ngx_quic_parse_int(p, end, &varint); - } - - if (p == NULL) { - goto error; - } - } - - b->last = p; - - f->u.ack.ranges_length = b->last - b->pos; - - if (f->type == NGX_QUIC_FT_ACK_ECN) { - - if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) - { - goto error; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, - "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", - f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); - } - - break; - - case NGX_QUIC_FT_PING: - break; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - - p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); - if (p == NULL) { - goto error; - } - - p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); - if (p == NULL) { - goto error; - } - - if (f->u.ncid.retire > f->u.ncid.seqnum) { - goto error; - } - - p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); - if (p == NULL) { - goto error; - } - - if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { - goto error; - } - - p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); - if (p == NULL) { - goto error; - } - - p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - - p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE_APP: - - p = ngx_quic_parse_int(p, end, &f->u.close.error_code); - if (p == NULL) { - goto error; - } - - if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); - if (p == NULL) { - goto error; - } - } - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - goto error; - } - - f->u.close.reason.len = varint; - - p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, - &f->u.close.reason.data); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - f->u.stream.type = f->type; - - f->u.stream.off = ngx_quic_stream_bit_off(f->type); - f->u.stream.len = ngx_quic_stream_bit_len(f->type); - f->u.stream.fin = ngx_quic_stream_bit_fin(f->type); - - p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); - if (p == NULL) { - goto error; - } - - if (f->type & 0x04) { - p = ngx_quic_parse_int(p, end, &f->u.stream.offset); - if (p == NULL) { - goto error; - } - - } else { - f->u.stream.offset = 0; - } - - if (f->type & 0x02) { - p = ngx_quic_parse_int(p, end, &f->u.stream.length); - if (p == NULL) { - goto error; - } - - } else { - f->u.stream.length = end - p; /* up to packet end */ - } - - p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); - if (p == NULL) { - goto error; - } - - b->last = p; - break; - - case NGX_QUIC_FT_MAX_DATA: - - p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_RESET_STREAM: - - if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) - && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) - && (p = ngx_quic_parse_int(p, end, - &f->u.reset_stream.final_size)))) - { - goto error; - } - - break; - - case NGX_QUIC_FT_STOP_SENDING: - - p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); - if (p == NULL) { - goto error; - } - - p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_STREAMS_BLOCKED: - case NGX_QUIC_FT_STREAMS_BLOCKED2: - - p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); - if (p == NULL) { - goto error; - } - - f->u.streams_blocked.bidi = - (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; - break; - - case NGX_QUIC_FT_MAX_STREAMS: - case NGX_QUIC_FT_MAX_STREAMS2: - - p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); - if (p == NULL) { - goto error; - } - - f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; - - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - - p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); - if (p == NULL) { - goto error; - } - - p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_DATA_BLOCKED: - - p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_STREAM_DATA_BLOCKED: - - p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); - if (p == NULL) { - goto error; - } - - p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_PATH_CHALLENGE: - - p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); - if (p == NULL) { - goto error; - } - - break; - - case NGX_QUIC_FT_PATH_RESPONSE: - - p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); - if (p == NULL) { - goto error; - } - - break; - - default: - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic unknown frame type 0x%xi", f->type); - return NGX_ERROR; - } - - f->level = pkt->level; - - return p - start; - -error: - - pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to parse frame type:0x%xi", f->type); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) -{ - uint8_t ptype; - - /* frame permissions per packet: 4 bits: IH01: 12.4, Table 3 */ - 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 */ - /* STREAM0 */ 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 */ 0x3, -#if (NGX_QUIC_DRAFT_VERSION >= 28) - /* CONNECTION_CLOSE */ 0xF, - /* CONNECTION_CLOSE2 */ 0x3, -#else - /* CONNECTION_CLOSE */ 0xD, - /* CONNECTION_CLOSE2 */ 0x1, -#endif - /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ - }; - - if (ngx_quic_long_pkt(pkt->flags)) { - - if (ngx_quic_pkt_in(pkt->flags)) { - ptype = 8; /* initial */ - - } else if (ngx_quic_pkt_hs(pkt->flags)) { - ptype = 4; /* handshake */ - - } else { - ptype = 2; /* zero-rtt */ - } - - } else { - ptype = 1; /* application data */ - } - - if (ptype & ngx_quic_frame_masks[frame_type]) { - return NGX_OK; - } - - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic frame type 0x%xi is not " - "allowed in packet with flags 0x%xd", - frame_type, pkt->flags); - - return NGX_DECLINED; -} - - -ssize_t -ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, - uint64_t *gap, uint64_t *range) -{ - u_char *p; - - p = start; - - p = ngx_quic_parse_int(p, end, gap); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse ack frame gap"); - return NGX_ERROR; - } - - p = ngx_quic_parse_int(p, end, range); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse ack frame range"); - return NGX_ERROR; - } - - return p - start; -} - - -size_t -ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(gap); - len += ngx_quic_varint_len(range); - return len; - } - - start = p; - - ngx_quic_build_int(&p, gap); - ngx_quic_build_int(&p, range); - - return p - start; -} - - -ssize_t -ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) -{ - /* - * QUIC-recovery, section 2: - * - * Ack-eliciting Frames: All frames other than ACK, PADDING, and - * CONNECTION_CLOSE are considered ack-eliciting. - */ - f->need_ack = 1; - - switch (f->type) { - case NGX_QUIC_FT_ACK: - f->need_ack = 0; - return ngx_quic_create_ack(p, &f->u.ack, f->data); - - case NGX_QUIC_FT_STOP_SENDING: - return ngx_quic_create_stop_sending(p, &f->u.stop_sending); - - case NGX_QUIC_FT_CRYPTO: - return ngx_quic_create_crypto(p, &f->u.crypto, f->data); - - case NGX_QUIC_FT_HANDSHAKE_DONE: - return ngx_quic_create_hs_done(p); - - case NGX_QUIC_FT_NEW_TOKEN: - return ngx_quic_create_new_token(p, &f->u.token); - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - return ngx_quic_create_stream(p, &f->u.stream, f->data); - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE_APP: - f->need_ack = 0; - return ngx_quic_create_close(p, &f->u.close); - - case NGX_QUIC_FT_MAX_STREAMS: - return ngx_quic_create_max_streams(p, &f->u.max_streams); - - case NGX_QUIC_FT_MAX_STREAM_DATA: - return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); - - case NGX_QUIC_FT_MAX_DATA: - return ngx_quic_create_max_data(p, &f->u.max_data); - - case NGX_QUIC_FT_PATH_RESPONSE: - return ngx_quic_create_path_response(p, &f->u.path_response); - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - return ngx_quic_create_new_connection_id(p, &f->u.ncid); - - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); - - default: - /* BUG: unsupported frame type generated */ - return NGX_ERROR; - } -} - - -static size_t -ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) -{ - size_t len; - u_char *start; - ngx_buf_t *b; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); - len += ngx_quic_varint_len(ack->largest); - len += ngx_quic_varint_len(ack->delay); - len += ngx_quic_varint_len(ack->range_count); - len += ngx_quic_varint_len(ack->first_range); - len += ack->ranges_length; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); - ngx_quic_build_int(&p, ack->largest); - ngx_quic_build_int(&p, ack->delay); - ngx_quic_build_int(&p, ack->range_count); - ngx_quic_build_int(&p, ack->first_range); - - while (ranges) { - b = ranges->buf; - p = ngx_cpymem(p, b->pos, b->last - b->pos); - ranges = ranges->next; - } - - return p - start; -} - - -static size_t -ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); - len += ngx_quic_varint_len(ss->id); - len += ngx_quic_varint_len(ss->error_code); - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); - ngx_quic_build_int(&p, ss->id); - ngx_quic_build_int(&p, ss->error_code); - - return p - start; -} - - -static size_t -ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, - ngx_chain_t *data) -{ - size_t len; - u_char *start; - ngx_buf_t *b; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); - len += ngx_quic_varint_len(crypto->offset); - len += ngx_quic_varint_len(crypto->length); - len += crypto->length; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); - ngx_quic_build_int(&p, crypto->offset); - ngx_quic_build_int(&p, crypto->length); - - while (data) { - b = data->buf; - p = ngx_cpymem(p, b->pos, b->last - b->pos); - data = data->next; - } - - return p - start; -} - - -static size_t -ngx_quic_create_hs_done(u_char *p) -{ - u_char *start; - - if (p == NULL) { - return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); - - return p - start; -} - - -static size_t -ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); - len += ngx_quic_varint_len(token->length); - len += token->length; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); - ngx_quic_build_int(&p, token->length); - p = ngx_cpymem(p, token->data, token->length); - - return p - start; -} - - -static size_t -ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, - ngx_chain_t *data) -{ - size_t len; - u_char *start; - ngx_buf_t *b; - - if (p == NULL) { - len = ngx_quic_varint_len(sf->type); - - if (sf->off) { - len += ngx_quic_varint_len(sf->offset); - } - - len += ngx_quic_varint_len(sf->stream_id); - - /* length is always present in generated frames */ - len += ngx_quic_varint_len(sf->length); - - len += sf->length; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, sf->type); - ngx_quic_build_int(&p, sf->stream_id); - - if (sf->off) { - ngx_quic_build_int(&p, sf->offset); - } - - /* length is always present in generated frames */ - ngx_quic_build_int(&p, sf->length); - - while (data) { - b = data->buf; - p = ngx_cpymem(p, b->pos, b->last - b->pos); - data = data->next; - } - - return p - start; -} - - -static size_t -ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) -{ - size_t len; - u_char *start; - ngx_uint_t type; - - type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; - - if (p == NULL) { - len = ngx_quic_varint_len(type); - len += ngx_quic_varint_len(ms->limit); - return len; - } - - start = p; - - ngx_quic_build_int(&p, type); - ngx_quic_build_int(&p, ms->limit); - - return p - start; -} - - -static ngx_int_t -ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, - ngx_quic_tp_t *dst) -{ - uint64_t varint; - ngx_str_t str; - - varint = 0; - ngx_str_null(&str); - - switch (id) { - - case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: - /* zero-length option */ - if (end - p != 0) { - return NGX_ERROR; - } - dst->disable_active_migration = 1; - return NGX_OK; - - case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: - case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: - case NGX_QUIC_TP_INITIAL_MAX_DATA: - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: - case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: - case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: - case NGX_QUIC_TP_ACK_DELAY_EXPONENT: - case NGX_QUIC_TP_MAX_ACK_DELAY: - case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: - - p = ngx_quic_parse_int(p, end, &varint); - if (p == NULL) { - return NGX_ERROR; - } - break; - - case NGX_QUIC_TP_INITIAL_SCID: - - str.len = end - p; - str.data = p; - break; - - default: - return NGX_DECLINED; - } - - switch (id) { - - case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: - dst->max_idle_timeout = varint; - break; - - case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: - dst->max_udp_payload_size = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_DATA: - dst->initial_max_data = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: - dst->initial_max_stream_data_bidi_local = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: - dst->initial_max_stream_data_bidi_remote = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: - dst->initial_max_stream_data_uni = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: - dst->initial_max_streams_bidi = varint; - break; - - case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: - dst->initial_max_streams_uni = varint; - break; - - case NGX_QUIC_TP_ACK_DELAY_EXPONENT: - dst->ack_delay_exponent = varint; - break; - - case NGX_QUIC_TP_MAX_ACK_DELAY: - dst->max_ack_delay = varint; - break; - - case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: - dst->active_connection_id_limit = varint; - break; - - case NGX_QUIC_TP_INITIAL_SCID: - dst->initial_scid = str; - break; - - default: - return NGX_ERROR; - } - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, - ngx_log_t *log) -{ - uint64_t id, len; - ngx_int_t rc; - - while (p < end) { - p = ngx_quic_parse_int(p, end, &id); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse transport param id"); - 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; - } - - p = ngx_quic_parse_int(p, end, &len); - if (p == NULL) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse" - " transport param id:0x%xL length", id); - return NGX_ERROR; - } - - rc = ngx_quic_parse_transport_param(p, p + len, id, tp); - - if (rc == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse" - " transport param id:0x%xL data", id); - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic unknown transport param id:0x%xL, skipped", id); - } - - p += len; - } - - if (p != end) { - ngx_log_error(NGX_LOG_INFO, log, 0, - "quic trailing garbage in" - " transport parameters: bytes:%ui", - end - p); - return NGX_ERROR; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, - "quic transport parameters parsed ok"); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp disable active migration: %ui", - tp->disable_active_migration); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", - tp->max_idle_timeout); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_udp_payload_size:%ui", - tp->max_udp_payload_size); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", - tp->initial_max_data); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_bidi_local:%ui", - tp->initial_max_stream_data_bidi_local); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_bidi_remote:%ui", - tp->initial_max_stream_data_bidi_remote); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp max_stream_data_uni:%ui", - tp->initial_max_stream_data_uni); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp initial_max_streams_bidi:%ui", - tp->initial_max_streams_bidi); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp initial_max_streams_uni:%ui", - tp->initial_max_streams_uni); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp ack_delay_exponent:%ui", - tp->ack_delay_exponent); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", - tp->max_ack_delay); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp active_connection_id_limit:%ui", - tp->active_connection_id_limit); - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, - "quic tp initial source_connection_id len:%uz %xV", - tp->initial_scid.len, &tp->initial_scid); -#endif - - return NGX_OK; -} - - -static size_t -ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); - len += ngx_quic_varint_len(ms->id); - len += ngx_quic_varint_len(ms->limit); - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); - ngx_quic_build_int(&p, ms->id); - ngx_quic_build_int(&p, ms->limit); - - return p - start; -} - - -static size_t -ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); - len += ngx_quic_varint_len(md->max_data); - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); - ngx_quic_build_int(&p, md->max_data); - - return p - start; -} - - -static size_t -ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); - len += sizeof(pc->data); - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); - p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); - - return p - start; -} - - -static size_t -ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); - len += ngx_quic_varint_len(ncid->seqnum); - len += ngx_quic_varint_len(ncid->retire); - len++; - len += ncid->len; - len += NGX_QUIC_SR_TOKEN_LEN; - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); - ngx_quic_build_int(&p, ncid->seqnum); - ngx_quic_build_int(&p, ncid->retire); - *p++ = ncid->len; - p = ngx_cpymem(p, ncid->cid, ncid->len); - p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); - - return p - start; -} - - -static size_t -ngx_quic_create_retire_connection_id(u_char *p, - ngx_quic_retire_cid_frame_t *rcid) -{ - size_t len; - u_char *start; - - if (p == NULL) { - len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); - len += ngx_quic_varint_len(rcid->sequence_number); - return len; - } - - start = p; - - ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); - ngx_quic_build_int(&p, rcid->sequence_number); - - return p - start; -} - - -ssize_t -ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, - size_t *clen) -{ - u_char *p; - size_t len; - -#define ngx_quic_tp_len(id, value) \ - ngx_quic_varint_len(id) \ - + ngx_quic_varint_len(value) \ - + ngx_quic_varint_len(ngx_quic_varint_len(value)) - -#define ngx_quic_tp_vint(id, value) \ - do { \ - ngx_quic_build_int(&p, id); \ - ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ - ngx_quic_build_int(&p, value); \ - } while (0) - -#define ngx_quic_tp_strlen(id, value) \ - ngx_quic_varint_len(id) \ - + ngx_quic_varint_len(value.len) \ - + value.len - -#define ngx_quic_tp_str(id, value) \ - do { \ - ngx_quic_build_int(&p, id); \ - ngx_quic_build_int(&p, value.len); \ - p = ngx_cpymem(p, value.data, value.len); \ - } while (0) - - p = pos; - - len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, - tp->initial_max_streams_uni); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, - tp->initial_max_streams_bidi); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, - tp->initial_max_stream_data_bidi_local); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, - tp->initial_max_stream_data_bidi_remote); - - len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, - tp->initial_max_stream_data_uni); - - len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, - tp->max_idle_timeout); - - if (clen) { - *clen = len; - } - - if (tp->disable_active_migration) { - len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); - len += ngx_quic_varint_len(0); - } - - len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, - tp->active_connection_id_limit); - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - 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); - } -#else - if (tp->original_dcid.len) { - len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); - } -#endif - - 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; - } - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, - tp->initial_max_data); - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, - tp->initial_max_streams_uni); - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, - tp->initial_max_streams_bidi); - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, - tp->initial_max_stream_data_bidi_local); - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, - tp->initial_max_stream_data_bidi_remote); - - ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, - tp->initial_max_stream_data_uni); - - ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, - tp->max_idle_timeout); - - if (tp->disable_active_migration) { - ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); - ngx_quic_build_int(&p, 0); - } - - ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, - tp->active_connection_id_limit); - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - 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); - } -#else - if (tp->original_dcid.len) { - ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); - } -#endif - - 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; -} - - -static size_t -ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) -{ - size_t len; - u_char *start; - ngx_uint_t type; - - type = cl->app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP - : NGX_QUIC_FT_CONNECTION_CLOSE; - - if (p == NULL) { - len = ngx_quic_varint_len(type); - len += ngx_quic_varint_len(cl->error_code); - - if (!cl->app) { - len += ngx_quic_varint_len(cl->frame_type); - } - - len += ngx_quic_varint_len(cl->reason.len); - len += cl->reason.len; - - return len; - } - - start = p; - - ngx_quic_build_int(&p, type); - ngx_quic_build_int(&p, cl->error_code); - - if (!cl->app) { - ngx_quic_build_int(&p, cl->frame_type); - } - - ngx_quic_build_int(&p, cl->reason.len); - p = ngx_cpymem(p, cl->reason.data, cl->reason.len); - - return p - start; -} diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h deleted file mode 100644 index aa1c888e0..000000000 --- a/src/event/ngx_event_quic_transport.h +++ /dev/null @@ -1,356 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ -#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ - - -#include -#include - - -/* QUIC flags in first byte, see quic-transport 17.2 and 17.3 */ - -#define NGX_QUIC_PKT_LONG 0x80 /* header form */ -#define NGX_QUIC_PKT_FIXED_BIT 0x40 -#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ -#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ - -#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) -#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) - -/* Long packet types */ -#define NGX_QUIC_PKT_INITIAL 0x00 -#define NGX_QUIC_PKT_ZRTT 0x10 -#define NGX_QUIC_PKT_HANDSHAKE 0x20 -#define NGX_QUIC_PKT_RETRY 0x30 - -#define ngx_quic_pkt_in(flags) \ - (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) -#define ngx_quic_pkt_zrtt(flags) \ - (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) -#define ngx_quic_pkt_hs(flags) \ - (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) -#define ngx_quic_pkt_retry(flags) \ - (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) - -#define ngx_quic_pkt_rb_mask(flags) \ - (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) -#define ngx_quic_pkt_hp_mask(flags) \ - (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) - -#define ngx_quic_level_name(lvl) \ - (lvl == ssl_encryption_application) ? "app" \ - : (lvl == ssl_encryption_initial) ? "init" \ - : (lvl == ssl_encryption_handshake) ? "hs" : "early" - - -/* 12.4. Frames and Frame Types */ -#define NGX_QUIC_FT_PADDING 0x00 -#define NGX_QUIC_FT_PING 0x01 -#define NGX_QUIC_FT_ACK 0x02 -#define NGX_QUIC_FT_ACK_ECN 0x03 -#define NGX_QUIC_FT_RESET_STREAM 0x04 -#define NGX_QUIC_FT_STOP_SENDING 0x05 -#define NGX_QUIC_FT_CRYPTO 0x06 -#define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM0 0x08 -#define NGX_QUIC_FT_STREAM1 0x09 -#define NGX_QUIC_FT_STREAM2 0x0A -#define NGX_QUIC_FT_STREAM3 0x0B -#define NGX_QUIC_FT_STREAM4 0x0C -#define NGX_QUIC_FT_STREAM5 0x0D -#define NGX_QUIC_FT_STREAM6 0x0E -#define NGX_QUIC_FT_STREAM7 0x0F -#define NGX_QUIC_FT_MAX_DATA 0x10 -#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 -#define NGX_QUIC_FT_MAX_STREAMS 0x12 -#define NGX_QUIC_FT_MAX_STREAMS2 0x13 -#define NGX_QUIC_FT_DATA_BLOCKED 0x14 -#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 -#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 -#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 -#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 -#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 -#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A -#define NGX_QUIC_FT_PATH_RESPONSE 0x1B -#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C -#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D -#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E - -/* 22.4. QUIC Transport Error Codes Registry */ -/* Keep in sync with ngx_quic_errors[] */ -#define NGX_QUIC_ERR_NO_ERROR 0x00 -#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 -#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 -#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 -#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 -#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 -#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 -#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 -#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 -#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 -#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A -#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B -#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C -#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D -#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E - -#define NGX_QUIC_ERR_LAST 0x0F -#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 - -#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) - - -/* Transport parameters */ -#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 -#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 -#define NGX_QUIC_TP_SR_TOKEN 0x02 -#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 -#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 -#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 -#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 -#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 -#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 -#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 -#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A -#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B -#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C -#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D -#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E -#define NGX_QUIC_TP_INITIAL_SCID 0x0F -#define NGX_QUIC_TP_RETRY_SCID 0x10 - -#define NGX_QUIC_CID_LEN_MIN 8 -#define NGX_QUIC_CID_LEN_MAX 20 - -#define NGX_QUIC_MAX_RANGES 10 - - -typedef struct { - uint64_t gap; - uint64_t range; -} ngx_quic_ack_range_t; - - -typedef struct { - uint64_t largest; - uint64_t delay; - uint64_t range_count; - uint64_t first_range; - uint64_t ect0; - uint64_t ect1; - uint64_t ce; - uint64_t ranges_length; -} ngx_quic_ack_frame_t; - - -typedef struct { - uint64_t seqnum; - uint64_t retire; - uint8_t len; - u_char cid[NGX_QUIC_CID_LEN_MAX]; - u_char srt[NGX_QUIC_SR_TOKEN_LEN]; -} ngx_quic_new_conn_id_frame_t; - - -typedef struct { - uint64_t length; - u_char *data; -} ngx_quic_new_token_frame_t; - -/* - * common layout for CRYPTO and STREAM frames; - * conceptually, CRYPTO frame is also a stream - * frame lacking some properties - */ -typedef struct { - uint64_t offset; - uint64_t length; -} ngx_quic_ordered_frame_t; - -typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; - - -typedef struct { - /* initial fields same as in ngx_quic_ordered_frame_t */ - uint64_t offset; - uint64_t length; - - uint8_t type; - uint64_t stream_id; - unsigned off:1; - unsigned len:1; - unsigned fin:1; -} ngx_quic_stream_frame_t; - - -typedef struct { - uint64_t max_data; -} ngx_quic_max_data_frame_t; - - -typedef struct { - uint64_t error_code; - uint64_t frame_type; - ngx_str_t reason; - ngx_uint_t app; /* unsigned app:1; */ -} ngx_quic_close_frame_t; - - -typedef struct { - uint64_t id; - uint64_t error_code; - uint64_t final_size; -} ngx_quic_reset_stream_frame_t; - - -typedef struct { - uint64_t id; - uint64_t error_code; -} ngx_quic_stop_sending_frame_t; - - -typedef struct { - uint64_t limit; - ngx_uint_t bidi; /* unsigned: bidi:1 */ -} ngx_quic_streams_blocked_frame_t; - - -typedef struct { - uint64_t limit; - ngx_uint_t bidi; /* unsigned: bidi:1 */ -} ngx_quic_max_streams_frame_t; - - -typedef struct { - uint64_t id; - uint64_t limit; -} ngx_quic_max_stream_data_frame_t; - - -typedef struct { - uint64_t limit; -} ngx_quic_data_blocked_frame_t; - - -typedef struct { - uint64_t id; - uint64_t limit; -} ngx_quic_stream_data_blocked_frame_t; - - -typedef struct { - uint64_t sequence_number; -} ngx_quic_retire_cid_frame_t; - - -typedef struct { - u_char data[8]; -} ngx_quic_path_challenge_frame_t; - - -typedef struct ngx_quic_frame_s ngx_quic_frame_t; - -struct ngx_quic_frame_s { - ngx_uint_t type; - enum ssl_encryption_level_t level; - ngx_queue_t queue; - uint64_t pnum; - size_t plen; - ngx_msec_t first; - ngx_msec_t last; - ssize_t len; - ngx_uint_t need_ack; - /* unsigned need_ack:1; */ - - ngx_chain_t *data; - union { - ngx_quic_ack_frame_t ack; - ngx_quic_crypto_frame_t crypto; - ngx_quic_ordered_frame_t ord; - ngx_quic_new_conn_id_frame_t ncid; - ngx_quic_new_token_frame_t token; - ngx_quic_stream_frame_t stream; - ngx_quic_max_data_frame_t max_data; - ngx_quic_close_frame_t close; - ngx_quic_reset_stream_frame_t reset_stream; - ngx_quic_stop_sending_frame_t stop_sending; - ngx_quic_streams_blocked_frame_t streams_blocked; - ngx_quic_max_streams_frame_t max_streams; - ngx_quic_max_stream_data_frame_t max_stream_data; - ngx_quic_data_blocked_frame_t data_blocked; - ngx_quic_stream_data_blocked_frame_t stream_data_blocked; - ngx_quic_retire_cid_frame_t retire_cid; - ngx_quic_path_challenge_frame_t path_challenge; - ngx_quic_path_challenge_frame_t path_response; - } u; -}; - - -typedef struct { - ngx_log_t *log; - - ngx_quic_keys_t *keys; - - ngx_msec_t received; - uint64_t number; - uint8_t num_len; - uint32_t trunc; - uint8_t flags; - uint32_t version; - ngx_str_t token; - enum ssl_encryption_level_t level; - ngx_uint_t error; - - /* filled in by parser */ - ngx_buf_t *raw; /* udp datagram */ - - u_char *data; /* quic packet */ - size_t len; - - /* cleartext fields */ - ngx_str_t odcid; /* retry packet tag */ - ngx_str_t dcid; - ngx_str_t scid; - uint64_t pn; - u_char *plaintext; - ngx_str_t payload; /* decrypted data */ - - unsigned need_ack:1; - unsigned key_phase:1; - unsigned key_update:1; - unsigned parsed:1; - unsigned decrypted:1; -} ngx_quic_header_t; - - -u_char *ngx_quic_error_text(uint64_t error_code); - -ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); - -size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); - -size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); - -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); -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, - u_char *end, uint64_t *gap, uint64_t *range); -size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); - -ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, - ngx_quic_tp_t *tp, 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); - -#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c new file mode 100644 index 000000000..73cde84d5 --- /dev/null +++ b/src/event/quic/ngx_event_quic.c @@ -0,0 +1,6530 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + +/* + * 7.4. Cryptographic Message Buffering + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + +#define NGX_QUIC_STREAM_GONE (void *) -1 + +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + +/* + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + +#define NGX_QUIC_MAX_ACK_GAP 2 + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + + uint64_t received; + uint64_t sent; + uint64_t recv_max_data; + 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; +} ngx_quic_streams_t; + + +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + +/* + * 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets which + * are also Initial packets. +*/ +typedef struct { + enum ssl_encryption_level_t level; + + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ + + ngx_queue_t frames; + ngx_queue_t sent; + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + ngx_uint_t send_ack; +} ngx_quic_send_ctx_t; + + +typedef struct { + ngx_udp_connection_t udp; + + uint32_t version; + ngx_str_t scid; /* initial client ID */ + ngx_str_t dcid; /* server (our own) ID */ + ngx_str_t odcid; /* original server ID */ + ngx_str_t token; + + struct sockaddr *sockaddr; + socklen_t socklen; + + ngx_queue_t client_ids; + ngx_queue_t server_ids; + ngx_queue_t free_client_ids; + ngx_queue_t free_server_ids; + ngx_uint_t nclient_ids; + ngx_uint_t nserver_ids; + uint64_t max_retired_seqnum; + uint64_t client_seqnum; + uint64_t server_seqnum; + + ngx_uint_t client_tp_done; + ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; + + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; + + ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; + + ngx_quic_keys_t *keys; + + ngx_quic_conf_t *conf; + + ngx_event_t push; + ngx_event_t pto; + ngx_event_t close; + ngx_msec_t last_cc; + + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + + ngx_uint_t pto_count; + + ngx_queue_t free_frames; + ngx_chain_t *free_bufs; + ngx_buf_t *free_shadow_bufs; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_uint_t nframes; + ngx_uint_t nbufs; +#endif + + ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; + off_t received; + + ngx_uint_t error; + enum ssl_encryption_level_t error_level; + ngx_uint_t error_ftype; + const char *error_reason; + + unsigned error_app:1; + unsigned send_timer_set:1; + unsigned closing:1; + unsigned draining:1; + unsigned key_phase:1; + unsigned in_retry:1; + unsigned initialized:1; + unsigned validated:1; +} ngx_quic_connection_t; + + +typedef struct { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_client_id_t; + + +typedef struct { + ngx_udp_connection_t udp; + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +} ngx_quic_server_id_t; + + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +#if BORINGSSL_API_VERSION >= 10 +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +#endif + +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); + + +static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, + ngx_quic_header_t *inpkt); +static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); +static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); +static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); +static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); +static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); +static void ngx_quic_input_handler(ngx_event_t *rev); + +static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); +static void ngx_quic_close_timer_handler(ngx_event_t *ev); +static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, + ngx_quic_connection_t *qc); + +static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); +static void ngx_quic_discard_ctx(ngx_connection_t *c, + enum ssl_encryption_level_t level); +static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); +static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); +static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); + +static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_msec_t *send_time); +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_inline ngx_msec_t ngx_quic_pto(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +static void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); + +static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, + ngx_quic_frame_handler_pt handler, void *data); +static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, + ngx_quic_frame_t *f, uint64_t offset_in); +static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); + +static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); +static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + +static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); +static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +static 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); +static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); +static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum); +static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); +static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); +static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); +static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, + ngx_str_t *id); +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); +static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); + +static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, + ngx_quic_frame_t *frame); + +static ngx_int_t ngx_quic_output(ngx_connection_t *c); +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); +static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); + +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_send_ctx_t *ctx); +static void ngx_quic_pto_handler(ngx_event_t *ev); +static void ngx_quic_lost_handler(ngx_event_t *ev); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); +static void ngx_quic_resend_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +static void ngx_quic_push_handler(ngx_event_t *ev); + +static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + uint64_t id); +static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, + uint64_t id); +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, + uint64_t id, size_t rcvbuf_size); +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 ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); +static void ngx_quic_stream_cleanup_handler(void *data); +static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); + +static void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); + +static ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); +static ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, + size_t len); +static ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, + size_t limit); +static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, + size_t len); + + +static SSL_QUIC_METHOD quic_method = { +#if BORINGSSL_API_VERSION >= 10 + ngx_quic_set_read_secret, + ngx_quic_set_write_secret, +#else + ngx_quic_set_encryption_secrets, +#endif + ngx_quic_add_handshake_data, + ngx_quic_flush_flight, + ngx_quic_send_alert, +}; + + +#if (NGX_DEBUG) + +static void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range, largest, smallest; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", + f->u.ack.range_count, f->u.ack.delay); + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->end; + + } else { + pos = NULL; + end = NULL; + } + + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->u.close.app ? "_APP" : "", f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), + p - buf, buf); +} + + +static void +ngx_quic_connstate_dbg(ngx_connection_t *c) +{ + u_char *p, *last; + ngx_quic_connection_t *qc; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = p + sizeof(buf); + + qc = ngx_quic_get_connection(c); + + p = ngx_slprintf(p, last, "state:"); + + if (qc) { + + if (qc->error) { + p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); + p = ngx_slprintf(p, last, " error:%ui", qc->error); + + if (qc->error_reason) { + p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); + } + } + + 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" : ""); + p = ngx_slprintf(p, last, "%s", qc->in_retry ? " retry" : ""); + p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); + + } else { + p = ngx_slprintf(p, last, " early"); + } + + if (c->read->timer_set) { + p = ngx_slprintf(p, last, + qc && qc->send_timer_set ? " send:%M" : " read:%M", + c->read->timer.key - ngx_current_msec); + } + + if (qc) { + + if (qc->push.timer_set) { + p = ngx_slprintf(p, last, " push:%M", + qc->push.timer.key - ngx_current_msec); + } + + if (qc->pto.timer_set) { + p = ngx_slprintf(p, last, " pto:%M", + qc->pto.timer.key - ngx_current_msec); + } + + if (qc->close.timer_set) { + p = ngx_slprintf(p, last, " close:%M", + qc->close.timer.key - ngx_current_msec); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %*s", p - buf, buf); +} + +#else + +#define ngx_quic_log_frame(log, f, tx) +#define ngx_quic_connstate_dbg(c) + +#endif + + +#if BORINGSSL_API_VERSION >= 10 + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + cipher, rsecret, secret_len); +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len); +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + cipher = SSL_get_current_cipher(ssl_conn); + + if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + cipher, rsecret, secret_len) + != 1) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + return 1; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len); +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *end; + size_t client_params_len; + const uint8_t *client_params; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + ngx_quic_frames_stream_t *fs; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + 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 defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + if (qc->conf->require_alpn) { + unsigned int len; + const unsigned char *data; + + SSL_get0_alpn_selected(ssl_conn, &data, &len); + + if (len == 0) { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + } +#endif + + 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); + + if (client_params_len == 0) { + /* quic-tls 8.2 */ + 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"); + return 0; + } + + p = (u_char *) client_params; + end = p + client_params_len; + + if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + + return 0; + } + + if (qc->ctp.max_idle_timeout > 0 + && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; + } + + if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return 0; + } + + if (qc->ctp.max_udp_payload_size > ngx_quic_max_udp_payload(c)) { + qc->ctp.max_udp_payload_size = ngx_quic_max_udp_payload(c); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client maximum packet size truncated"); + } + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + if (qc->scid.len != qc->ctp.initial_scid.len + || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, + qc->scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id " + "mismatch"); + return 0; + } +#endif + + qc->streams.server_max_streams_bidi = qc->ctp.initial_max_streams_bidi; + qc->streams.server_max_streams_uni = qc->ctp.initial_max_streams_uni; + + qc->client_tp_done = 1; + } + + fs = &qc->crypto[level]; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } + + frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); + if (frame->data == NGX_CHAIN_ERROR) { + return 0; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = fs->sent; + frame->u.crypto.length = len; + + fs->sent += len; + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ +#if (NGX_DEBUG) + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); +#endif + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() lvl:%d alert:%d", + (int) level, (int) alert); + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error_level = level; + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "TLS alert"; + qc->error_app = 0; + qc->error_ftype = 0; + + if (ngx_quic_send_cc(c) != NGX_OK) { + return 0; + } + + return 1; +} + + +void +ngx_quic_run(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"); + + rc = ngx_quic_input(c, c->buffer, conf); + if (rc != NGX_OK) { + ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); + return; + } + + qc = ngx_quic_get_connection(c); + + ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT + : qc->tp.max_idle_timeout); + + c->read->handler = ngx_quic_input_handler; + + ngx_quic_connstate_dbg(c); + return; +} + + +static ngx_quic_connection_t * +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_uint_t i; + ngx_quic_tp_t *ctp; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NULL; + } + + qc->keys = ngx_quic_keys_new(c->pool); + if (qc->keys == NULL) { + return NULL; + } + + qc->version = pkt->version; + + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sent); + qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; + } + + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_queue_init(&qc->crypto[i].frames); + } + + ngx_queue_init(&qc->free_frames); + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->server_ids); + ngx_queue_init(&qc->free_client_ids); + ngx_queue_init(&qc->free_server_ids); + + qc->avg_rtt = NGX_QUIC_INITIAL_RTT; + qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; + qc->min_rtt = NGX_TIMER_INFINITE; + + /* + * qc->latest_rtt = 0 + * qc->nclient_ids = 0 + * qc->nserver_ids = 0 + * qc->max_retired_seqnum = 0 + */ + + qc->received = pkt->raw->last - pkt->raw->start; + + qc->pto.log = c->log; + qc->pto.data = c; + qc->pto.handler = ngx_quic_pto_handler; + qc->pto.cancelable = 1; + + qc->push.log = c->log; + qc->push.data = c; + qc->push.handler = ngx_quic_push_handler; + qc->push.cancelable = 1; + + qc->conf = conf; + qc->tp = conf->tp; + + if (qc->tp.disable_active_migration) { + qc->sockaddr = ngx_palloc(c->pool, c->socklen); + if (qc->sockaddr == NULL) { + return NULL; + } + + ngx_memcpy(qc->sockaddr, c->sockaddr, c->socklen); + qc->socklen = c->socklen; + } + + ctp = &qc->ctp; + ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); + ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + + qc->streams.recv_max_data = qc->tp.initial_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->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + qc->odcid.len = pkt->dcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->odcid.data == NULL) { + return NULL; + } + + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NULL; + } + + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + return NULL; + } + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + qc->tp.original_dcid = qc->odcid; +#endif + qc->tp.initial_scid = qc->dcid; + + qc->scid.len = pkt->scid.len; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + return NULL; + } + ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NULL; + } + + cid->seqnum = 0; + cid->len = pkt->scid.len; + ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + qc->client_seqnum = 0; + + qc->server_seqnum = NGX_QUIC_UNSET_PN; + + return qc; +} + + +static ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) ngx_quic_send(c, buf, len); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *tail, ch; + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* A stateless reset uses an entire UDP datagram */ + if (pkt->raw->start != pkt->data) { + return NGX_DECLINED; + } + + tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (cid->seqnum == 0) { + /* no stateless reset token in initial connection id */ + continue; + } + + /* constant time comparison */ + + for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { + ch |= tail[i] ^ cid->sr_token[i]; + } + + if (ch == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); +#endif + + (void) ngx_quic_send(c, buf, len); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +{ + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create server id %*xs", + (size_t) NGX_QUIC_SERVER_CID_LEN, id); + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_retry(ngx_connection_t *c) +{ + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + + qc = ngx_quic_get_connection(c); + + if (ngx_quic_new_token(c, &token) != NGX_OK) { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = qc->version; + pkt.log = c->log; + pkt.odcid = qc->odcid; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); +#endif + + len = ngx_quic_send(c, res.data, res.len); + if (len == NGX_ERROR) { + return NGX_ERROR; + } + + qc->token = token; +#if (NGX_QUIC_DRAFT_VERSION < 28) + qc->tp.original_dcid = qc->odcid; +#endif + qc->tp.retry_scid = qc->dcid; + qc->in_retry = 1; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) +{ + int len, iv_len; + u_char *data, *p, *key, *iv; + ngx_msec_t now; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + ngx_quic_connection_t *qc; + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + + len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now)); + data = c->addr_text.data; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + + p = ngx_cpymem(in, data, len); + + now = ngx_current_msec; + len += sizeof(now); + ngx_memcpy(p, &now, sizeof(now)); + + cipher = EVP_aes_256_cbc(); + iv_len = EVP_CIPHER_iv_length(cipher); + + token->len = iv_len + len + EVP_CIPHER_block_size(cipher); + token->data = ngx_pnalloc(c->pool, token->len); + if (token->data == NULL) { + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + qc = ngx_quic_get_connection(c); + key = qc->conf->token_key; + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *key, *iv, *p, *data; + ngx_msec_t msec; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + ngx_quic_connection_t *qc; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + + qc = ngx_quic_get_connection(c); + + /* Retry token */ + + if (qc->token.len) { + if (pkt->token.len != qc->token.len) { + goto bad_token; + } + + if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { + goto bad_token; + } + + return NGX_OK; + } + + /* NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_cbc(); + key = qc->conf->token_key; + iv = pkt->token.data; + iv_len = EVP_CIPHER_iv_length(cipher); + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + goto bad_token; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { + goto bad_token; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len; + + if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto bad_token; + } + + if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto bad_token; + } + + EVP_CIPHER_CTX_free(ctx); + + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + +#if (NGX_HAVE_UNIX_DOMAIN) + case AF_UNIX: + + len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec)); + data = c->addr_text.data; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + + if (ngx_memcmp(tdec, data, len) != 0) { + goto bad_token; + } + + ngx_memcpy(&msec, tdec + len, sizeof(msec)); + + if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + return NGX_OK; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + qc->error = NGX_QUIC_ERR_INVALID_TOKEN; + qc->error_reason = "invalid_token"; + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + u_char *p; + size_t clen; + ssize_t len; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + return NGX_ERROR; + } + + c->ssl->no_wait_shutdown = 1; + + ssl_conn = c->ssl->connection; + + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_method() failed"); + return NGX_ERROR; + } + +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + + if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, + qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); + + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + if (len < 0) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); +#endif + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + +#if NGX_OPENSSL_QUIC_ZRTT_CTX + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} + + +static ngx_inline size_t +ngx_quic_max_udp_payload(ngx_connection_t *c) +{ + /* TODO: path MTU discovery */ + +#if (NGX_HAVE_INET6) + if (c->sockaddr->sa_family == AF_INET6) { + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; + } +#endif + + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; +} + + +static void +ngx_quic_input_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_int_t rc; + ngx_buf_t b; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + + ngx_memzero(&b, sizeof(ngx_buf_t)); + b.start = buf; + b.end = buf + sizeof(buf); + b.pos = b.last = b.start; + b.memory = 1; + + c = rev->data; + qc = ngx_quic_get_connection(c); + + 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) { + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_OK); + return; + } + + n = c->recv(c, b.start, b.end - b.start); + + if (n == NGX_AGAIN) { + if (qc->closing) { + ngx_quic_close_connection(c, NGX_OK); + } + return; + } + + if (n == NGX_ERROR) { + c->read->eof = 1; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (qc->tp.disable_active_migration) { + if (c->socklen != qc->socklen + || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0) + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic dropping packet from new address"); + return; + } + } + + b.last += n; + qc->received += n; + + rc = ngx_quic_input(c, &b, NULL); + + if (rc == NGX_ERROR) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (rc == NGX_DECLINED) { + return; + } + + /* rc == NGX_OK */ + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); +} + + +static void +ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) +{ + ngx_pool_t *pool; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_close_connection rc:%i", rc); + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close connection early error"); + + } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { + return; + } + + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#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_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_send_ctx_t *ctx; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->closing) { + + /* drop packets from retransmit queues, no ack is expected */ + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); + } + + if (rc == NGX_DONE) { + + /* + * 10.2. Idle Timeout + * + * If the idle timeout is enabled by either peer, a connection is + * silently closed and its state is discarded when it remains idle + */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic closing %s connection", + qc->draining ? "drained" : "idle"); + + } else { + + /* + * 10.3. Immediate Close + * + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) + * to terminate the connection immediately. + */ + + qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) + : ssl_encryption_initial; + + if (rc == NGX_OK) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close drain:%d", + qc->draining); + + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_timer_handler; + qc->close.cancelable = 1; + + ctx = ngx_quic_get_send_ctx(qc, qc->error_level); + + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + + qc->error = NGX_QUIC_ERR_NO_ERROR; + + } else { + if (qc->error == 0 && !qc->error_app) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close due to %s error: %ui %s", + qc->error_app ? "app " : "", qc->error, + qc->error_reason ? qc->error_reason : ""); + } + + (void) ngx_quic_send_cc(c); + + if (qc->error_level == ssl_encryption_handshake) { + /* for clients that might not have handshake keys */ + qc->error_level = ssl_encryption_initial; + (void) ngx_quic_send_cc(c); + } + } + + qc->closing = 1; + } + + if (rc == NGX_ERROR && qc->close.timer_set) { + /* do not wait for timer in case of fatal error */ + ngx_del_timer(&qc->close); + } + + if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); + } + + while (!ngx_queue_empty(&qc->server_ids)) { + q = ngx_queue_head(&qc->server_ids); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + ngx_queue_remove(q); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + } + + if (qc->close.timer_set) { + return NGX_AGAIN; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic part of connection is terminated"); + + /* may be tested from SSL callback during SSL shutdown */ + c->udp = NULL; + + return NGX_OK; +} + + +void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + qc->error_ftype = 0; + + ngx_quic_close_connection(c, NGX_ERROR); +} + + +static void +ngx_quic_close_timer_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close timer"); + + c = ev->data; + ngx_quic_close_connection(c, NGX_DONE); +} + + +static ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_event_t *rev, *wev; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + +#if (NGX_DEBUG) + ngx_uint_t ns; +#endif + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + +#if (NGX_DEBUG) + ns = 0; +#endif + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + rev = qs->c->read; + rev->error = 1; + rev->ready = 1; + + wev = qs->c->write; + wev->error = 1; + wev->ready = 1; + + ngx_post_event(rev, &ngx_posted_events); + + if (rev->timer_set) { + ngx_del_timer(rev); + } + +#if (NGX_DEBUG) + ns++; +#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has %ui active streams", ns); + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) +{ + u_char *p; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + + good = 0; + + p = b->pos; + + while (p < b->last) { + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + pkt.log = c->log; + pkt.flags = p[0]; + pkt.raw->pos++; + + qc = ngx_quic_get_connection(c); + if (qc) { + qc->error = 0; + qc->error_reason = 0; + } + + rc = ngx_quic_process_packet(c, conf, &pkt); + +#if (NGX_DEBUG) + if (pkt.parsed) { + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet %s done decr:%d pn:%L perr:%ui rc:%i", + ngx_quic_level_name(pkt.level), pkt.decrypted, + pkt.pn, pkt.error, rc); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done parse failed rc:%i", rc); + } +#endif + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + good = 1; + } + + /* NGX_OK || NGX_DECLINED */ + + /* + * we get NGX_DECLINED when there are no keys [yet] available + * to decrypt packet. + * Instead of queueing it, we ignore it and rely on the sender's + * retransmission: + * + * 12.2. Coalescing Packets: + * + * For example, if decryption fails (because the keys are + * not available or any other reason), the receiver MAY either + * discard or buffer the packet for later processing and MUST + * attempt to process the remaining packets. + * + * We also skip packets that don't match connection state + * or cannot be parsed properly. + */ + + /* b->pos is at header end, adjust by actual packet length */ + b->pos = pkt.data + pkt.len; + + /* firefox workaround: skip zero padding at the end of quic packet */ + while (b->pos < b->last && *(b->pos) == 0) { + b->pos++; + } + + p = b->pos; + } + + return good ? NGX_OK : NGX_DECLINED; +} + + +static ngx_int_t +ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + c->log->action = "parsing quic packet"; + + rc = ngx_quic_parse_packet(pkt); + + if (rc == NGX_DECLINED || rc == NGX_ERROR) { + return rc; + } + + pkt->parsed = 1; + + c->log->action = "processing quic packet"; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx dcid len:%uz %xV", + pkt->dcid.len, &pkt->dcid); + +#if (NGX_DEBUG) + if (pkt->level != ssl_encryption_application) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx scid len:%uz %xV", + pkt->scid.len, &pkt->scid); + } + + if (pkt->level == ssl_encryption_initial) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic token len:%uz %xV", + pkt->token.len, &pkt->token); + } +#endif + + if (qc) { + + if (rc == NGX_ABORT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->level != ssl_encryption_application) { + if (pkt->version != qc->version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic version mismatch: 0x%xD", pkt->version); + return NGX_DECLINED; + } + } + + if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { + + if (pkt->level == ssl_encryption_application) { + if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); + + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + + return NGX_OK; + } + + return ngx_quic_send_stateless_reset(c, qc->conf, pkt); + } + + return NGX_DECLINED; + } + + if (qc->in_retry) { + + c->log->action = "retrying quic connection"; + + if (pkt->level != ssl_encryption_initial) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic discard late retry packet"); + return NGX_DECLINED; + } + + if (!pkt->token.len) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic discard retry packet without token"); + return NGX_DECLINED; + } + + qc->odcid.len = pkt->dcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->odcid.data == NULL) { + return NGX_ERROR; + } + + ngx_quic_clear_temp_server_ids(c); + + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + return NGX_ERROR; + } + + qc->server_seqnum = 0; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + return NGX_ERROR; + } + + qc->tp.initial_scid = qc->dcid; + qc->in_retry = 0; + + if (ngx_quic_init_secrets(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_validate_token(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + qc->validated = 1; + } + + } else { + + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } + + if (pkt->level == ssl_encryption_initial) { + + c->log->action = "creating quic connection"; + + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: len:%i", pkt->dcid.len); + return NGX_ERROR; + } + + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; + } + + c->udp = &qc->udp; + + if (ngx_terminate || ngx_exiting) { + qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; + return NGX_ERROR; + } + + if (pkt->token.len) { + rc = ngx_quic_validate_token(c, pkt); + + if (rc == NGX_OK) { + qc->validated = 1; + + } else if (rc == NGX_ERROR) { + return NGX_ERROR; + + } else { + /* NGX_DECLINED */ + if (conf->retry) { + return ngx_quic_send_retry(c); + } + } + + } else if (conf->retry) { + return ngx_quic_send_retry(c); + } + + if (ngx_quic_init_secrets(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { + return NGX_ERROR; + } + + qc->server_seqnum = 0; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + return NGX_ERROR; + } + + } else if (pkt->level == ssl_encryption_application) { + return ngx_quic_send_stateless_reset(c, conf, pkt); + + } else { + return NGX_ERROR; + } + } + + c->log->action = "decrypting packet"; + + if (!ngx_quic_keys_available(qc->keys, pkt->level)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no level %d keys yet, ignoring packet", pkt->level); + return NGX_DECLINED; + } + + pkt->keys = qc->keys; + pkt->key_phase = qc->key_phase; + pkt->plaintext = buf; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); + if (rc != NGX_OK) { + qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return rc; + } + + pkt->decrypted = 1; + + if (c->ssl == NULL) { + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (pkt->level == ssl_encryption_handshake) { + /* + * 4.10.1. The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ngx_quic_discard_ctx(c, ssl_encryption_initial); + + if (qc->validated == 0) { + qc->validated = 1; + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + + pkt->received = ngx_current_msec; + + c->log->action = "handling payload"; + + if (pkt->level != ssl_encryption_application) { + return ngx_quic_payload_handler(c, pkt); + } + + if (!pkt->key_update) { + return ngx_quic_payload_handler(c, pkt); + } + + /* switch keys and generate next on Key Phase change */ + + qc->key_phase ^= 1; + ngx_quic_keys_switch(c, qc->keys); + + rc = ngx_quic_payload_handler(c, pkt); + if (rc != NGX_OK) { + return rc; + } + + return ngx_quic_keys_update(c, qc->keys); +} + + +static ngx_int_t +ngx_quic_init_secrets(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) + != NGX_OK) + { + return NGX_ERROR; + } + + qc->initialized = 1; + + return NGX_OK; +} + + +static void +ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, level)) { + return; + } + + ngx_quic_keys_discard(qc->keys, level); + + qc->pto_count = 0; + + ctx = ngx_quic_get_send_ctx(qc, level); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + if (level == ssl_encryption_initial) { + ngx_quic_clear_temp_server_ids(c); + } + + ctx->send_ack = 0; +} + + +static ngx_int_t +ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (pkt->level == ssl_encryption_application) { + return NGX_OK; + } + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (pkt->scid.len == cid->len + && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) + { + return NGX_OK; + } + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_buf_t buf; + ngx_uint_t do_close; + ngx_chain_t chain; + ngx_quic_frame_t frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->closing) { + /* + * 10.1 Closing and Draining Connection States + * ... delayed or reordered packets are properly discarded. + * + * An endpoint retains only enough information to generate + * a packet containing a CONNECTION_CLOSE frame and to identify + * packets as belonging to the connection. + */ + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); + } + + p = pkt->payload.data; + end = p + pkt->payload.len; + + do_close = 0; + + while (p < end) { + + c->log->action = "parsing frames"; + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + buf.temporary = 1; + + chain.buf = &buf; + chain.next = NULL; + frame.data = &chain; + + len = ngx_quic_parse_frame(pkt, p, end, &frame); + + if (len < 0) { + qc->error = pkt->error; + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 0); + + c->log->action = "handling frames"; + + p += len; + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + continue; + + case NGX_QUIC_FT_PADDING: + /* no action required */ + continue; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + do_close = 1; + continue; + } + + /* got there with ack-eliciting packet */ + pkt->need_ack = 1; + + switch (frame.type) { + + case NGX_QUIC_FT_CRYPTO: + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_DATA: + + if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + if (ngx_quic_handle_streams_blocked_frame(c, pkt, + &frame.u.streams_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, + &frame.u.stream_data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (ngx_quic_handle_max_stream_data_frame(c, pkt, + &frame.u.max_stream_data) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + if (ngx_quic_handle_reset_stream_frame(c, pkt, + &frame.u.reset_stream) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + if (ngx_quic_handle_stop_sending_frame(c, pkt, + &frame.u.stop_sending) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + if (ngx_quic_handle_new_connection_id_frame(c, pkt, &frame.u.ncid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (ngx_quic_handle_retire_connection_id_frame(c, pkt, + &frame.u.retire_cid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + /* TODO: handle */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic frame handler not implemented"); + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic missing frame handler"); + return NGX_ERROR; + } + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic trailing garbage in payload:%ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + return NGX_ERROR; + } + + if (do_close) { + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); + } + + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; + + c->log->action = "preparing ack"; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, + ctx->first_range, ctx->nranges); + + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&qc->push, &ngx_posted_events); + + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; + + if (ctx->pending_ack == NGX_QUIC_UNSET_PN + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == NGX_QUIC_UNSET_PN) { + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + return NGX_OK; +} + + +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + + base = ctx->largest_range; + + if (base == NGX_QUIC_UNSET_PN) { + return; + } + + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = NGX_QUIC_UNSET_PN; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + +static ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t len, left; + uint64_t ack_delay; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, **ll; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->ctp.ack_delay_exponent; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + ll = &frame->data; + b = NULL; + + for (i = 0; i < ctx->nranges; i++) { + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; + } + + *ll = NULL; + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ack_delay; + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->draining) { + return NGX_OK; + } + + if (!qc->initialized) { + /* try to initialize secrets to send an early error */ + if (ngx_quic_init_secrets(c) != NGX_OK) { + return NGX_OK; + } + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = qc->error_level; + frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; + frame->u.close.app = qc->error_app; + + if (qc->error_reason) { + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; + } + + ngx_quic_queue_frame(qc, frame); + + qc->last_cc = ngx_current_msec; + + return ngx_quic_output(c); +} + + +static ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c) +{ + ngx_str_t token; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->conf->retry) { + return NGX_OK; + } + + if (ngx_quic_new_token(c, &token) != NGX_OK) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->u.token.length = token.len; + frame->u.token.data = token.data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *f) +{ + ssize_t n; + u_char *pos, *end; + uint64_t min, max, gap, range; + ngx_msec_t send_time; + ngx_uint_t i; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + + ack = &f->u.ack; + + /* + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + * (19.3.1) + */ + + if (ack->first_range > ack->largest) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + max = ack->largest; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + + /* 13.2.3. Receiver Tracking of ACK Frames */ + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { + ctx->largest_ack = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic updated largest received ack:%uL", max); + + /* + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time); + } + } + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap + 2 > min) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + max = min - gap - 2; + + if (range > max) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_quic_detect_lost(c); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t min, uint64_t max, ngx_msec_t *send_time) +{ + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + *send_time = NGX_TIMER_INFINITE; + found = 0; + + q = ngx_queue_last(&ctx->sent); + + while (q != ngx_queue_sentinel(&ctx->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_prev(q); + + if (f->pnum >= min && f->pnum <= max) { + ngx_quic_congestion_ack(c, f); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + ngx_quic_handle_stream_ack(c, f); + break; + } + + if (f->pnum == max) { + *send_time = f->last; + } + + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + } + } + + if (!found) { + + if (max < ctx->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ACK for the packet not sent"); + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; + + return NGX_ERROR; + } + + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + + qc->pto_count = 0; + + return NGX_OK; +} + + +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; + + if (c->ssl->handshaked) { + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample latest:%M min:%M avg:%M var:%M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + +static ngx_inline ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* PTO calculation: quic-recovery, Appendix 8 */ + duration = qc->avg_rtt; + + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration <<= qc->pto_count; + + if (qc->congestion.in_flight == 0) { /* no in-flight packets */ + return duration; + } + + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay << qc->pto_count; + } + + return duration; +} + + +static void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t sent, unacked; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (sn == NULL) { + return; + } + + wev = sn->c->write; + sent = sn->c->sent; + unacked = sent - sn->acked; + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + + sn->acked += f->u.stream.length; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, + "quic stream ack len:%uL acked:%uL unacked:%uL", + f->u.stream.length, sn->acked, sent - sn->acked); +} + + +static ngx_int_t +ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) +{ + size_t full_len; + ngx_int_t rc; + ngx_queue_t *q; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + if (f->offset > fs->received) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic out-of-order frame: expecting:%uL got:%uL", + fs->received, f->offset); + + return ngx_quic_buffer_frame(c, fs, frame); + } + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; + } + + /* intersecting data range, frame modified */ + } + + /* f->offset == fs->received */ + + rc = handler(c, frame, data); + if (rc == NGX_ERROR) { + return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; + } + + /* rc == NGX_OK */ + + fs->received += f->length; + + /* now check the queue if we can continue with buffered frames */ + + do { + q = ngx_queue_head(&fs->frames); + if (q == ngx_queue_sentinel(&fs->frames)) { + break; + } + + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &frame->u.ord; + + if (f->offset > fs->received) { + /* gap found, nothing more to do */ + break; + } + + full_len = f->length; + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + ngx_queue_remove(q); + fs->total -= f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic skipped buffered frame, total:%ui", + fs->total); + ngx_quic_free_frame(c, frame); + continue; + } + + /* frame was adjusted, proceed to input */ + } + + /* f->offset == fs->received */ + + rc = handler(c, frame, data); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; + } + + fs->received += f->length; + fs->total -= full_len; + + ngx_queue_remove(q); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic consumed buffered frame, total:%ui", fs->total); + + ngx_quic_free_frame(c, frame); + + } while (1); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, + uint64_t offset_in) +{ + size_t tail, n; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + tail = offset_in - f->offset; + + if (tail >= f->length) { + /* range preceeding already received data or duplicate, ignore */ + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic old or duplicate data in ordered frame, ignored"); + return NGX_DONE; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic adjusted ordered frame data start to expected offset"); + + /* intersecting range: adjust data size */ + + f->offset += tail; + f->length -= tail; + + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + n = ngx_buf_size(b); + + if (n >= tail) { + b->pos += tail; + break; + } + + cl->buf->pos = cl->buf->last; + tail -= n; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame) +{ + ngx_queue_t *q; + ngx_quic_frame_t *dst, *item; + ngx_quic_ordered_frame_t *f, *df; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_buffer_frame"); + + f = &frame->u.ord; + + /* frame start offset is in the future, buffer it */ + + dst = ngx_quic_alloc_frame(c); + if (dst == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); + + dst->data = ngx_quic_copy_chain(c, frame->data, 0); + if (dst->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + df = &dst->u.ord; + + fs->total += f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ordered frame with unexpected offset:" + " buffered total:%ui", fs->total); + + if (ngx_queue_empty(&fs->frames)) { + ngx_queue_insert_after(&fs->frames, &dst->queue); + return NGX_OK; + } + + for (q = ngx_queue_last(&fs->frames); + q != ngx_queue_sentinel(&fs->frames); + q = ngx_queue_prev(q)) + { + item = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &item->u.ord; + + if (f->offset < df->offset) { + ngx_queue_insert_after(q, &dst->queue); + return NGX_OK; + } + } + + ngx_queue_insert_after(&fs->frames, &dst->queue); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + ngx_quic_frames_stream_t *fs; + + qc = ngx_quic_get_connection(c); + fs = &qc->crypto[pkt->level]; + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } + + rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, + NULL); + if (rc != NGX_DECLINED) { + return rc; + } + + /* speeding up handshake completion */ + + if (pkt->level == ssl_encryption_initial) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ssl_conn = c->ssl->connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + b->pos, b->last - b->pos)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + + return NGX_OK; + } + + if (SSL_in_init(ssl_conn)) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handshake completed successfully"); + + c->ssl->handshaked = 1; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + /* 12.4 Frames and frame types, figure 8 */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (ngx_quic_send_new_token(c) != NGX_OK) { + return NGX_ERROR; + } + + /* + * Generating next keys before a key update is received. + * See quic-tls 9.4 Header Protection Timing Side-Channels. + */ + + if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { + return NGX_ERROR; + } + + /* + * 4.10.2 An endpoint MUST discard its handshake keys + * when the TLS handshake is confirmed + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); + + if (ngx_quic_issue_server_ids(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + size_t window; + uint64_t last; + ngx_buf_t *b; + ngx_pool_t *pool; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + ngx_quic_frames_stream_t *fs; + + qc = ngx_quic_get_connection(c); + f = &frame->u.stream; + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->stream_id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + fs = &sn->fs; + b = sn->b; + window = b->end - b->last; + + if (last > window) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + goto cleanup; + } + + if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn) + != NGX_OK) + { + goto cleanup; + } + + sc->listening->handler(sc); + + return NGX_OK; + } + + fs = &sn->fs; + b = sn->b; + window = (b->pos - b->start) + (b->end - b->last); + + if (last > fs->received && last - fs->received > window) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn); + +cleanup: + + pool = sc->pool; + + ngx_close_connection(sc); + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +{ + uint64_t id; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_chain_t *cl; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = ngx_quic_get_connection(c); + sn = data; + + f = &frame->u.stream; + id = f->stream_id; + + b = sn->b; + + if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no space in stream buffer"); + return NGX_ERROR; + } + + if ((size_t) (b->end - b->last) < f->length) { + b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; + } + + for (cl = frame->data; cl; cl = cl->next) { + b->last = ngx_cpymem(b->last, cl->buf->pos, + cl->buf->last - cl->buf->pos); + } + + rev = sn->c->read; + rev->ready = 1; + + if (f->fin) { + rev->pending_eof = 1; + } + + if (rev->active) { + rev->handler(rev); + } + + /* check if stream was destroyed by handler */ + if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { + return NGX_DONE; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_event_t *wev; + 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 (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (qc->streams.sent >= qc->streams.send_max_data) { + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + wev = qs->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + } + + qc->streams.send_max_data = f->max_data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + size_t n; + ngx_buf_t *b; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + b = sn->b; + n = b->end - b->last; + + sn->c->listening->handler(sn->c); + + } else { + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = f->id; + frame->u.max_stream_data.limit = n; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + uint64_t sent; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit > sn->send_max_data) { + sn->send_max_data = f->limit; + } + + sn->c->listening->handler(sn->c); + + return NGX_OK; + } + + if (f->limit <= sn->send_max_data) { + return NGX_OK; + } + + sent = sn->c->sent; + + if (sent >= sn->send_max_data) { + wev = sn->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + + sn->send_max_data = f->limit; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_event_t *rev; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + rev = sc->read; + rev->error = 1; + rev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + rev = sn->c->read; + rev->error = 1; + rev->ready = 1; + + if (rev->active) { + rev->handler(rev); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_event_t *wev; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + wev = sc->write; + wev->error = 1; + wev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + wev = sn->c->write; + wev->error = 1; + wev->ready = 1; + + if (wev->active) { + wev->handler(wev); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + 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; + + 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; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_PATH_RESPONSE; + frame->u.path_response = *f; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { + return NGX_ERROR; + } + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) + { + /* + * ..a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NGX_ERROR; + } + + cid->seqnum = f->seqnum; + cid->len = f->len; + ngx_memcpy(cid->id, f->cid, f->len); + + ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + /* always use latest available connection id */ + if (f->seqnum > qc->client_seqnum) { + qc->scid.len = cid->len; + qc->scid.data = cid->id; + qc->client_seqnum = f->seqnum; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + /* this connection id must be retired */ + + if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + qc->nclient_ids--; + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = seqnum; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = ngx_queue_next(q)) + { + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum == f->sequence_number) { + ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + break; + } + } + + return ngx_quic_issue_server_ids(c); +} + + +static ngx_int_t +ngx_quic_issue_server_ids(ngx_connection_t *c) +{ + ngx_str_t dcid; + ngx_uint_t n; + ngx_quic_frame_t *frame; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + u_char id[NGX_QUIC_SERVER_CID_LEN]; + + qc = ngx_quic_get_connection(c); + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); + + while (qc->nserver_ids < n) { + if (ngx_quic_create_server_id(c, id) != NGX_OK) { + return NGX_ERROR; + } + + dcid.len = NGX_QUIC_SERVER_CID_LEN; + dcid.data = id; + + sid = ngx_quic_insert_server_id(c, &dcid); + if (sid == NULL) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} + + +static void +ngx_quic_clear_temp_server_ids(ngx_connection_t *c) +{ + ngx_queue_t *q, *next; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear temp server ids"); + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = next) + { + next = ngx_queue_next(q); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum != NGX_QUIC_UNSET_PN) { + continue; + } + + ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + } +} + + +static ngx_quic_server_id_t * +ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) +{ + ngx_str_t dcid; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + sid = ngx_quic_alloc_server_id(c, qc); + if (sid == NULL) { + return NULL; + } + + sid->seqnum = qc->server_seqnum; + + if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { + qc->server_seqnum++; + } + + sid->len = id->len; + ngx_memcpy(sid->id, id->data, id->len); + + ngx_queue_insert_tail(&qc->server_ids, &sid->queue); + qc->nserver_ids++; + + dcid.data = sid->id; + dcid.len = sid->len; + + ngx_insert_udp_connection(c, &sid->udp, &dcid); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic insert server id seqnum:%uL id len:%uz %xV", + sid->seqnum, id->len, id); + + return sid; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +static ngx_quic_server_id_t * +ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + + if (!ngx_queue_empty(&qc->free_server_ids)) { + + q = ngx_queue_head(&qc->free_server_ids); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + ngx_queue_remove(&sid->queue); + + ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); + + } else { + + sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); + if (sid == NULL) { + return NULL; + } + } + + return sid; +} + + +static void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_send_ctx_t *ctx; + + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_queue_insert_tail(&ctx->frames, &frame->queue); + + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +static ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + off_t max; + size_t len, min; + ssize_t n; + u_char *p; + ngx_uint_t i, pad; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + + for ( ;; ) { + p = dst; + + len = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + if (!qc->validated) { + max = qc->received * 3; + max = (c->sent >= max) ? 0 : max - c->sent; + len = ngx_min(len, (size_t) max); + } + + pad = ngx_quic_get_padding_level(c); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + + len = p - dst; + if (len == 0) { + break; + } + + n = ngx_quic_send(c, dst, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (!qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + } + + return NGX_OK; +} + + +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* + * 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->need_ack) { + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + return 1; + } + } + + return NGX_QUIC_SEND_CTX_LAST; +} + + +static ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; + + if (!ctx->send_ack) { + return NGX_OK; + } + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); + + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } + + return NGX_OK; + } + } + + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ctx->send_ack = 0; + + return NGX_OK; +} + + +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) +{ + size_t len, hlen, pad_len; + u_char *p; + ssize_t flen; + ngx_str_t out, res; + ngx_int_t rc; + ngx_uint_t nframes; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + hlen = (ctx->level == ssl_encryption_application) + ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; + + hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (!pkt.need_ack && f->need_ack && max > cg->window) { + max = cg->window; + } + + if (hlen + len >= max) { + break; + } + + if (hlen + len + f->len > max) { + rc = ngx_quic_split_frame(c, f, max - hlen - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } + } + + if (f->need_ack) { + pkt.need_ack = 1; + } + + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + + f->pnum = ctx->pnum; + f->first = now; + f->last = now; + f->plen = 0; + + nframes++; + } + + if (nframes == 0) { + return 0; + } + + out.data = src; + out.len = len; + + pkt.keys = qc->keys; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; + } + } + + ngx_quic_set_packet_number(&pkt, ctx); + + pkt.version = qc->version; + pkt.log = c->log; + pkt.level = ctx->level; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; + + pad_len = 4; + + if (min) { + hlen = EVP_GCM_TLS_TAG_LEN + + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + + if (min > hlen + pad_len) { + pad_len = min - hlen; + } + } + + if (out.len < pad_len) { + ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); + out.len = pad_len; + } + + pkt.payload = out; + + res.data = data; + + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx %s bytes:%ui" + " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", + ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, + pkt.number, pkt.num_len, pkt.trunc); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + if (pkt.need_ack) { + /* move frames into the sent queue to wait for ack */ + + if (!qc->closing) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + f->plen = res.len; + + do { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sent, q); + } while (--nframes); + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); + } + + cg->in_flight += res.len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + } + + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + return res.len; +} + + +static ngx_int_t +ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) +{ + size_t shrink; + ngx_quic_frame_t *nf; + ngx_quic_ordered_frame_t *of, *onf; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + nf = ngx_quic_alloc_frame(c); + if (nf == NULL) { + return NGX_ERROR; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + + nf->data = ngx_quic_split_bufs(c, f->data, of->length); + if (nf->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + + return NGX_OK; +} + + +static void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) +{ + ngx_buf_t b; + ngx_chain_t cl, *res; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.pos = b.start = buf; + b.last = b.end = buf + len; + b.last_buf = 1; + b.temporary = 1; + + cl.buf = &b; + cl.next= NULL; + + res = c->send_chain(c, &cl, 0); + if (res == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return len; +} + + +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; + } +} + + +static void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_quic_frame_t *start; + 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); + + qc->pto_count++; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto pnum:%uL pto_count:%ui level:%d", + start->pnum, qc->pto_count, start->level); + + ngx_quic_resend_frames(c, ctx); + } + + ngx_quic_connstate_dbg(c); +} + + +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); + + c = ev->data; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } + + ngx_quic_connstate_dbg(c); +} + + +static ngx_int_t +ngx_quic_detect_lost(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now, wait, min_wait, thr; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + min_wait = 0; + + thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); + thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } + + while (!ngx_queue_empty(&ctx->sent)) { + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum > ctx->largest_ack) { + break; + } + + wait = start->last + thr - now; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + + if (min_wait == 0 || wait < min_wait) { + min_wait = wait; + } + + break; + } + + ngx_quic_resend_frames(c, ctx); + } + } + + /* no more preceeding packets */ + + if (min_wait == 0) { + qc->pto.handler = ngx_quic_pto_handler; + return NGX_OK; + } + + qc->pto.handler = ngx_quic_lost_handler; + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + ngx_add_timer(&qc->pto, min_wait); + + return NGX_OK; +} + + +static void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t n; + ngx_buf_t *b; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); + + ngx_quic_congestion_lost(c, start); + + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != start->pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + 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; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + sn = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (sn == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + + if (f->u.max_stream_data.limit < n) { + f->u.max_stream_data.limit = n; + } + + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (sn && sn->c->write->error) { + /* RESET_STREAM was sent */ + ngx_quic_free_frame(c, f); + break; + } + + /* fall through */ + + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +ngx_connection_t * +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) +{ + size_t rcvbuf_size; + uint64_t id; + ngx_quic_stream_t *qs, *sn; + ngx_quic_connection_t *qc; + + qs = c->quic; + qc = ngx_quic_get_connection(qs->parent); + + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_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); + return NULL; + } + + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_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); + return NULL; + } + + id = (qc->streams.server_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" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + rcvbuf_size = 0; + } + + sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); + if (sn == NULL) { + return NULL; + } + + return sn->c; +} + + +static void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; + + for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; + + p = (qn->id < qnt->id) ? &temp->left : &temp->right; + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +static ngx_quic_stream_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) +{ + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; + + if (id == qn->id) { + return qn; + } + + node = (id < qn->id) ? node->left : node->right; + } + + return NULL; +} + + +static ngx_quic_stream_t * +ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) +{ + size_t n; + uint64_t min_id; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is new", id); + + qc = ngx_quic_get_connection(c); + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + n = qc->tp.initial_max_stream_data_uni; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_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; + n = qc->tp.initial_max_stream_data_bidi_remote; + } + + if (n < NGX_QUIC_STREAM_BUFSIZE) { + n = NGX_QUIC_STREAM_BUFSIZE; + } + + /* + * 2.1. Stream Types and Identifiers + * + * Within each type, streams are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being + * opened. + */ + + for ( /* void */ ; min_id < id; min_id += 0x04) { + + sn = ngx_quic_create_stream(c, min_id, n); + if (sn == NULL) { + return NULL; + } + + sn->c->listening->handler(sn->c); + } + + return ngx_quic_create_stream(c, id, n); +} + + +static ngx_quic_stream_t * +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) +{ + ngx_log_t *log; + ngx_pool_t *pool; + ngx_quic_stream_t *sn; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL create", id); + + qc = ngx_quic_get_connection(c); + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + return NULL; + } + + sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); + if (sn == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + sn->node.key = id; + sn->parent = c; + sn->id = id; + + sn->b = ngx_create_temp_buf(pool, rcvbuf_size); + if (sn->b == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + ngx_queue_init(&sn->fs.frames); + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + *log = *c->log; + pool->log = log; + + sn->c = ngx_get_connection(-1, log); + if (sn->c == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + sn->c->quic = sn; + sn->c->type = SOCK_STREAM; + sn->c->pool = pool; + sn->c->ssl = c->ssl; + sn->c->sockaddr = c->sockaddr; + sn->c->listening = c->listening; + sn->c->addr_text = c->addr_text; + sn->c->local_sockaddr = c->local_sockaddr; + sn->c->local_socklen = c->local_socklen; + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; + + sn->c->read->log = log; + sn->c->write->log = log; + + log->connection = sn->c->number; + + if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + sn->c->write->ready = 1; + } + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_uni; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + } else { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + } + } + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sn->c); + ngx_destroy_pool(pool); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sn->c; + + ngx_rbtree_insert(&qc->streams.tree, &sn->node); + + return sn; +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + b = qs->b; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + rev = c->read; + + if (rev->error) { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream recv id:0x%xL eof:%d avail:%z", + qs->id, rev->pending_eof, b->last - b->pos); + + if (b->pos == b->last) { + rev->ready = 0; + + if (rev->pending_eof) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; + } + + len = ngx_min(b->last - b->pos, (ssize_t) size); + + ngx_memcpy(buf, b->pos, len); + + b->pos += len; + qc->streams.received += len; + + if (b->pos == b->last) { + b->pos = b->start; + b->last = b->start; + rev->ready = rev->pending_eof; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z of size:%uz", + qs->id, len, size); + + if (!rev->pending_eof) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + + (b->end - b->last); + + ngx_quic_queue_frame(qc, frame); + } + + if ((qc->streams.recv_max_data / 2) < qc->streams.received) { + + frame = ngx_quic_alloc_frame(pc); + + if (frame == NULL) { + return NGX_ERROR; + } + + qc->streams.recv_max_data *= 2; + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv: increased max_data:%uL", + qs->id, qc->streams.recv_max_data); + } + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + size_t n, flow; + ngx_event_t *wev; + ngx_chain_t *cl; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + wev = c->write; + + if (wev->error) { + return NGX_CHAIN_ERROR; + } + + flow = ngx_quic_max_stream_flow(c); + if (flow == 0) { + wev->ready = 0; + return in; + } + + n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + frame->data = ngx_quic_copy_chain(pc, in, n); + if (frame->data == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + for (n = 0, cl = frame->data; cl; cl = cl->next) { + n += ngx_buf_size(cl->buf); + } + + while (in && ngx_buf_size(in->buf) == 0) { + in = in->next; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = n; + + c->sent += n; + qc->streams.sent += n; + + ngx_quic_queue_frame(qc, frame); + + wev->ready = (n < flow) ? 1 : 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uz", n); + + return in; +} + + +static size_t +ngx_quic_max_stream_flow(ngx_connection_t *c) +{ + size_t size; + uint64_t sent, unacked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + qc = ngx_quic_get_connection(qs->parent); + + size = NGX_QUIC_STREAM_BUFSIZE; + sent = c->sent; + unacked = sent - qs->acked; + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit buffer size"); + return 0; + } + + if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { + size = NGX_QUIC_STREAM_BUFSIZE - unacked; + } + + if (qc->streams.sent >= qc->streams.send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_DATA"); + return 0; + } + + if (qc->streams.sent + size > qc->streams.send_max_data) { + size = qc->streams.send_max_data - qc->streams.sent; + } + + if (sent >= qs->send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_STREAM_DATA"); + return 0; + } + + if (sent + size > qs->send_max_data) { + size = qs->send_max_data - sent; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow:%uz", size); + + return size; +} + + +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_quic_free_frames(pc, &qs->fs.frames); + + if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ + ngx_post_event(pc->read, &ngx_posted_events); + return; + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 + || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + if (!c->read->pending_eof && !c->read->error) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ + + ngx_quic_queue_frame(qc, frame); + } + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_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.bidi = 1; + } + + ngx_quic_queue_frame(qc, frame); + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + /* do not send fin for client unidirectional streams */ + return; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL send fin", qs->id); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 1; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = 0; + + ngx_quic_queue_frame(qc, frame); + + (void) ngx_quic_output(pc); +} + + +static ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse frame n:%ui", qc->nframes); +#endif + + } else { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ++qc->nframes; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc frame n:%ui", qc->nframes); +#endif + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + return frame; +} + + +static void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->in_flight -= f->plen; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + return; + } + + if (cg->window < cg->ssthresh) { + cg->window += f->plen; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->in_flight -= f->plen; + f->plen = 0; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + return; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); +} + + +static void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (frame->data) { + ngx_quic_free_bufs(c, frame->data); + } + + ngx_queue_insert_head(&qc->free_frames, &frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free frame n:%ui", qc->nframes); +#endif +} + + +uint32_t +ngx_quic_version(ngx_connection_t *c) +{ + uint32_t version; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + version = qc->version; + + return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; +} + + +static ngx_chain_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->free_bufs) { + cl = qc->free_bufs; + qc->free_bufs = cl->next; + + b = cl->buf; + b->pos = b->start; + b->last = b->start; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse buffer n:%ui", qc->nbufs); +#endif + + return cl; + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); + if (b == NULL) { + return NULL; + } + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + + cl->buf = b; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ++qc->nbufs; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc buffer n:%ui", qc->nbufs); +#endif + + return cl; +} + + +static void +ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_buf_t *b, *shadow; + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer n:%ui", qc->nbufs); +#endif + + cl = in; + in = in->next; + b = cl->buf; + + if (b->shadow) { + if (!b->last_shadow) { + b->recycled = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + do { + shadow = b->shadow; + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + b = shadow; + } while (b->recycled); + + if (b->shadow) { + b->last_shadow = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + cl->buf = b; + } + + cl->next = qc->free_bufs; + qc->free_bufs = cl; + } +} + + +static ngx_chain_t * +ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + b = cl->buf; + n = ngx_min((size_t) (b->end - b->last), len); + + b->last = ngx_cpymem(b->last, data, n); + + data += n; + len -= n; + + *ll = cl; + ll = &cl->next; + } + + *ll = NULL; + + return out; +} + + +static ngx_chain_t * +ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (in) { + if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { + in = in->next; + continue; + } + + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + + while (in && b->last != b->end) { + + n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); + + if (limit > 0 && n > limit) { + n = limit; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, n); + + in->buf->pos += n; + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (limit > 0) { + if (limit == n) { + goto done; + } + + limit -= n; + } + } + + } + +done: + + *ll = NULL; + + return out; +} + + +static ngx_chain_t * +ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *out; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { + n = ngx_buf_size(in->buf); + + if (n == len) { + out = in->next; + in->next = NULL; + return out; + } + + if (n > len) { + break; + } + + len -= n; + in = in->next; + } + + if (in == NULL) { + return NULL; + } + + /* split in->buf by creating shadow bufs which reference it */ + + if (in->buf->shadow == NULL) { + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + return NGX_CHAIN_ERROR; + } + } + + *b = *in->buf; + b->shadow = in->buf; + b->last_shadow = 1; + in->buf = b; + } + + out = ngx_alloc_chain_link(c->pool); + if (out == NULL) { + return NGX_CHAIN_ERROR; + } + + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + ngx_free_chain(c->pool, out); + return NGX_CHAIN_ERROR; + } + } + + out->buf = b; + out->next = in->next; + in->next = NULL; + + *b = *in->buf; + b->last_shadow = 0; + b->pos = b->pos + len; + + in->buf->shadow = b; + in->buf->last = in->buf->pos + len; + + return out; +} diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h new file mode 100644 index 000000000..59578feea --- /dev/null +++ b/src/event/quic/ngx_event_quic.h @@ -0,0 +1,143 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include +#include + + +/* Supported drafts: 27, 28, 29 */ +#ifndef NGX_QUIC_DRAFT_VERSION +#define NGX_QUIC_DRAFT_VERSION 29 +#endif + +#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ +#define NGX_QUIC_MAX_LONG_HEADER 56 + /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ + +#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 + +#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 +#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 + +#define NGX_QUIC_RETRY_TIMEOUT 3000 +#define NGX_QUIC_RETRY_LIFETIME 30000 +#define NGX_QUIC_RETRY_BUFFER_SIZE 128 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */ +#define NGX_QUIC_MAX_TOKEN_SIZE 32 + /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ + +/* quic-recovery, section 6.2.2, kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + +/* quic-recovery, section 6.1.1, Packet Threshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* quic-recovery, section 6.1.2, Time Threshold */ +#define NGX_QUIC_TIME_THR 1.125 +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + +#define NGX_QUIC_MIN_INITIAL_SIZE 1200 + +#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 +#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 + +#define NGX_QUIC_STREAM_BUFSIZE 65536 + +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN + +#define NGX_QUIC_SR_TOKEN_LEN 16 + +#define NGX_QUIC_MAX_SERVER_IDS 8 + +#define NGX_QUIC_BUFFER_SIZE 4096 + +#define ngx_quic_get_connection(c) ((ngx_quic_connection_t *)(c)->udp) + + +typedef struct { + /* configurable */ + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + size_t max_udp_payload_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + + /* TODO */ + void *preferred_address; +} ngx_quic_tp_t; + + +typedef struct { + ngx_ssl_t *ssl; + ngx_quic_tp_t tp; + ngx_flag_t retry; + ngx_flag_t require_alpn; + u_char token_key[32]; /* AES 256 */ + ngx_str_t sr_token_key; /* stateless reset token key */ +} ngx_quic_conf_t; + + +typedef struct { + uint64_t sent; + uint64_t received; + ngx_queue_t frames; /* reorder queue */ + size_t total; /* size of buffered data */ +} ngx_quic_frames_stream_t; + + +struct ngx_quic_stream_s { + ngx_rbtree_node_t node; + ngx_connection_t *parent; + ngx_connection_t *c; + uint64_t id; + uint64_t acked; + uint64_t send_max_data; + ngx_buf_t *b; + ngx_quic_frames_stream_t fs; +}; + + +typedef struct ngx_quic_keys_s ngx_quic_keys_t; + + +void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); +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); +uint32_t ngx_quic_version(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); + + +/********************************* DEBUG *************************************/ + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c new file mode 100644 index 000000000..401b71121 --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.c @@ -0,0 +1,1188 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +#define NGX_QUIC_IV_LEN 12 + +#define NGX_AES_128_GCM_SHA256 0x1301 +#define NGX_AES_256_GCM_SHA384 0x1302 +#define NGX_CHACHA20_POLY1305_SHA256 0x1303 + + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#else +#define ngx_quic_cipher_t EVP_CIPHER +#endif + + +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +typedef struct ngx_quic_secret_s { + ngx_str_t secret; + ngx_str_t key; + ngx_str_t iv; + ngx_str_t hp; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + + +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn); +static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); +static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, + ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); + +static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); +static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); +static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in); +static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, + ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); + +static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); +static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); + + +static ngx_int_t +ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level) +{ + ngx_int_t len; + + if (level == ssl_encryption_initial) { + id = NGX_AES_128_GCM_SHA256; + } + + switch (id) { + + case NGX_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case NGX_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + case NGX_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + + default: + return NGX_ERROR; + } + + return len; +} + + +ngx_int_t +ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, + ngx_str_t *secret) +{ + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_uint_t i; + const EVP_MD *digest; + const EVP_CIPHER *cipher; + ngx_quic_secret_t *client, *server; + + static const uint8_t salt[20] = +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" + "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; +#else + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" + "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; +#endif + + client = &keys->secrets[ssl_encryption_initial].client; + server = &keys->secrets[ssl_encryption_initial].server; + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + cipher = EVP_aes_128_gcm(); + digest = EVP_sha256(); + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_str_t iss = { + .data = is, + .len = is_len + }; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic ngx_quic_set_initial_secret"); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic initial secret len:%uz %*xs", is_len, is_len, is); +#endif + + /* draft-ietf-quic-tls-23#section-5.2 */ + client->secret.len = SHA256_DIGEST_LENGTH; + server->secret.len = SHA256_DIGEST_LENGTH; + + client->key.len = EVP_CIPHER_key_length(cipher); + server->key.len = EVP_CIPHER_key_length(cipher); + + client->hp.len = EVP_CIPHER_key_length(cipher); + server->hp.len = EVP_CIPHER_key_length(cipher); + + client->iv.len = EVP_CIPHER_iv_length(cipher); + server->iv.len = EVP_CIPHER_iv_length(cipher); + + struct { + ngx_str_t label; + ngx_str_t *key; + ngx_str_t *prk; + } seq[] = { + + /* draft-ietf-quic-tls-23#section-5.2 */ + { ngx_string("tls13 client in"), &client->secret, &iss }, + { + ngx_string("tls13 quic key"), + &client->key, + &client->secret, + }, + { + ngx_string("tls13 quic iv"), + &client->iv, + &client->secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &client->hp, + &client->secret, + }, + { ngx_string("tls13 server in"), &server->secret, &iss }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + ngx_string("tls13 quic key"), + &server->key, + &server->secret, + }, + { + ngx_string("tls13 quic iv"), + &server->iv, + &server->secret, + }, + { + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + ngx_string("tls13 quic hp"), + &server->hp, + &server->secret, + }, + + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label, + seq[i].prk->data, seq[i].prk->len) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, + ngx_str_t *label, const uint8_t *prk, size_t prk_len) +{ + size_t info_len; + uint8_t *p; + uint8_t info[20]; + + if (out->data == NULL) { + out->data = ngx_pnalloc(pool, out->len); + if (out->data == NULL) { + return NGX_ERROR; + } + } + + info_len = 2 + 1 + label->len + 1; + + info[0] = 0; + info[1] = out->len; + info[2] = label->len; + p = ngx_cpymem(&info[3], label->data, label->len); + *p = '\0'; + + if (ngx_hkdf_expand(out->data, out->len, digest, + prk, prk_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, + "ngx_hkdf_expand(%V) failed", label); + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic expand %V key len:%uz %xV", label, out->len, out); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + + if (EVP_PKEY_derive_init(pctx) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + return NGX_ERROR; + } + +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, + ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_open(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + u_char *tag; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_DecryptUpdate(ctx, out->data, &len, in->data, + in->len - EVP_GCM_TLS_TAG_LEN) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + tag = in->data + in->len - EVP_GCM_TLS_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, EVP_GCM_TLS_TAG_LEN, tag) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_DecryptFinal_ex(ctx, out->data + len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_DecryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + EVP_CIPHER_CTX_free(ctx); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, s->key.data, s->key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_AEAD_CTX_seal(ctx, out->data, &out->len, out->len, nonce, s->iv.len, + in->data, in->len, ad->data, ad->len) + != 1) + { + EVP_AEAD_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + EVP_AEAD_CTX_free(ctx); +#else + int len; + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, s->key.data, nonce) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_EncryptFinal_ex(ctx, out->data + out->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, EVP_GCM_TLS_TAG_LEN, + out->data + in->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + return NGX_ERROR; + } + + EVP_CIPHER_CTX_free(ctx); + + out->len += EVP_GCM_TLS_TAG_LEN; +#endif + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, u_char *out, u_char *in) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[5] = {0}; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t counter; + + ngx_memcpy(&counter, in, sizeof(uint32_t)); + + if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); + return NGX_OK; + } +#endif + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, in) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + goto failed; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + goto failed; + } + + if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); + goto failed; + } + + EVP_CIPHER_CTX_free(ctx); + + return NGX_OK; + +failed: + + EVP_CIPHER_CTX_free(ctx); + + return NGX_ERROR; +} + + +int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = is_write ? &keys->secrets[level].server + : &keys->secrets[level].client; + + keys->cipher = SSL_CIPHER_get_protocol_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); + return 0; + } + + if (level == ssl_encryption_initial) { + return 0; + } + + peer_secret->secret.data = ngx_pnalloc(pool, secret_len); + if (peer_secret->secret.data == NULL) { + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + peer_secret->key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + peer_secret->hp.len = key_len; + + struct { + ngx_str_t label; + ngx_str_t *key; + const uint8_t *secret; + } seq[] = { + { ngx_string("tls13 quic key"), &peer_secret->key, secret }, + { ngx_string("tls13 quic iv"), &peer_secret->iv, secret }, + { ngx_string("tls13 quic hp"), &peer_secret->hp, secret }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret, secret_len) + != NGX_OK) + { + return 0; + } + } + + return 1; +} + + +ngx_quic_keys_t * +ngx_quic_keys_new(ngx_pool_t *pool) +{ + return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t)); +} + + +ngx_uint_t +ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + return keys->secrets[level].client.key.len != 0; +} + + +void +ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + keys->secrets[level].client.key.len = 0; +} + + +void +ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_quic_secrets_t *current, *next, tmp; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + tmp = *current; + *current = *next; + *next = tmp; +} + + +ngx_int_t +ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_uint_t i; + ngx_quic_ciphers_t ciphers; + ngx_quic_secrets_t *current, *next; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + + if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application) + == NGX_ERROR) + { + return NGX_ERROR; + } + + next->client.secret.len = current->client.secret.len; + next->client.key.len = current->client.key.len; + next->client.iv.len = current->client.iv.len; + next->client.hp = current->client.hp; + + next->server.secret.len = current->server.secret.len; + next->server.key.len = current->server.key.len; + next->server.iv.len = current->server.iv.len; + next->server.hp = current->server.hp; + + struct { + ngx_str_t label; + ngx_str_t *key; + ngx_str_t *secret; + } seq[] = { + { + ngx_string("tls13 quic ku"), + &next->client.secret, + ¤t->client.secret, + }, + { + ngx_string("tls13 quic key"), + &next->client.key, + &next->client.secret, + }, + { + ngx_string("tls13 quic iv"), + &next->client.iv, + &next->client.secret, + }, + { + ngx_string("tls13 quic ku"), + &next->server.secret, + ¤t->server.secret, + }, + { + ngx_string("tls13 quic key"), + &next->server.key, + &next->server.secret, + }, + { + ngx_string("tls13 quic iv"), + &next->server.iv, + &next->server.secret, + }, + }; + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + + if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label, + seq[i].secret->data, seq[i].secret->len) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *pnp, *sample; + ngx_str_t ad, out; + ngx_uint_t i; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + u_char nonce[12], mask[16]; + + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + + ad.data = res->data; + ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); + + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); + + if (ngx_quic_tls_seal(ciphers.c, secret, &out, + nonce, &pkt->payload, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + sample = &out.data[4 - pkt->num_len]; + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) + != NGX_OK) + { + return NGX_ERROR; + } + + /* quic-tls: 5.4.1. Header Protection Application */ + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + ngx_quic_secret_t secret; + ngx_quic_ciphers_t ciphers; + + /* 5.8. Retry Packet Integrity */ + static u_char key[16] = +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; +#else + "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; +#endif + static u_char nonce[12] = +#if (NGX_QUIC_DRAFT_VERSION >= 29) + "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; +#else + "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75"; +#endif + static ngx_str_t in = ngx_string(""); + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = EVP_GCM_TLS_TAG_LEN; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic retry itag len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) { + return NGX_ERROR; + } + + secret.key.len = sizeof(key); + secret.key.data = key; + secret.iv.len = sizeof(nonce); + + if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = itag.data + itag.len - start; + res->data = start; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, + u_char *token) +{ + uint8_t *p; + size_t is_len, key_len, info_len; + ngx_str_t label; + const EVP_MD *digest; + uint8_t info[20]; + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t key[SHA256_DIGEST_LENGTH]; + + /* 10.4.2. Calculating a Stateless Reset Token */ + + digest = EVP_sha256(); + ngx_str_set(&label, "sr_token_key"); + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + cid->data, cid->len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "ngx_hkdf_extract(%V) failed", &label); + return NGX_ERROR; + } + + key_len = SHA256_DIGEST_LENGTH; + + info_len = 2 + 1 + label.len + 1; + + info[0] = 0; + info[1] = key_len; + info[2] = label.len; + + p = ngx_cpymem(&info[3], label.data, label.len); + *p = '\0'; + + if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "ngx_hkdf_expand(%V) failed", &label); + return NGX_ERROR; + } + + ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); +#endif + + return NGX_OK; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn) +{ + u_char *p; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = ngx_min(len * 8, 62); + + p = *pos; + truncated_pn = *p++ ^ *mask++; + + while (--len) { + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); + } + + *pos = p; + + expected_pn = *largest_pn + 1; + pn_win = 1ULL << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) + && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + + } else if (candidate_pn > expected_pn + pn_hwin + && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); + + return candidate_pn; +} + + +static void +ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) +{ + nonce[len - 4] ^= (pn & 0xff000000) >> 24; + nonce[len - 3] ^= (pn & 0x00ff0000) >> 16; + nonce[len - 2] ^= (pn & 0x0000ff00) >> 8; + nonce[len - 1] ^= (pn & 0x000000ff); +} + + +ngx_int_t +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_create_retry_packet(pkt, res); + } + + return ngx_quic_create_packet(pkt, res); +} + + +ngx_int_t +ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) +{ + u_char *p, *sample; + size_t len; + uint64_t pn, lpn; + ngx_int_t pnl, rc, key_phase; + ngx_str_t in, ad; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + uint8_t mask[16], nonce[12]; + + if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &pkt->keys->secrets[pkt->level].client; + + p = pkt->raw->pos; + len = pkt->data + pkt->len - p; + + /* draft-ietf-quic-tls-23#section-5.4.2: + * the Packet Number field is assumed to be 4 bytes long + * draft-ietf-quic-tls-23#section-5.4.[34]: + * AES-Based and ChaCha20-Based header protections sample 16 bytes + */ + + if (len < EVP_GCM_TLS_TAG_LEN + 4) { + return NGX_DECLINED; + } + + sample = p + 4; + + /* header protection */ + + if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample) + != NGX_OK) + { + return NGX_DECLINED; + } + + pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + if (ngx_quic_short_pkt(pkt->flags)) { + key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; + + if (key_phase != pkt->key_phase) { + secret = &pkt->keys->next_key.client; + pkt->key_update = 1; + } + } + + lpn = *largest_pn; + + pnl = (pkt->flags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); + + pkt->pn = pn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx clearflags:%xd", pkt->flags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx number:%uL len:%xi", pn, pnl); + + /* packet protection */ + + in.data = p; + in.len = len - pnl; + + ad.len = p - pkt->data; + ad.data = pkt->plaintext; + + ngx_memcpy(ad.data, pkt->data, ad.len); + ad.data[0] = pkt->flags; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + pkt->payload.len = in.len - EVP_GCM_TLS_TAG_LEN; + pkt->payload.data = pkt->plaintext + ad.len; + + rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload, + nonce, &in, &ad, pkt->log); + if (rc != NGX_OK) { + return NGX_DECLINED; + } + + if (pkt->payload.len == 0) { + /* + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { + /* + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic reserved bit set in packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet payload len:%uz %xV", + pkt->payload.len, &pkt->payload); +#endif + + *largest_pn = lpn; + + return NGX_OK; +} + diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h new file mode 100644 index 000000000..4e39ea57a --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.h @@ -0,0 +1,38 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ + + +#include +#include + + +#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) + + +ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, + ngx_quic_keys_t *keys, ngx_str_t *secret); +int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + ngx_str_t *key, u_char *token); + +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); + + +#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c new file mode 100644 index 000000000..45c60c255 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.c @@ -0,0 +1,1983 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 +#define NGX_QUIC_LONG_DCID_OFFSET 6 +#define NGX_QUIC_SHORT_DCID_OFFSET 1 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + +#define ngx_quic_write_uint24(p, s) \ + ((p)[0] = (u_char) ((s) >> 16), \ + (p)[1] = (u_char) ((s) >> 8), \ + (p)[2] = (u_char) (s), \ + (p) + 3) + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define NGX_QUIC_VERSION(c) (0xff000000 + (c)) + + +static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static ngx_uint_t ngx_quic_varint_len(uint64_t value); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); +static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, + u_char **out); +static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, + u_char *dst); + +static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + size_t dcid_len); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); +static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); + +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); + +static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, + ngx_uint_t frame_type); +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, + ngx_chain_t *ranges); +static size_t ngx_quic_create_stop_sending(u_char *p, + ngx_quic_stop_sending_frame_t *ss); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); +static size_t ngx_quic_create_hs_done(u_char *p); +static size_t ngx_quic_create_new_token(u_char *p, + ngx_quic_new_token_frame_t *token); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data); +static size_t ngx_quic_create_max_streams(u_char *p, + ngx_quic_max_streams_frame_t *ms); +static size_t ngx_quic_create_max_stream_data(u_char *p, + ngx_quic_max_stream_data_frame_t *ms); +static size_t ngx_quic_create_max_data(u_char *p, + ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_response(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_new_connection_id(u_char *p, + ngx_quic_new_conn_id_frame_t *rcid); +static size_t ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); + +static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, + uint16_t id, ngx_quic_tp_t *dst); + + +uint32_t ngx_quic_versions[] = { +#if (NGX_QUIC_DRAFT_VERSION >= 29) + /* pretend we support all versions in range draft-29..v1 */ + NGX_QUIC_VERSION(29), + NGX_QUIC_VERSION(30), + NGX_QUIC_VERSION(31), + NGX_QUIC_VERSION(32), + /* QUICv1 */ + 0x00000001 +#else + NGX_QUIC_VERSION(NGX_QUIC_DRAFT_VERSION) +#endif +}; + +#define NGX_QUIC_NVERSIONS \ + (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) + + +/* literal errors indexed by corresponding value */ +static char *ngx_quic_errors[] = { + "NO_ERROR", + "INTERNAL_ERROR", + "CONNECTION_REFUSED", + "FLOW_CONTROL_ERROR", + "STREAM_LIMIT_ERROR", + "STREAM_STATE_ERROR", + "FINAL_SIZE_ERROR", + "FRAME_ENCODING_ERROR", + "TRANSPORT_PARAMETER_ERROR", + "CONNECTION_ID_LIMIT_ERROR", + "PROTOCOL_VIOLATION", + "INVALID_TOKEN", + "APPLICATION_ERROR", + "CRYPTO_BUFFER_EXCEEDED", + "KEY_UPDATE_ERROR", +}; + + +static ngx_inline u_char * +ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + if (pos >= end) { + return NULL; + } + + p = pos; + len = 1 << (*p >> 6); + + value = *p++ & 0x3f; + + if ((size_t)(end - p) < (len - 1)) { + return NULL; + } + + while (--len) { + value = (value << 8) + *p++; + } + + *out = value; + + return p; +} + + +static ngx_inline u_char * +ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) +{ + if ((size_t)(end - pos) < 1) { + return NULL; + } + + *value = *pos; + + return pos + 1; +} + + +static ngx_inline u_char * +ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint32(pos); + + return pos + sizeof(uint32_t); +} + + +static ngx_inline u_char * +ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + *out = pos; + + return pos + len; +} + + +static u_char * +ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + ngx_memcpy(dst, pos, len); + + return pos + len; +} + + +static ngx_uint_t +ngx_quic_varint_len(uint64_t value) +{ + ngx_uint_t bits; + + bits = 0; + while (value >> ((8 << bits) - 2)) { + bits++; + } + + return 1 << bits; +} + + +static void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + ngx_uint_t bits, len; + + p = *pos; + bits = 0; + + while (value >> ((8 << bits) - 2)) { + bits++; + } + + len = (1 << bits); + + while (len--) { + *p++ = value >> (len * 8); + } + + **pos |= bits << 6; + *pos = p; +} + + +u_char * +ngx_quic_error_text(uint64_t error_code) +{ + if (error_code >= NGX_QUIC_ERR_CRYPTO_ERROR) { + return (u_char *) "handshake error"; + } + + if (error_code >= NGX_QUIC_ERR_LAST) { + return (u_char *) "unknown error"; + } + + return (u_char *) ngx_quic_errors[error_code]; +} + + +ngx_int_t +ngx_quic_parse_packet(ngx_quic_header_t *pkt) +{ + if (!ngx_quic_long_pkt(pkt->flags)) { + pkt->level = ssl_encryption_application; + + if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) + { + return NGX_DECLINED; + } + + return NGX_OK; + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_DECLINED; + } + + if (!ngx_quic_supported_version(pkt->version)) { + return NGX_ABORT; + } + + if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { + return NGX_DECLINED; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) +{ + u_char *p, *end; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx short flags:%xd", pkt->flags); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid_len; + + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint8_t idlen; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + p = ngx_quic_read_uint32(p, end, &pkt->version); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read version"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx long flags:%xd version:%xD", + pkt->flags, pkt->version); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet dcid is too long"); + return NGX_ERROR; + } + + pkt->dcid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet scid is too long"); + return NGX_ERROR; + } + + pkt->scid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t varint; + + p = pkt->raw->pos; + end = pkt->raw->last; + + pkt->log->action = "parsing quic long header"; + + if (ngx_quic_pkt_in(pkt->flags)) { + + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } + + pkt->token.len = varint; + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + pkt->level = ssl_encryption_initial; + + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; + + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx %s len:%uL", + ngx_quic_level_name(pkt->level), varint); + + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", + ngx_quic_level_name(pkt->level)); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + varint - pkt->data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + +size_t +ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) +{ + u_char *p, *start; + ngx_uint_t i; + + p = start = out; + + *p++ = pkt->flags; + + /* + * The Version field of a Version Negotiation packet + * MUST be set to 0x00000000 + */ + p = ngx_quic_write_uint32(p, 0); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); + } + + return p - start; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, + u_char **pnp) +{ + return ngx_quic_short_pkt(pkt->flags) + ? ngx_quic_create_short_header(pkt, out, pkt_len, pnp) + : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); +} + + +static size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp) +{ + u_char *p, *start; + + if (out == NULL) { + return 5 + 2 + pkt->dcid.len + pkt->scid.len + + ngx_quic_varint_len(pkt_len + pkt->num_len) + pkt->num_len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); + } + + ngx_quic_build_int(&p, pkt_len + pkt->num_len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +static size_t +ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp) +{ + u_char *p, *start; + + if (out == NULL) { + return 1 + pkt->dcid.len + pkt->num_len; + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +size_t +ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start) +{ + u_char *p; + + p = out; + + *p++ = pkt->odcid.len; + p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); + + *start = p; + + *p++ = 0xff; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + + return p - out; +} + + +#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) +#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) +#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) + +ssize_t +ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *f) +{ + u_char *p; + uint64_t varint; + ngx_buf_t *b; + ngx_uint_t i; + + b = f->data->buf; + + p = start; + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to obtain quic frame type"); + return NGX_ERROR; + } + + f->type = varint; + + if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.crypto.length); + if (p == NULL) { + goto error; + } + + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + break; + + case NGX_QUIC_FT_PADDING: + + while (p < end && *p == NGX_QUIC_FT_PADDING) { + p++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) + { + goto error; + } + + b->pos = p; + + /* process all ranges to get bounds, values are ignored */ + for (i = 0; i < f->u.ack.range_count; i++) { + + p = ngx_quic_parse_int(p, end, &varint); + if (p) { + p = ngx_quic_parse_int(p, end, &varint); + } + + if (p == NULL) { + goto error; + } + } + + b->last = p; + + f->u.ack.ranges_length = b->last - b->pos; + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + + if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) + && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) + { + goto error; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.retire > f->u.ncid.seqnum) { + goto error; + } + + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); + if (p == NULL) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); + if (p == NULL) { + goto error; + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); + if (p == NULL) { + goto error; + } + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.close.reason.len = varint; + + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, + &f->u.close.reason.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + f->u.stream.type = f->type; + + f->u.stream.off = ngx_quic_stream_bit_off(f->type); + f->u.stream.len = ngx_quic_stream_bit_len(f->type); + f->u.stream.fin = ngx_quic_stream_bit_fin(f->type); + + p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); + if (p == NULL) { + goto error; + } + + if (f->type & 0x04) { + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.offset = 0; + } + + if (f->type & 0x02) { + p = ngx_quic_parse_int(p, end, &f->u.stream.length); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.length = end - p; /* up to packet end */ + } + + p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + break; + + case NGX_QUIC_FT_MAX_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) + && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) + && (p = ngx_quic_parse_int(p, end, + &f->u.reset_stream.final_size)))) + { + goto error; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); + if (p == NULL) { + goto error; + } + + f->u.streams_blocked.bidi = + (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); + if (p == NULL) { + goto error; + } + + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); + if (p == NULL) { + goto error; + } + + break; + + default: + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xi", f->type); + return NGX_ERROR; + } + + f->level = pkt->level; + + return p - start; + +error: + + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse frame type:0x%xi", f->type); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +{ + uint8_t ptype; + + /* frame permissions per packet: 4 bits: IH01: 12.4, Table 3 */ + 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 */ + /* STREAM0 */ 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 */ 0x3, +#if (NGX_QUIC_DRAFT_VERSION >= 28) + /* CONNECTION_CLOSE */ 0xF, + /* CONNECTION_CLOSE2 */ 0x3, +#else + /* CONNECTION_CLOSE */ 0xD, + /* CONNECTION_CLOSE2 */ 0x1, +#endif + /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + }; + + if (ngx_quic_long_pkt(pkt->flags)) { + + if (ngx_quic_pkt_in(pkt->flags)) { + ptype = 8; /* initial */ + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + ptype = 4; /* handshake */ + + } else { + ptype = 2; /* zero-rtt */ + } + + } else { + ptype = 1; /* application data */ + } + + if (ptype & ngx_quic_frame_masks[frame_type]) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xd", + frame_type, pkt->flags); + + return NGX_DECLINED; +} + + +ssize_t +ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, + uint64_t *gap, uint64_t *range) +{ + u_char *p; + + p = start; + + p = ngx_quic_parse_int(p, end, gap); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame gap"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, range); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame range"); + return NGX_ERROR; + } + + return p - start; +} + + +size_t +ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(gap); + len += ngx_quic_varint_len(range); + return len; + } + + start = p; + + ngx_quic_build_int(&p, gap); + ngx_quic_build_int(&p, range); + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) +{ + /* + * QUIC-recovery, section 2: + * + * Ack-eliciting Frames: All frames other than ACK, PADDING, and + * CONNECTION_CLOSE are considered ack-eliciting. + */ + f->need_ack = 1; + + switch (f->type) { + case NGX_QUIC_FT_ACK: + f->need_ack = 0; + return ngx_quic_create_ack(p, &f->u.ack, f->data); + + case NGX_QUIC_FT_STOP_SENDING: + return ngx_quic_create_stop_sending(p, &f->u.stop_sending); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto, f->data); + + case NGX_QUIC_FT_HANDSHAKE_DONE: + return ngx_quic_create_hs_done(p); + + case NGX_QUIC_FT_NEW_TOKEN: + return ngx_quic_create_new_token(p, &f->u.token); + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + return ngx_quic_create_stream(p, &f->u.stream, f->data); + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + f->need_ack = 0; + return ngx_quic_create_close(p, &f->u.close); + + case NGX_QUIC_FT_MAX_STREAMS: + return ngx_quic_create_max_streams(p, &f->u.max_streams); + + case NGX_QUIC_FT_MAX_STREAM_DATA: + return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + + case NGX_QUIC_FT_MAX_DATA: + return ngx_quic_create_max_data(p, &f->u.max_data); + + case NGX_QUIC_FT_PATH_RESPONSE: + return ngx_quic_create_path_response(p, &f->u.path_response); + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + return ngx_quic_create_new_connection_id(p, &f->u.ncid); + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->largest); + len += ngx_quic_varint_len(ack->delay); + len += ngx_quic_varint_len(ack->range_count); + len += ngx_quic_varint_len(ack->first_range); + len += ack->ranges_length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->largest); + ngx_quic_build_int(&p, ack->delay); + ngx_quic_build_int(&p, ack->range_count); + ngx_quic_build_int(&p, ack->first_range); + + while (ranges) { + b = ranges->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + ranges = ranges->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); + len += ngx_quic_varint_len(ss->id); + len += ngx_quic_varint_len(ss->error_code); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); + ngx_quic_build_int(&p, ss->id); + ngx_quic_build_int(&p, ss->error_code); + + return p - start; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->length); + len += crypto->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_hs_done(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); + + return p - start; +} + + +static size_t +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); + len += ngx_quic_varint_len(token->length); + len += token->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); + ngx_quic_build_int(&p, token->length); + p = ngx_cpymem(p, token->data, token->length); + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(sf->type); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + len += ngx_quic_varint_len(sf->stream_id); + + /* length is always present in generated frames */ + len += ngx_quic_varint_len(sf->length); + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, sf->type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + /* length is always present in generated frames */ + ngx_quic_build_int(&p, sf->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static ngx_int_t +ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, + ngx_quic_tp_t *dst) +{ + uint64_t varint; + ngx_str_t str; + + varint = 0; + ngx_str_null(&str); + + switch (id) { + + case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* zero-length option */ + if (end - p != 0) { + return NGX_ERROR; + } + dst->disable_active_migration = 1; + return NGX_OK; + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + case NGX_QUIC_TP_INITIAL_MAX_DATA: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + case NGX_QUIC_TP_MAX_ACK_DELAY: + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + return NGX_ERROR; + } + break; + + case NGX_QUIC_TP_INITIAL_SCID: + + str.len = end - p; + str.data = p; + break; + + default: + return NGX_DECLINED; + } + + switch (id) { + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + dst->max_idle_timeout = varint; + break; + + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + dst->max_udp_payload_size = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_DATA: + dst->initial_max_data = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + dst->initial_max_stream_data_bidi_local = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + dst->initial_max_stream_data_bidi_remote = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + dst->initial_max_stream_data_uni = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + dst->initial_max_streams_bidi = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + dst->initial_max_streams_uni = varint; + break; + + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + dst->ack_delay_exponent = varint; + break; + + case NGX_QUIC_TP_MAX_ACK_DELAY: + dst->max_ack_delay = varint; + break; + + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + dst->active_connection_id_limit = varint; + break; + + case NGX_QUIC_TP_INITIAL_SCID: + dst->initial_scid = str; + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, + ngx_log_t *log) +{ + uint64_t id, len; + ngx_int_t rc; + + while (p < end) { + p = ngx_quic_parse_int(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse transport param id"); + 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; + } + + p = ngx_quic_parse_int(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL length", id); + return NGX_ERROR; + } + + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL data", id); + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic unknown transport param id:0x%xL, skipped", id); + } + + p += len; + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic trailing garbage in" + " transport parameters: bytes:%ui", + end - p); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic transport parameters parsed ok"); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp disable active migration: %ui", + tp->disable_active_migration); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", + tp->max_idle_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_udp_payload_size:%ui", + tp->max_udp_payload_size); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", + tp->initial_max_data); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_local:%ui", + tp->initial_max_stream_data_bidi_local); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_remote:%ui", + tp->initial_max_stream_data_bidi_remote); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_uni:%ui", + tp->initial_max_stream_data_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_bidi:%ui", + tp->initial_max_streams_bidi); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_uni:%ui", + tp->initial_max_streams_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp ack_delay_exponent:%ui", + tp->ack_delay_exponent); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", + tp->max_ack_delay); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp active_connection_id_limit:%ui", + tp->active_connection_id_limit); + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial source_connection_id len:%uz %xV", + tp->initial_scid.len, &tp->initial_scid); +#endif + + return NGX_OK; +} + + +static size_t +ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); + len += ngx_quic_varint_len(ms->id); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); + ngx_quic_build_int(&p, ms->id); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static size_t +ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); + len += ngx_quic_varint_len(md->max_data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); + ngx_quic_build_int(&p, md->max_data); + + return p - start; +} + + +static size_t +ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); + len += ngx_quic_varint_len(ncid->seqnum); + len += ngx_quic_varint_len(ncid->retire); + len++; + len += ncid->len; + len += NGX_QUIC_SR_TOKEN_LEN; + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); + ngx_quic_build_int(&p, ncid->seqnum); + ngx_quic_build_int(&p, ncid->retire); + *p++ = ncid->len; + p = ngx_cpymem(p, ncid->cid, ncid->len); + p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); + + return p - start; +} + + +static size_t +ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); + len += ngx_quic_varint_len(rcid->sequence_number); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); + ngx_quic_build_int(&p, rcid->sequence_number); + + return p - start; +} + + +ssize_t +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, + size_t *clen) +{ + u_char *p; + size_t len; + +#define ngx_quic_tp_len(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value) \ + + ngx_quic_varint_len(ngx_quic_varint_len(value)) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#define ngx_quic_tp_strlen(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value.len) \ + + value.len + +#define ngx_quic_tp_str(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, value.len); \ + p = ngx_cpymem(p, value.data, value.len); \ + } while (0) + + p = pos; + + len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + if (clen) { + *clen = len; + } + + if (tp->disable_active_migration) { + len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + len += ngx_quic_varint_len(0); + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + 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); + } +#else + if (tp->original_dcid.len) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } +#endif + + 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; + } + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, + tp->initial_max_data); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + if (tp->disable_active_migration) { + ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + ngx_quic_build_int(&p, 0); + } + + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + 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); + } +#else + if (tp->original_dcid.len) { + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } +#endif + + 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; +} + + +static size_t +ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = cl->app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(cl->error_code); + + if (!cl->app) { + len += ngx_quic_varint_len(cl->frame_type); + } + + len += ngx_quic_varint_len(cl->reason.len); + len += cl->reason.len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, cl->error_code); + + if (!cl->app) { + ngx_quic_build_int(&p, cl->frame_type); + } + + ngx_quic_build_int(&p, cl->reason.len); + p = ngx_cpymem(p, cl->reason.data, cl->reason.len); + + return p - start; +} diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h new file mode 100644 index 000000000..aa1c888e0 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.h @@ -0,0 +1,356 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ +#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ + + +#include +#include + + +/* QUIC flags in first byte, see quic-transport 17.2 and 17.3 */ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_PKT_FIXED_BIT 0x40 +#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) +#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) + +/* Long packet types */ +#define NGX_QUIC_PKT_INITIAL 0x00 +#define NGX_QUIC_PKT_ZRTT 0x10 +#define NGX_QUIC_PKT_HANDSHAKE 0x20 +#define NGX_QUIC_PKT_RETRY 0x30 + +#define ngx_quic_pkt_in(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) + +#define ngx_quic_pkt_rb_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) +#define ngx_quic_pkt_hp_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) + +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" + + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM0 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E + +/* 22.4. QUIC Transport Error Codes Registry */ +/* Keep in sync with ngx_quic_errors[] */ +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E + +#define NGX_QUIC_ERR_LAST 0x0F +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 + +#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) + + +/* Transport parameters */ +#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 +#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define NGX_QUIC_TP_SR_TOKEN 0x02 +#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 +#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_TP_INITIAL_SCID 0x0F +#define NGX_QUIC_TP_RETRY_SCID 0x10 + +#define NGX_QUIC_CID_LEN_MIN 8 +#define NGX_QUIC_CID_LEN_MAX 20 + +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + +typedef struct { + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + uint64_t ranges_length; +} ngx_quic_ack_frame_t; + + +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint8_t len; + u_char cid[NGX_QUIC_CID_LEN_MAX]; + u_char srt[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_new_conn_id_frame_t; + + +typedef struct { + uint64_t length; + u_char *data; +} ngx_quic_new_token_frame_t; + +/* + * common layout for CRYPTO and STREAM frames; + * conceptually, CRYPTO frame is also a stream + * frame lacking some properties + */ +typedef struct { + uint64_t offset; + uint64_t length; +} ngx_quic_ordered_frame_t; + +typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; + + +typedef struct { + /* initial fields same as in ngx_quic_ordered_frame_t */ + uint64_t offset; + uint64_t length; + + uint8_t type; + uint64_t stream_id; + unsigned off:1; + unsigned len:1; + unsigned fin:1; +} ngx_quic_stream_frame_t; + + +typedef struct { + uint64_t max_data; +} ngx_quic_max_data_frame_t; + + +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; + ngx_uint_t app; /* unsigned app:1; */ +} ngx_quic_close_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; + uint64_t final_size; +} ngx_quic_reset_stream_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; +} ngx_quic_stop_sending_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_streams_blocked_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_max_streams_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_max_stream_data_frame_t; + + +typedef struct { + uint64_t limit; +} ngx_quic_data_blocked_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_stream_data_blocked_frame_t; + + +typedef struct { + uint64_t sequence_number; +} ngx_quic_retire_cid_frame_t; + + +typedef struct { + u_char data[8]; +} ngx_quic_path_challenge_frame_t; + + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + enum ssl_encryption_level_t level; + ngx_queue_t queue; + uint64_t pnum; + size_t plen; + ngx_msec_t first; + ngx_msec_t last; + ssize_t len; + ngx_uint_t need_ack; + /* unsigned need_ack:1; */ + + ngx_chain_t *data; + union { + ngx_quic_ack_frame_t ack; + ngx_quic_crypto_frame_t crypto; + ngx_quic_ordered_frame_t ord; + ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_new_token_frame_t token; + ngx_quic_stream_frame_t stream; + ngx_quic_max_data_frame_t max_data; + ngx_quic_close_frame_t close; + ngx_quic_reset_stream_frame_t reset_stream; + ngx_quic_stop_sending_frame_t stop_sending; + ngx_quic_streams_blocked_frame_t streams_blocked; + ngx_quic_max_streams_frame_t max_streams; + ngx_quic_max_stream_data_frame_t max_stream_data; + ngx_quic_data_blocked_frame_t data_blocked; + ngx_quic_stream_data_blocked_frame_t stream_data_blocked; + ngx_quic_retire_cid_frame_t retire_cid; + ngx_quic_path_challenge_frame_t path_challenge; + ngx_quic_path_challenge_frame_t path_response; + } u; +}; + + +typedef struct { + ngx_log_t *log; + + ngx_quic_keys_t *keys; + + ngx_msec_t received; + uint64_t number; + uint8_t num_len; + uint32_t trunc; + uint8_t flags; + uint32_t version; + ngx_str_t token; + enum ssl_encryption_level_t level; + ngx_uint_t error; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t odcid; /* retry packet tag */ + ngx_str_t dcid; + ngx_str_t scid; + uint64_t pn; + u_char *plaintext; + ngx_str_t payload; /* decrypted data */ + + unsigned need_ack:1; + unsigned key_phase:1; + unsigned key_update:1; + unsigned parsed:1; + unsigned decrypted:1; +} ngx_quic_header_t; + + +u_char *ngx_quic_error_text(uint64_t error_code); + +ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); + +size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); + +size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, + size_t pkt_len, u_char **pnp); + +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); +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, + u_char *end, uint64_t *gap, uint64_t *range); +size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); + +ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, 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); + +#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ -- cgit v1.2.3 From fb655007a110e3bfd4e8eaba8b401d67fb283a9a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 25 Dec 2020 14:18:51 +0300 Subject: QUIC: ngx_quic_module. --- auto/modules | 28 ++++++++++++++++++---------- src/event/quic/ngx_event_quic.c | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/auto/modules b/auto/modules index 82a887eb1..299e245ef 100644 --- a/auto/modules +++ b/auto/modules @@ -1325,16 +1325,24 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_link=YES ngx_module_order= - if [ $USE_OPENSSL_QUIC = YES ]; then - ngx_module_deps="$ngx_module_deps \ - src/event/quic/ngx_event_quic.h \ - src/event/quic/ngx_event_quic_transport.h \ - src/event/quic/ngx_event_quic_protection.h" - ngx_module_srcs="$ngx_module_srcs \ - src/event/quic/ngx_event_quic.c \ - src/event/quic/ngx_event_quic_transport.c \ - src/event/quic/ngx_event_quic_protection.c" - fi + . auto/module +fi + + +if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps="src/event/quic/ngx_event_quic.h \ + src/event/quic/ngx_event_quic_transport.h \ + src/event/quic/ngx_event_quic_protection.h" + ngx_module_srcs="src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_transport.c \ + src/event/quic/ngx_event_quic_protection.c" + + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= . auto/module fi diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 73cde84d5..9047f25a3 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -385,6 +385,29 @@ static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len); +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_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 +}; + + static SSL_QUIC_METHOD quic_method = { #if BORINGSSL_API_VERSION >= 10 ngx_quic_set_read_secret, -- cgit v1.2.3 From b20b58ca7d1323664c5e8f91231ade0edf0d0f31 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 15 Dec 2020 15:23:07 +0300 Subject: Core: added interface to linux bpf() system call. It contains wrappers for operations with BPF maps and for loading BPF programs. --- auto/options | 2 + auto/os/linux | 26 ++++++++++ src/core/ngx_bpf.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/core/ngx_bpf.h | 43 ++++++++++++++++ src/core/ngx_core.h | 3 ++ 5 files changed, 217 insertions(+) create mode 100644 src/core/ngx_bpf.c create mode 100644 src/core/ngx_bpf.h diff --git a/auto/options b/auto/options index fc4365ed3..ad3583058 100644 --- a/auto/options +++ b/auto/options @@ -169,6 +169,8 @@ USE_GEOIP=NO NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO +BPF_FOUND=NO + NGX_LIBATOMIC=NO NGX_CPU_CACHE_LINE= diff --git a/auto/os/linux b/auto/os/linux index 5e280eca7..f257d1afe 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -208,3 +208,29 @@ ngx_include="sys/vfs.h"; . auto/include CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64" + + +# (E)BPF + +ngx_feature="BPF support" +ngx_feature_name="NGX_HAVE_BPF" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test=" + union bpf_attr attr = { 0 }; + /* only declare BPF support if all required features found */ + attr.map_flags = 0; + attr.map_type = BPF_MAP_TYPE_SOCKHASH; + syscall(__NR_bpf, 0, &attr, 0);" + +. auto/feature + +if [ $ngx_found = yes ]; then + BPF_FOUND=YES + + CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" + CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" +fi diff --git a/src/core/ngx_bpf.c b/src/core/ngx_bpf.c new file mode 100644 index 000000000..6b2611708 --- /dev/null +++ b/src/core/ngx_bpf.c @@ -0,0 +1,143 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + +#define NGX_BPF_LOGBUF_SIZE (16 * 1024) + + +static ngx_inline int +ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) +{ + return syscall(__NR_bpf, cmd, attr, size); +} + + +void +ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd) +{ + ngx_uint_t i; + ngx_bpf_reloc_t *rl; + + rl = program->relocs; + + for (i = 0; i < program->nrelocs; i++) { + if (ngx_strcmp(rl[i].name, symbol) == 0) { + program->ins[rl[i].offset].src_reg = 1; + program->ins[rl[i].offset].imm = fd; + } + } +} + + +int +ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) +{ + int fd; + union bpf_attr attr; +#if (NGX_DEBUG) + char buf[NGX_BPF_LOGBUF_SIZE]; +#endif + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.license = (uint64_t) program->license; + attr.prog_type = program->type; + attr.insns = (uint64_t) program->ins; + attr.insn_cnt = program->nins; + +#if (NGX_DEBUG) + /* for verifier errors */ + attr.log_buf = (uint64_t) buf; + attr.log_size = NGX_BPF_LOGBUF_SIZE; + attr.log_level = 1; +#endif + + fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to load BPF program"); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "bpf verifier: %s", buf); + + return -1; + } + + return fd; +} + + +int +ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags) +{ + int fd; + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_type = type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = map_flags; + + fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to create BPF map"); + return NGX_ERROR; + } + + return fd; +} + + +int +ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uint64_t) key; + attr.value = (uint64_t) value; + attr.flags = flags; + + return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_delete(int fd, const void *key) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uint64_t) key; + + return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_lookup(int fd, const void *key, void *value) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uint64_t) key; + attr.value = (uint64_t) value; + + return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} diff --git a/src/core/ngx_bpf.h b/src/core/ngx_bpf.h new file mode 100644 index 000000000..f62a36e11 --- /dev/null +++ b/src/core/ngx_bpf.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_BPF_H_INCLUDED_ +#define _NGX_BPF_H_INCLUDED_ + + +#include +#include + +#include + + +typedef struct { + char *name; + int offset; +} ngx_bpf_reloc_t; + +typedef struct { + char *license; + enum bpf_prog_type type; + struct bpf_insn *ins; + size_t nins; + ngx_bpf_reloc_t *relocs; + size_t nrelocs; +} ngx_bpf_program_t; + + +void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, + int fd); +int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program); + +int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags); +int ngx_bpf_map_update(int fd, const void *key, const void *value, + uint64_t flags); +int ngx_bpf_map_delete(int fd, const void *key); +int ngx_bpf_map_lookup(int fd, const void *key, void *value); + +#endif /* _NGX_BPF_H_INCLUDED_ */ diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 8e6c756c9..256836546 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -95,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #include #include +#if (NGX_HAVE_BPF) +#include +#endif #define LF (u_char) '\n' -- cgit v1.2.3 From c4f31ccca174ff617a594b49ef255354e979b72d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 25 Dec 2020 15:01:15 +0300 Subject: QUIC: ngx_quic_bpf module. The quic kernel bpf helper inspects packet payload for DCID, extracts key and routes the packet into socket matching the key. Due to reuseport feature, each worker owns a personal socket, which is identified by the same key, used to create DCID. BPF objects are locked in RAM and are subject to RLIMIT_MEMLOCK. The "ulimit -l" command may be used to setup proper limits, if maps cannot be created with EPERM or updated with ETOOLONG. --- auto/modules | 16 + auto/options | 7 + auto/os/linux | 38 ++ src/core/nginx.c | 3 + src/event/quic/bpf/bpfgen.sh | 113 +++++ src/event/quic/bpf/makefile | 30 ++ src/event/quic/bpf/ngx_quic_reuseport_helper.c | 140 ++++++ src/event/quic/ngx_event_quic.c | 39 ++ src/event/quic/ngx_event_quic_bpf.c | 649 +++++++++++++++++++++++++ src/event/quic/ngx_event_quic_bpf_code.c | 90 ++++ src/event/quic/ngx_event_quic_transport.c | 18 + src/event/quic/ngx_event_quic_transport.h | 2 + 12 files changed, 1145 insertions(+) create mode 100644 src/event/quic/bpf/bpfgen.sh create mode 100644 src/event/quic/bpf/makefile create mode 100644 src/event/quic/bpf/ngx_quic_reuseport_helper.c create mode 100644 src/event/quic/ngx_event_quic_bpf.c create mode 100644 src/event/quic/ngx_event_quic_bpf_code.c diff --git a/auto/modules b/auto/modules index 299e245ef..b3a697e71 100644 --- a/auto/modules +++ b/auto/modules @@ -1345,6 +1345,22 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then ngx_module_order= . auto/module + + if [ $NGX_QUIC_BPF$BPF_FOUND$SO_COOKIE_FOUND = YESYESYES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_bpf_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ + src/event/quic/ngx_event_quic_bpf_code.c" + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + have=NGX_QUIC_BPF . auto/have + fi fi diff --git a/auto/options b/auto/options index ad3583058..d677dd970 100644 --- a/auto/options +++ b/auto/options @@ -45,6 +45,8 @@ USE_THREADS=NO NGX_FILE_AIO=NO +NGX_QUIC_BPF=YES + HTTP=YES NGX_HTTP_LOG_PATH= @@ -170,6 +172,7 @@ NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO BPF_FOUND=NO +SO_COOKIE_FOUND=NO NGX_LIBATOMIC=NO @@ -216,6 +219,8 @@ do --with-file-aio) NGX_FILE_AIO=YES ;; + --without-quic_bpf_module) NGX_QUIC_BPF=NO ;; + --with-ipv6) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-ipv6\" option is deprecated" @@ -450,6 +455,8 @@ cat << END --with-file-aio enable file AIO support + --without-quic_bpf_module disable ngx_quic_bpf_module + --with-http_ssl_module enable ngx_http_ssl_module --with-http_quic_module enable ngx_http_quic_module --with-http_v2_module enable ngx_http_v2_module diff --git a/auto/os/linux b/auto/os/linux index f257d1afe..4649f7aa8 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -234,3 +234,41 @@ if [ $ngx_found = yes ]; then CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" fi + + +# SO_COOKIE socket option + +ngx_feature="SO_COOKIE" +ngx_feature_name="NGX_HAVE_SO_COOKIE" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(uint64_t); + uint64_t cookie; + getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" +. auto/feature + +if [ $ngx_found = yes ]; then + SO_COOKIE_FOUND=YES + have=NGX_HAVE_SO_COOKIE . auto/have +fi + + +# ngx_quic_bpf module uses sockhash to select socket from reuseport group, +# support appeared in Linux-5.7: +# +# commit: 9fed9000c5c6cacfcaaa48aff74818072ae294cc +# bpf: Allow selecting reuseport socket from a SOCKMAP/SOCKHASH +# +if [ $NGX_QUIC_BPF$BPF_FOUND = YESYES ]; then + echo $ngx_n "checking for kernel with reuseport/BPF support...$ngx_c" + if [ $version -lt 329472 ]; then + echo " not found (at least 5.7 is required)" + NGX_QUIC_BPF=NO + else + echo " found" + fi +fi + diff --git a/src/core/nginx.c b/src/core/nginx.c index 48a20e9fd..062ab0898 100644 --- a/src/core/nginx.c +++ b/src/core/nginx.c @@ -680,6 +680,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].ignore) { + continue; + } p = ngx_sprintf(p, "%ud;", ls[i].fd); } diff --git a/src/event/quic/bpf/bpfgen.sh b/src/event/quic/bpf/bpfgen.sh new file mode 100644 index 000000000..78cbdac4d --- /dev/null +++ b/src/event/quic/bpf/bpfgen.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +export LANG=C + +set -e + +if [ $# -lt 1 ]; then + echo "Usage: PROGNAME=foo LICENSE=bar $0 " + exit 1 +fi + + +self=$0 +filename=$1 +funcname=$PROGNAME + +generate_head() +{ + cat << END +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +END +} + +generate_tail() +{ + cat << END + +ngx_bpf_program_t $PROGNAME = { + .relocs = bpf_reloc_prog_$funcname, + .nrelocs = sizeof(bpf_reloc_prog_$funcname) + / sizeof(bpf_reloc_prog_$funcname[0]), + .ins = bpf_insn_prog_$funcname, + .nins = sizeof(bpf_insn_prog_$funcname) + / sizeof(bpf_insn_prog_$funcname[0]), + .license = "$LICENSE", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; + +END +} + +process_relocations() +{ + echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {" + + objdump -r $filename | awk '{ + + if (enabled && $NF > 0) { + off = strtonum(sprintf("0x%s", $1)); + name = $3; + + printf(" { \"%s\", %d },\n", name, off/8); + } + + if ($1 == "OFFSET") { + enabled=1; + } +}' + echo "};" + echo +} + +process_section() +{ + echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {" + echo " /* opcode dst src offset imm */" + + section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname") + + # dd doesn't know hex + length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3)) + offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6)) + + for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8) + do + opcode=0x${ins:0:2} + srcdst=0x${ins:2:2} + + # bytes are dumped in LE order + offset=0x${ins:6:2}${ins:4:2} # short + immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int + + dst="$(($srcdst & 0xF))" + src="$(($srcdst & 0xF0))" + src="$(($src >> 4))" + + opcode=$(printf "0x%x" $opcode) + dst=$(printf "BPF_REG_%d" $dst) + src=$(printf "BPF_REG_%d" $src) + offset=$(printf "%d" $offset) + immedi=$(printf "0x%x" $immedi) + + printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi + done + +cat << END +}; + +END +} + +generate_head +process_relocations +process_section +generate_tail + diff --git a/src/event/quic/bpf/makefile b/src/event/quic/bpf/makefile new file mode 100644 index 000000000..b4d758f33 --- /dev/null +++ b/src/event/quic/bpf/makefile @@ -0,0 +1,30 @@ +CFLAGS=-O2 -Wall + +LICENSE=BSD + +PROGNAME=ngx_quic_reuseport_helper +RESULT=ngx_event_quic_bpf_code +DEST=../$(RESULT).c + +all: $(RESULT) + +$(RESULT): $(PROGNAME).o + LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@ + +DEFS=-DPROGNAME=\"$(PROGNAME)\" \ + -DLICENSE_$(LICENSE) \ + -DLICENSE=\"$(LICENSE)\" \ + +$(PROGNAME).o: $(PROGNAME).c + clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@ + +install: $(RESULT) + cp $(RESULT) $(DEST) + +clean: + @rm -f $(RESULT) *.o + +debug: $(PROGNAME).o + llvm-objdump -S -no-show-raw-insn $< + +.DELETE_ON_ERROR: diff --git a/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/event/quic/bpf/ngx_quic_reuseport_helper.c new file mode 100644 index 000000000..05919aaa9 --- /dev/null +++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +/* + * the bpf_helpers.h is not included into linux-headers, only available + * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf. + */ +#include + + +#if !defined(SEC) +#define SEC(NAME) __attribute__((section(NAME), used)) +#endif + + +#if defined(LICENSE_GPL) + +/* + * To see debug: + * + * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable + * cat /sys/kernel/debug/tracing/trace_pipe + * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable + */ + +#define debugmsg(fmt, ...) \ +do { \ + char __buf[] = fmt; \ + bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \ +} while (0) + +#else + +#define debugmsg(fmt, ...) + +#endif + +char _license[] SEC("license") = LICENSE; + +/*****************************************************************************/ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_SERVER_CID_LEN 20 + + +#define advance_data(nbytes) \ + offset += nbytes; \ + if (start + offset > end) { \ + debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \ + goto failed; \ + } \ + data = start + offset - 1; + + +#define ngx_quic_parse_uint64(p) \ + (((__u64)(p)[0] << 56) | \ + ((__u64)(p)[1] << 48) | \ + ((__u64)(p)[2] << 40) | \ + ((__u64)(p)[3] << 32) | \ + (p)[4] << 24 | \ + (p)[5] << 16 | \ + (p)[6] << 8 | \ + (p)[7]) + +/* + * actual map object is created by the "bpf" system call, + * all pointers to this variable are replaced by the bpf loader + */ +struct bpf_map_def SEC("maps") ngx_quic_sockmap; + + +SEC(PROGNAME) +int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) +{ + int rc; + __u64 key; + size_t len, offset; + unsigned char *start, *end, *data, *dcid; + + start = ctx->data; + end = (unsigned char *) ctx->data_end; + offset = 0; + + advance_data(sizeof(struct udphdr)); /* skip UDP header */ + advance_data(1); /* QUIC flags */ + + if (data[0] & NGX_QUIC_PKT_LONG) { + + advance_data(4); /* skip QUIC version */ + len = data[0]; /* read DCID length */ + + if (len < 8) { + /* it's useless to search for key in such short DCID */ + return SK_PASS; + } + + advance_data(1); /* skip DCID len */ + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + } + + dcid = &data[1]; + advance_data(len); /* we expect the packet to have full DCID */ + + /* make verifier happy */ + if (dcid + sizeof(__u64) > end) { + goto failed; + } + + key = ngx_quic_parse_uint64(dcid); + + rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0); + + switch (rc) { + case 0: + debugmsg("nginx quic socket selected by key 0x%x", key); + return SK_PASS; + + /* kernel returns positive error numbers, errno.h defines positive */ + case -ENOENT: + debugmsg("nginx quic default route for key 0x%x", key); + /* let the default reuseport logic decide which socket to choose */ + return SK_PASS; + + default: + debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%x", + rc, key); + goto failed; + } + +failed: + /* + * SK_DROP will generate ICMP, but we may want to process "invalid" packet + * in userspace quic to investigate further and finally react properly + * (maybe ignore, maybe send something in response or close connection) + */ + return SK_PASS; +} diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 9047f25a3..4da52a908 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -232,6 +232,9 @@ static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); +#if (NGX_QUIC_BPF) +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); +#endif static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, @@ -1297,6 +1300,14 @@ ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) return NGX_ERROR; } +#if (NGX_QUIC_BPF) + if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic bpf failed to generate socket key"); + /* ignore error, things still may work */ + } +#endif + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic create server id %*xs", (size_t) NGX_QUIC_SERVER_CID_LEN, id); @@ -1304,6 +1315,34 @@ ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) } +#if (NGX_QUIC_BPF) + +static ngx_int_t +ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) +{ + int fd; + uint64_t cookie; + socklen_t optlen; + + fd = c->listening->fd; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic getsockopt(SO_COOKIE) failed"); + + return NGX_ERROR; + } + + ngx_quic_dcid_encode_key(id, cookie); + + return NGX_OK; +} + +#endif + + static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c) { diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c new file mode 100644 index 000000000..f449793ea --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -0,0 +1,649 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include + + +#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS" +#define NGX_QUIC_BPF_VARSEP ';' +#define NGX_QUIC_BPF_ADDRSEP '#' + + +#define ngx_quic_bpf_get_conf(cycle) \ + (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module) + +#define ngx_quic_bpf_get_old_conf(cycle) \ + cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \ + : NULL + +#define ngx_core_get_conf(cycle) \ + (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module) + + +typedef struct { + ngx_queue_t queue; + int map_fd; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_uint_t unused; /* unsigned unused:1; */ +} ngx_quic_sock_group_t; + + +typedef struct { + ngx_flag_t enabled; + ngx_uint_t map_size; + ngx_queue_t groups; /* of ngx_quic_sock_group_t */ +} ngx_quic_bpf_conf_t; + + +static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle); + +static void ngx_quic_bpf_cleanup(void *data); +static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd, + const char *name); + +static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, + struct sockaddr *sa, socklen_t socklen); +static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log); + +static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle); + +extern ngx_bpf_program_t ngx_quic_reuseport_helper; + + +static ngx_command_t ngx_quic_bpf_commands[] = { + + { ngx_string("quic_bpf"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_quic_bpf_conf_t, enabled), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_bpf_module_ctx = { + ngx_string("quic_bpf"), + ngx_quic_bpf_create_conf, + NULL +}; + + +ngx_module_t ngx_quic_bpf_module = { + NGX_MODULE_V1, + &ngx_quic_bpf_module_ctx, /* module context */ + ngx_quic_bpf_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + ngx_quic_bpf_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_quic_bpf_create_conf(ngx_cycle_t *cycle) +{ + ngx_quic_bpf_conf_t *bcf; + + bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t)); + if (bcf == NULL) { + return NULL; + } + + bcf->enabled = NGX_CONF_UNSET; + bcf->map_size = NGX_CONF_UNSET_UINT; + + ngx_queue_init(&bcf->groups); + + return bcf; +} + + +static ngx_int_t +ngx_quic_bpf_module_init(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_listening_t *ls; + ngx_core_conf_t *ccf; + ngx_pool_cleanup_t *cln; + ngx_quic_bpf_conf_t *bcf; + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + ngx_conf_init_value(bcf->enabled, 0); + + bcf->map_size = ccf->worker_processes * 4; + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = bcf; + cln->handler = ngx_quic_bpf_cleanup; + + if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) { + if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) { + goto failed; + } + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].quic && ls[i].reuseport) { + if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) { + goto failed; + } + } + } + + if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + if (ngx_is_init_cycle(cycle->old_cycle)) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize, check limits"); + + /* refuse to start */ + return NGX_ERROR; + } + + /* + * returning error now will lead to master process exiting immediately + * leaving worker processes orphaned, what is really unexpected. + * Instead, just issue a not about failed initialization and try + * to cleanup a bit. Still program can be already loaded to kernel + * for some reuseport groups, and there is no way to revert, so + * behaviour may be inconsistent. + */ + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize properly, ignored." + "please check limits and note that nginx state now " + "can be inconsistent and restart may be required"); + + return NGX_OK; +} + + +static void +ngx_quic_bpf_cleanup(void *data) +{ + ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data; + + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map"); + } +} + + +static ngx_inline void +ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) +{ + if (close(fd) != -1) { + return; + } + + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "quic bpf close %s fd:%i failed", name, fd); +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls) +{ + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + grp->sockaddr, grp->socklen, 1) + == NGX_OK) + { + return grp; + } + } + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa, + socklen_t socklen) +{ + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NULL; + } + + grp->socklen = socklen; + grp->sockaddr = ngx_palloc(cycle->pool, socklen); + if (grp->sockaddr == NULL) { + return NULL; + } + ngx_memcpy(grp->sockaddr, sa, socklen); + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + return grp; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + int progfd, failed, flags, rc; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + if (!bcf->enabled) { + return NULL; + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH, + sizeof(uint64_t), sizeof(uint64_t), + bcf->map_size, 0); + if (grp->map_fd == -1) { + goto failed; + } + + flags = fcntl(grp->map_fd, F_GETFD); + if (flags == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf getfd failed"); + goto failed; + } + + /* need to inherit map during binary upgrade after exec */ + flags &= ~FD_CLOEXEC; + + rc = fcntl(grp->map_fd, F_SETFD, flags); + if (rc == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf setfd failed"); + goto failed; + } + + ngx_bpf_program_link(&ngx_quic_reuseport_helper, + "ngx_quic_sockmap", grp->map_fd); + + progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper); + if (progfd < 0) { + goto failed; + } + + failed = 0; + + if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, + &progfd, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed"); + failed = 1; + } + + ngx_quic_bpf_close(cycle->log, progfd, "program"); + + if (failed) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap created fd:%i", grp->map_fd); + return grp; + +failed: + + if (grp->map_fd != -1) { + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + } + + ngx_queue_remove(&grp->queue); + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + ngx_quic_bpf_conf_t *bcf, *old_bcf; + ngx_quic_sock_group_t *grp, *ogrp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_find_group(bcf, ls); + if (grp) { + return grp; + } + + old_bcf = ngx_quic_bpf_get_old_conf(cycle); + + if (old_bcf == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + ogrp = ngx_quic_bpf_find_group(old_bcf, ls); + if (ogrp == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = dup(ogrp->map_fd); + if (grp->map_fd == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to duplicate bpf map descriptor"); + + ngx_queue_remove(&grp->queue); + + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd duplicated old:%i new:%i", + ogrp->map_fd, grp->map_fd); + + return grp; +} + + +static ngx_int_t +ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + uint64_t cookie; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_get_group(cycle, ls); + + if (grp == NULL) { + if (!bcf->enabled) { + return NGX_OK; + } + + return NGX_ERROR; + } + + grp->unused = 0; + + cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log); + if (cookie == (uint64_t) NGX_ERROR) { + return NGX_ERROR; + } + + /* map[cookie] = socket; for use in kernel helper */ + if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to update socket map key=%xL", cookie); + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%d", + grp->map_fd, ls->fd, cookie, ls->worker); + + /* do not inherit this socket */ + ls->ignore = 1; + + return NGX_OK; +} + + +static uint64_t +ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log) +{ + uint64_t cookie; + socklen_t optlen; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "quic bpf getsockopt(SO_COOKIE) failed"); + + return (ngx_uint_t) NGX_ERROR; + } + + return cookie; +} + + +static ngx_int_t +ngx_quic_bpf_export_maps(ngx_cycle_t *cycle) +{ + u_char *p, *buf; + size_t len; + ngx_str_t *var; + ngx_queue_t *q; + ngx_core_conf_t *ccf; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + len = sizeof(NGX_QUIC_BPF_VARNAME) + 1; + + q = ngx_queue_head(&bcf->groups); + + while (q != ngx_queue_sentinel(&bcf->groups)) { + + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + q = ngx_queue_next(q); + + if (grp->unused) { + /* + * map was inherited, but it is not used in this configuration; + * do not pass such map further and drop the group to prevent + * interference with changes during reload + */ + + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + ngx_queue_remove(&grp->queue); + + continue; + } + + len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1; + } + + len++; + + buf = ngx_palloc(cycle->pool, len); + if (buf == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=", + sizeof(NGX_QUIC_BPF_VARNAME)); + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + p = ngx_sprintf(p, "%ud", grp->map_fd); + + *p++ = NGX_QUIC_BPF_ADDRSEP; + + p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p, + NGX_SOCKADDR_STRLEN, 1); + + *p++ = NGX_QUIC_BPF_VARSEP; + } + + *p = '\0'; + + var = ngx_array_push(&ccf->env); + if (var == NULL) { + return NGX_ERROR; + } + + var->data = buf; + var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) +{ + int s; + u_char *inherited, *p, *v; + ngx_uint_t in_fd; + ngx_addr_t tmp; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME); + + if (inherited == NULL) { + return NGX_OK; + } + + bcf = ngx_quic_bpf_get_conf(cycle); + +#if (NGX_SUPPRESS_WARN) + s = -1; +#endif + + in_fd = 1; + + for (p = inherited, v = p; *p; p++) { + + switch (*p) { + + case NGX_QUIC_BPF_ADDRSEP: + + if (!in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 0; + + s = ngx_atoi(v, p - v); + if (s == NGX_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited map fd"); + return NGX_ERROR; + } + + v = p + 1; + break; + + case NGX_QUIC_BPF_VARSEP: + + if (in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 1; + + grp = ngx_pcalloc(cycle->pool, + sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NGX_ERROR; + } + + grp->map_fd = s; + + if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v) + != NGX_OK) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited" + " address '%*s'", p - v , v); + + ngx_quic_bpf_close(cycle->log, s, "inherited map"); + + return NGX_ERROR; + } + + grp->sockaddr = tmp.sockaddr; + grp->socklen = tmp.socklen; + + grp->unused = 1; + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap inherited with " + "fd:%i address:%*s", + grp->map_fd, p - v, v); + v = p + 1; + break; + + default: + break; + } + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_bpf_code.c b/src/event/quic/ngx_event_quic_bpf_code.c new file mode 100644 index 000000000..9cbeb3432 --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf_code.c @@ -0,0 +1,90 @@ +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include +#include + +#include "ngx_bpf.h" + + +static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { + { "ngx_quic_sockmap", 56 }, +}; + +static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { + /* opcode dst src offset imm */ + { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 }, + { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 55, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 52, 0x0 }, + { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 43, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 40, 0x0 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x71, BPF_REG_5, BPF_REG_4, (int16_t) 12, 0x0 }, + { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 36, 0x0 }, + { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 33, 0x0 }, + { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 30, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, + { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0xc7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 }, + { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 }, + { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 }, + { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 }, + { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, +}; + + +ngx_bpf_program_t ngx_quic_reuseport_helper = { + .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper, + .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]), + .ins = bpf_insn_prog_ngx_quic_reuseport_helper, + .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]), + .license = "BSD", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; + diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 45c60c255..b2ae19620 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -43,6 +43,17 @@ #endif +#define ngx_quic_write_uint64(p, s) \ + ((p)[0] = (u_char) ((s) >> 56), \ + (p)[1] = (u_char) ((s) >> 48), \ + (p)[2] = (u_char) ((s) >> 40), \ + (p)[3] = (u_char) ((s) >> 32), \ + (p)[4] = (u_char) ((s) >> 24), \ + (p)[5] = (u_char) ((s) >> 16), \ + (p)[6] = (u_char) ((s) >> 8), \ + (p)[7] = (u_char) (s), \ + (p) + sizeof(uint64_t)) + #define ngx_quic_write_uint24(p, s) \ ((p)[0] = (u_char) ((s) >> 16), \ (p)[1] = (u_char) ((s) >> 8), \ @@ -1981,3 +1992,10 @@ ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) return p - start; } + + +void +ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key) +{ + (void) ngx_quic_write_uint64(dcid, key); +} diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index aa1c888e0..fe1cf2632 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -353,4 +353,6 @@ ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, size_t *clen); +void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); + #endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ -- cgit v1.2.3 From 291eb52899b39f3f1a7bfbab4b73abea1efa2c1f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 30 Dec 2020 20:47:35 +0300 Subject: QUIC: fixed header protection macro name. --- src/event/quic/ngx_event_quic_transport.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index fe1cf2632..1a3fe7766 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -4,8 +4,8 @@ */ -#ifndef _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ -#define _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ +#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ #include @@ -355,4 +355,4 @@ ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); -#endif /* _NGX_EVENT_QUIC_WIRE_H_INCLUDED_ */ +#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */ -- cgit v1.2.3 From f3c9e9f9616066c6f1d16b9b1e01b7a3d0e2503a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 11 Jan 2021 15:25:48 +0300 Subject: QUIC: draft-33 salt and retry keys. Notably, the version negotiation table is updated to reject draft-33/QUICv1 (which requires a new TLS codepoint) unless explicitly asked to built with. --- src/event/quic/ngx_event_quic_protection.c | 13 ++++++++++--- src/event/quic/ngx_event_quic_transport.c | 9 +++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 401b71121..253f43482 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -152,7 +152,10 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_quic_secret_t *client, *server; static const uint8_t salt[20] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) +#if (NGX_QUIC_DRAFT_VERSION >= 33) + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" + "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; +#elif (NGX_QUIC_DRAFT_VERSION >= 29) "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; #else @@ -889,13 +892,17 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) /* 5.8. Retry Packet Integrity */ static u_char key[16] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) +#if (NGX_QUIC_DRAFT_VERSION >= 33) + "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; +#elif (NGX_QUIC_DRAFT_VERSION >= 29) "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; #else "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; #endif static u_char nonce[12] = -#if (NGX_QUIC_DRAFT_VERSION >= 29) +#if (NGX_QUIC_DRAFT_VERSION >= 33) + "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; +#elif (NGX_QUIC_DRAFT_VERSION >= 29) "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; #else "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75"; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index b2ae19620..2ecfac5a4 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -123,14 +123,15 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint32_t ngx_quic_versions[] = { -#if (NGX_QUIC_DRAFT_VERSION >= 29) - /* pretend we support all versions in range draft-29..v1 */ +#if (NGX_QUIC_DRAFT_VERSION >= 33) + /* QUICv1 */ + 0x00000001, + NGX_QUIC_VERSION(33), +#elif (NGX_QUIC_DRAFT_VERSION >= 29) NGX_QUIC_VERSION(29), NGX_QUIC_VERSION(30), NGX_QUIC_VERSION(31), NGX_QUIC_VERSION(32), - /* QUICv1 */ - 0x00000001 #else NGX_QUIC_VERSION(NGX_QUIC_DRAFT_VERSION) #endif -- cgit v1.2.3 From 9e489d208fff35c490b43980a064c38cc8dc4f2c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 22 Jan 2021 16:34:06 +0300 Subject: HTTP/3: refactored request parser. The change reduces diff to the default branch for src/http/ngx_http_request.c and src/http/ngx_http_parse.c. --- src/http/modules/ngx_http_quic_module.c | 37 +++ src/http/modules/ngx_http_quic_module.h | 2 +- src/http/ngx_http.h | 5 + src/http/ngx_http_parse.c | 2 - src/http/ngx_http_request.c | 197 +++--------- src/http/ngx_http_request.h | 2 +- src/http/v3/ngx_http_v3.h | 10 +- src/http/v3/ngx_http_v3_request.c | 524 +++++++++++++++++++++----------- src/http/v3/ngx_http_v3_streams.c | 66 ++-- 9 files changed, 457 insertions(+), 388 deletions(-) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index ff79cdc8d..5314af35b 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -175,6 +175,43 @@ static ngx_http_variable_t ngx_http_quic_vars[] = { }; +ngx_int_t +ngx_http_quic_init(ngx_connection_t *c) +{ + ngx_quic_conf_t *qcf; + ngx_http_connection_t *hc, *phc; + ngx_http_core_loc_conf_t *clcf; + + hc = c->data; + + hc->ssl = 1; + + if (c->quic == NULL) { + c->log->connection = c->number; + + qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_quic_module); + + ngx_quic_run(c, qcf); + + return NGX_DONE; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init quic stream"); + + phc = c->quic->parent->data; + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; + hc->conf_ctx = phc->conf_ctx; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + ngx_set_connection_log(c, clcf->error_log); + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h index bd4930f8a..bc75dd501 100644 --- a/src/http/modules/ngx_http_quic_module.h +++ b/src/http/modules/ngx_http_quic_module.h @@ -18,7 +18,7 @@ #define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" -extern ngx_module_t ngx_http_quic_module; +ngx_int_t ngx_http_quic_init(ngx_connection_t *c); #endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */ diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 2a3d81a37..778744d71 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -134,6 +134,11 @@ void ngx_http_handler(ngx_http_request_t *r); void ngx_http_run_posted_requests(ngx_connection_t *c); ngx_int_t ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr); +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, + ngx_str_t *host); +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 7b5cd30cd..20ad89a77 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -143,7 +143,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) /* HTTP methods: GET, HEAD, POST */ case sw_start: - r->parse_start = p; r->request_start = p; if (ch == CR || ch == LF) { @@ -896,7 +895,6 @@ ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, /* first char */ case sw_start: - r->parse_start = p; r->header_name_start = p; r->invalid_header = 0; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 0ce4dacec..abcc1bba8 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -31,10 +31,6 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, - ngx_uint_t alloc); -static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, - ngx_str_t *host); static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); @@ -52,7 +48,6 @@ static void ngx_http_keepalive_handler(ngx_event_t *ev); static void ngx_http_set_lingering_close(ngx_connection_t *c); static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); -static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); static void ngx_http_log_request(ngx_http_request_t *r); static u_char *ngx_http_log_error_handler(ngx_http_request_t *r, @@ -303,54 +298,11 @@ ngx_http_init_connection(ngx_connection_t *c) hc->conf_ctx = hc->addr_conf->default_server->ctx; #if (NGX_HTTP_QUIC) - if (hc->addr_conf->quic) { - ngx_quic_conf_t *qcf; - ngx_http_connection_t *phc; - ngx_http_core_loc_conf_t *clcf; - - hc->ssl = 1; - -#if (NGX_HTTP_V3) - - if (hc->addr_conf->http3) { - ngx_int_t rc; - - rc = ngx_http_v3_init_connection(c); - - if (rc == NGX_ERROR) { - ngx_http_close_connection(c); - return; - } - - if (rc == NGX_DONE) { - return; - } - } - -#endif - - if (c->quic == NULL) { - c->log->connection = c->number; - - qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, - ngx_http_quic_module); - ngx_quic_run(c, qcf); + if (ngx_http_quic_init(c) == NGX_DONE) { return; } - - phc = c->quic->parent->data; - - if (phc->ssl_servername) { - hc->ssl_servername = phc->ssl_servername; - hc->conf_ctx = phc->conf_ctx; - - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, - ngx_http_core_module); - ngx_set_connection_log(c, clcf->error_log); - } } - #endif ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); @@ -380,6 +332,13 @@ ngx_http_init_connection(ngx_connection_t *c) } #endif +#if (NGX_HTTP_V3) + if (hc->addr_conf->http3) { + ngx_http_v3_init(c); + return; + } +#endif + #if (NGX_HTTP_SSL) { ngx_http_ssl_srv_conf_t *sscf; @@ -669,12 +628,6 @@ ngx_http_alloc_request(ngx_connection_t *c) r->method = NGX_HTTP_UNKNOWN; r->http_version = NGX_HTTP_VERSION_10; -#if (NGX_HTTP_V3) - if (hc->addr_conf->http3) { - r->http_version = NGX_HTTP_VERSION_30; - } -#endif - r->headers_in.content_length_n = -1; r->headers_in.keep_alive_n = -1; r->headers_out.content_length_n = -1; @@ -1140,16 +1093,7 @@ ngx_http_process_request_line(ngx_event_t *rev) } } - switch (r->http_version) { -#if (NGX_HTTP_V3) - case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_request(r, r->header_in); - break; -#endif - - default: /* HTTP/1.x */ - rc = ngx_http_parse_request_line(r, r->header_in); - } + rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { @@ -1157,7 +1101,7 @@ ngx_http_process_request_line(ngx_event_t *rev) r->request_line.len = r->request_end - r->request_start; r->request_line.data = r->request_start; - r->request_length = r->header_in->pos - r->parse_start; + r->request_length = r->header_in->pos - r->request_start; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http request line: \"%V\"", &r->request_line); @@ -1234,15 +1178,6 @@ ngx_http_process_request_line(ngx_event_t *rev) break; } - if (rc == NGX_BUSY) { - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return; - } - - break; - } - if (rc != NGX_AGAIN) { /* there was error while a request line parsing */ @@ -1272,8 +1207,8 @@ ngx_http_process_request_line(ngx_event_t *rev) } if (rv == NGX_DECLINED) { - r->request_line.len = r->header_in->end - r->parse_start; - r->request_line.data = r->parse_start; + r->request_line.len = r->header_in->end - r->request_start; + r->request_line.data = r->request_start; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long URI"); @@ -1437,7 +1372,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - rc = NGX_OK; + rc = NGX_AGAIN; for ( ;; ) { @@ -1453,7 +1388,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) } if (rv == NGX_DECLINED) { - p = r->parse_start; + p = r->header_name_start; r->lingering_close = 1; @@ -1473,7 +1408,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent too long header line: \"%*s...\"", - len, r->parse_start); + len, r->header_name_start); ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); @@ -1491,32 +1426,21 @@ ngx_http_process_request_headers(ngx_event_t *rev) /* the host header could change the server configuration context */ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - switch (r->http_version) { -#if (NGX_HTTP_V3) - case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_header(r, r->header_in, - cscf->underscores_in_headers); - break; -#endif - - default: /* HTTP/1.x */ - rc = ngx_http_parse_header_line(r, r->header_in, - cscf->underscores_in_headers); - } + rc = ngx_http_parse_header_line(r, r->header_in, + cscf->underscores_in_headers); if (rc == NGX_OK) { - r->request_length += r->header_in->pos - r->parse_start; + r->request_length += r->header_in->pos - r->header_name_start; if (r->invalid_header && cscf->ignore_invalid_headers) { /* there was error while a header line parsing */ ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid header line: \"%*s: %*s\"", - r->header_name_end - r->header_name_start, - r->header_name_start, - r->header_end - r->header_start, r->header_start); + "client sent invalid header line: \"%*s\"", + r->header_end - r->header_name_start, + r->header_name_start); continue; } @@ -1532,17 +1456,11 @@ ngx_http_process_request_headers(ngx_event_t *rev) h->key.len = r->header_name_end - r->header_name_start; h->key.data = r->header_name_start; - - if (h->key.data[h->key.len]) { - h->key.data[h->key.len] = '\0'; - } + h->key.data[h->key.len] = '\0'; h->value.len = r->header_end - r->header_start; h->value.data = r->header_start; - - if (h->value.data[h->value.len]) { - h->value.data[h->value.len] = '\0'; - } + h->value.data[h->value.len] = '\0'; h->lowcase_key = ngx_pnalloc(r->pool, h->key.len); if (h->lowcase_key == NULL) { @@ -1578,7 +1496,7 @@ ngx_http_process_request_headers(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http header done"); - r->request_length += r->header_in->pos - r->parse_start; + r->request_length += r->header_in->pos - r->header_name_start; r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; @@ -1693,7 +1611,7 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, return NGX_OK; } - old = r->parse_start; + old = request_line ? r->request_start : r->header_name_start; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); @@ -1771,14 +1689,6 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, b->pos = new + (r->header_in->pos - old); b->last = new + (r->header_in->pos - old); - r->parse_start = new; - - r->header_in = b; - - if (r->http_version > NGX_HTTP_VERSION_11) { - return NGX_OK; - } - if (request_line) { r->request_start = new; @@ -1827,6 +1737,8 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->header_end = new + (r->header_end - old); } + r->header_in = b; + return NGX_OK; } @@ -2047,46 +1959,13 @@ ngx_http_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } - if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_11) { + if (r->headers_in.host == NULL && r->http_version > NGX_HTTP_VERSION_10) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent HTTP/1.1 request without \"Host\" header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } - if (r->headers_in.host == NULL && r->http_version == NGX_HTTP_VERSION_20) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP/2 request without " - "\":authority\" or \"Host\" header"); - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; - } - - if (r->http_version == NGX_HTTP_VERSION_30) { - if (r->headers_in.server.len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP/3 request without " - "\":authority\" or \"Host\" header"); - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; - } - - if (r->headers_in.host) { - if (r->headers_in.host->value.len != r->headers_in.server.len - || ngx_memcmp(r->headers_in.host->value.data, - r->headers_in.server.data, - r->headers_in.server.len) - != 0) - { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent HTTP/3 request with different " - "values of \":authority\" and \"Host\" headers"); - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; - } - } - } - if (r->headers_in.content_length) { r->headers_in.content_length_n = ngx_atoof(r->headers_in.content_length->value.data, @@ -2125,12 +2004,6 @@ ngx_http_process_request_header(ngx_http_request_t *r) } } -#if (NGX_HTTP_V3) - if (r->http_version == NGX_HTTP_VERSION_30) { - r->headers_in.chunked = 1; - } -#endif - if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) { if (r->headers_in.keep_alive) { r->headers_in.keep_alive_n = @@ -2235,7 +2108,7 @@ ngx_http_process_request(ngx_http_request_t *r) } -static ngx_int_t +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) { u_char *h, ch; @@ -2326,7 +2199,7 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) } -static ngx_int_t +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) { ngx_int_t rc; @@ -3744,7 +3617,7 @@ ngx_http_post_action(ngx_http_request_t *r) } -static void +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_connection_t *c; @@ -3965,15 +3838,15 @@ ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr, len -= p - buf; buf = p; - if (r->request_line.data == NULL && r->parse_start) { - for (p = r->parse_start; p < r->header_in->last; p++) { + if (r->request_line.data == NULL && r->request_start) { + for (p = r->request_start; p < r->header_in->last; p++) { if (*p == CR || *p == LF) { break; } } - r->request_line.len = p - r->parse_start; - r->request_line.data = r->parse_start; + r->request_line.len = p - r->request_start; + r->request_line.data = r->request_start; } if (r->request_line.len) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 518c2f4d1..4121e3c3b 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -325,6 +325,7 @@ typedef struct { unsigned ssl:1; unsigned proxy_protocol:1; + unsigned http3:1; } ngx_http_connection_t; @@ -581,7 +582,6 @@ struct ngx_http_request_s { * via ngx_http_ephemeral_t */ - u_char *parse_start; u_char *uri_start; u_char *uri_end; u_char *uri_ext; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 89ed9c98a..9f91ff8f1 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -127,17 +127,11 @@ typedef struct { uint64_t next_push_id; uint64_t max_push_id; - ngx_uint_t settings_sent; - /* unsigned settings_sent:1; */ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; } ngx_http_v3_connection_t; -ngx_int_t ngx_http_v3_init_connection(ngx_connection_t *c); - -ngx_int_t ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b); -ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, - ngx_uint_t allow_underscores); +void ngx_http_v3_init(ngx_connection_t *c); ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_http_chunked_t *ctx); @@ -157,6 +151,8 @@ uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index); uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, size_t len); +ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); +void ngx_http_v3_init_uni_stream(ngx_connection_t *c); ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4597bc180..09c1ec335 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,8 +10,13 @@ #include +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_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); static const struct { @@ -37,230 +42,256 @@ static const struct { }; -ngx_int_t -ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) +void +ngx_http_v3_init(ngx_connection_t *c) { - size_t len; - u_char *p; - ngx_int_t rc, n; - ngx_str_t *name, *value; - ngx_connection_t *c; - ngx_http_v3_parse_headers_t *st; - - c = r->connection; - st = r->h3_parse; - - if (st == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header"); - - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); - if (st == NULL) { - goto failed; - } - - r->h3_parse = st; - r->parse_start = b->pos; - r->state = 1; + size_t size; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + ngx_http_close_connection(c); + return; } - while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos); - - if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request headers"); - goto failed; - } - - if (rc == NGX_ERROR) { - goto failed; - } + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + return; + } - if (rc == NGX_BUSY) { - return NGX_BUSY; - } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); - b->pos++; + hc = c->data; - if (rc == NGX_AGAIN) { - continue; - } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - name = &st->header_rep.header.name; - value = &st->header_rep.header.value; + size = cscf->client_header_buffer_size; - n = ngx_http_v3_process_pseudo_header(r, name, value); + b = c->buffer; - if (n == NGX_ERROR) { - goto failed; + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_close_connection(c); + return; } - if (n == NGX_OK && rc == NGX_OK) { - continue; - } + c->buffer = b; - len = r->method_name.len + 1 - + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3.0") - 1; + } else if (b->start == NULL) { - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - goto failed; + b->start = ngx_palloc(c->pool, size); + if (b->start == NULL) { + ngx_http_close_connection(c); + return; } - r->request_start = p; - - p = ngx_cpymem(p, r->method_name.data, r->method_name.len); - r->method_end = p - 1; - *p++ = ' '; - p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); - *p++ = ' '; - r->http_protocol.data = p; - p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + b->pos = b->start; + b->last = b->start; + b->end = b->last + size; + } - r->request_end = p; - r->state = 0; + c->log->action = "reading client request"; - return NGX_OK; + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return; } - return NGX_AGAIN; + r->http_version = NGX_HTTP_VERSION_30; -failed: + c->data = r; - return NGX_HTTP_PARSE_INVALID_REQUEST; + rev = c->read; + rev->handler = ngx_http_v3_process_request; + + ngx_http_v3_process_request(rev); } -ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, - ngx_uint_t allow_underscores) +static void +ngx_http_v3_process_request(ngx_event_t *rev) { - u_char ch; + ssize_t n; + ngx_buf_t *b; ngx_int_t rc; - ngx_str_t *name, *value; - ngx_uint_t hash, i, n; ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_core_srv_conf_t *cscf; ngx_http_v3_parse_headers_t *st; - enum { - sw_start = 0, - sw_done, - sw_next, - sw_header - }; - c = r->connection; - st = r->h3_parse; + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); - switch (r->state) { + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } - case sw_start: - r->parse_start = b->pos; + st = r->h3_parse; - if (st->state) { - r->state = sw_next; - goto done; + if (st == NULL) { + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); + if (st == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; } - name = &st->header_rep.header.name; + r->h3_parse = st; + } - if (name->len && name->data[0] != ':') { - r->state = sw_done; - goto done; - } + b = r->header_in; - /* fall through */ + for ( ;; ) { - case sw_done: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header done"); - return NGX_HTTP_PARSE_HEADER_DONE; + if (b->pos == b->last) { - case sw_next: - r->parse_start = b->pos; - r->invalid_header = 0; - break; + if (!rev->ready) { + break; + } - case sw_header: - break; - } + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } - while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + c->log->action = "reading client request"; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + b->pos = b->start; + b->last = b->start + n; + } + + rc = ngx_http_v3_parse_headers(c, st, *b->pos); if (rc > 0) { ngx_http_v3_finalize_connection(c, rc, "could not parse request headers"); - return NGX_HTTP_PARSE_INVALID_HEADER; + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; } if (rc == NGX_ERROR) { - return NGX_HTTP_PARSE_INVALID_HEADER; + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; } - if (rc == NGX_DONE) { - r->state = sw_done; - goto done; + if (rc == NGX_BUSY) { + if (rev->error) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; } - if (rc == NGX_OK) { - r->state = sw_next; - goto done; + b->pos++; + r->request_length++; + + if (rc == NGX_AGAIN) { + continue; } - } - r->state = sw_header; - return NGX_AGAIN; + /* rc == NGX_OK || rc == NGX_DONE */ -done: + if (ngx_http_v3_process_header(r, &st->header_rep.header.name, + &st->header_rep.header.value) + != NGX_OK) + { + break; + } - name = &st->header_rep.header.name; - value = &st->header_rep.header.value; + if (rc == NGX_DONE) { + if (ngx_http_v3_process_request_header(r) != NGX_OK) { + break; + } - r->header_name_start = name->data; - r->header_name_end = name->data + name->len; - r->header_start = value->data; - r->header_end = value->data + value->len; + ngx_http_process_request(r); + break; + } + } - hash = 0; - i = 0; + ngx_http_run_posted_requests(c); - for (n = 0; n < name->len; n++) { - ch = name->data[n]; + return; +} - if (ch >= 'A' && ch <= 'Z') { - /* - * A request or response containing uppercase - * header field names MUST be treated as malformed - */ - return NGX_HTTP_PARSE_INVALID_HEADER; - } - if (ch == '\0') { - return NGX_HTTP_PARSE_INVALID_HEADER; - } +static ngx_int_t +ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; - if (ch == '_' && !allow_underscores) { - r->invalid_header = 1; - continue; - } + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } - if ((ch < 'a' || ch > 'z') - && (ch < '0' || ch > '9') - && ch != '-' && ch != '_') - { - r->invalid_header = 1; - continue; - } + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } - hash = ngx_hash(hash, ch); - r->lowcase_header[i++] = ch; - i &= (NGX_HTTP_LC_HEADER_LEN - 1); + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } - r->header_hash = hash; - r->lowcase_index = i; + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); return NGX_OK; } @@ -269,75 +300,210 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { - ngx_uint_t i; - ngx_connection_t *c; + ngx_uint_t i; - if (name->len == 0 || name->data[0] != ':') { - return NGX_DONE; + if (r->request_line.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent out of order pseudo-headers"); + goto failed; } - c = r->connection; - if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) / sizeof(ngx_http_v3_methods[0]); i++) { if (value->len == ngx_http_v3_methods[i].name.len - && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data, - value->len) == 0) + && ngx_strncmp(value->data, + ngx_http_v3_methods[i].name.data, value->len) + == 0) { r->method = ngx_http_v3_methods[i].method; break; } } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 method \"%V\" %ui", value, r->method); return NGX_OK; } if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + r->uri_start = value->data; r->uri_end = value->data + value->len; if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid :path header: \"%V\"", value); - return NGX_ERROR; + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":path\" header: \"%V\"", + value); + goto failed; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 path \"%V\"", value); - return NGX_OK; } if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { - r->schema_start = value->data; - r->schema_end = value->data + value->len; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 schema \"%V\"", value); + r->schema = *value; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 schema \"%V\"", value); return NGX_OK; } if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + r->host_start = value->data; r->host_end = value->data + value->len; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 authority \"%V\"", value); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \"%V\"", name); + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + +static ngx_int_t +ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) +{ + size_t len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + + if (r->request_line.len) { return NGX_OK; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 unknown pseudo header \"%V\" \"%V\"", name, value); + len = r->method_name.len + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3.0") - 1; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + + r->request_line.len = p - r->request_line.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->host_end) { + + host.len = r->host_end - r->host_start; + host.data = r->host_start; + + rc = ngx_http_validate_host(&host, r->pool, 0); + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid host in request line"); + goto failed; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { + return NGX_ERROR; + } + + r->headers_in.server = host; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_process_request_header(ngx_http_request_t *r) +{ + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent neither \":authority\" nor \"Host\" header"); + goto failed; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent \":authority\" and \"Host\" headers " + "with different values"); + goto failed; + } + } + + if (r->headers_in.content_length) { + r->headers_in.content_length_n = + ngx_atoof(r->headers_in.content_length->value.data, + r->headers_in.content_length->value.len); + + if (r->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \"Content-Length\" header"); + goto failed; + } + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 8ac048715..871914065 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -40,44 +40,49 @@ static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); ngx_int_t -ngx_http_v3_init_connection(ngx_connection_t *c) +ngx_http_v3_init_session(ngx_connection_t *c) { - ngx_http_connection_t *hc; - ngx_http_v3_uni_stream_t *us; + ngx_connection_t *pc; + ngx_http_connection_t *phc; ngx_http_v3_connection_t *h3c; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init connection"); - - hc = c->data; - - if (c->quic == NULL) { - h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_connection_t)); - if (h3c == NULL) { - return NGX_ERROR; - } + pc = c->quic->parent; + phc = pc->data; - h3c->hc = *hc; - - ngx_queue_init(&h3c->blocked); - ngx_queue_init(&h3c->pushing); - - c->data = h3c; + if (phc->http3) { return NGX_OK; } - if (ngx_http_v3_send_settings(c) == NGX_ERROR) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); + + h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_connection_t)); + if (h3c == NULL) { return NGX_ERROR; } - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - return NGX_OK; - } + h3c->hc = *phc; + h3c->hc.http3 = 1; + + ngx_queue_init(&h3c->blocked); + ngx_queue_init(&h3c->pushing); + + pc->data = h3c; + + return ngx_http_v3_send_settings(c); +} + + +void +ngx_http_v3_init_uni_stream(ngx_connection_t *c) +{ + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - NULL); - return NGX_ERROR; + ngx_http_close_connection(c); + return; } us->index = -1; @@ -88,8 +93,6 @@ ngx_http_v3_init_connection(ngx_connection_t *c) c->write->handler = ngx_http_v3_dummy_write_handler; ngx_http_v3_read_uni_stream_type(c->read); - - return NGX_DONE; } @@ -478,10 +481,6 @@ ngx_http_v3_send_settings(ngx_connection_t *c) h3c = c->quic->parent->data; - if (h3c->settings_sent) { - return NGX_OK; - } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); @@ -512,17 +511,12 @@ ngx_http_v3_send_settings(ngx_connection_t *c) goto failed; } - h3c->settings_sent = 1; - return NGX_OK; failed: ngx_http_v3_close_uni_stream(cc); - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "could not send settings"); - return NGX_ERROR; } -- cgit v1.2.3 From 4e312daa7ec04e52cdefcbc8749ef2f6d366064b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 22 Jan 2021 15:57:41 +0300 Subject: HTTP/3: client pseudo-headers restrictions. - :method, :path and :scheme are expected exactly once and not empty - :method and :scheme character validation is added - :authority cannot appear more than once --- src/http/v3/ngx_http_v3_request.c | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 09c1ec335..59a8889bf 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -300,6 +300,7 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { + u_char ch, c; ngx_uint_t i; if (r->request_line.len) { @@ -310,6 +311,18 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":method\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":method\" header"); + goto failed; + } + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) @@ -325,6 +338,16 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, } } + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", value); + goto failed; + } + } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 method \"%V\" %ui", value, r->method); return NGX_OK; @@ -332,6 +355,18 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + if (r->uri_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":path\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":path\" header"); + goto failed; + } + r->uri_start = value->data; r->uri_end = value->data + value->len; @@ -349,6 +384,39 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":scheme\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":scheme\" header"); + goto failed; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') + || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":scheme\" header: \"%V\"", + value); + goto failed; + } + r->schema = *value; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -358,6 +426,12 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + if (r->host_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":authority\" header"); + goto failed; + } + r->host_start = value->data; r->host_end = value->data + value->len; @@ -388,6 +462,24 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) return NGX_OK; } + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":method\" header"); + goto failed; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":scheme\" header"); + goto failed; + } + + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } + len = r->method_name.len + 1 + (r->uri_end - r->uri_start) + 1 + sizeof("HTTP/3.0") - 1; -- cgit v1.2.3 From 88f6b969e6963d5771b708adeb43d1c8de14db6e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 12 Jan 2021 21:08:55 +0000 Subject: HTTP/3: added comment. --- src/http/v3/ngx_http_v3.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 9f91ff8f1..0bb2c414b 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -116,6 +116,7 @@ typedef struct { typedef struct { + /* the http connection must be first */ ngx_http_connection_t hc; ngx_http_v3_dynamic_table_t table; -- cgit v1.2.3 From 7bac596afb31344cf40c93d8ae1ce87d2b6c76c1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Jan 2021 13:43:36 +0300 Subject: HTTP/3: client header validation. A header with the name containing null, CR, LF, colon or uppercase characters, is now considered an error. A header with the value containing null, CR or LF, is also considered an error. Also, header is considered invalid unless its name only contains lowercase characters, digits, minus and optionally underscore. Such header can be optionally ignored. --- src/http/v3/ngx_http_v3_request.c | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 59a8889bf..fb1626718 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -13,6 +13,8 @@ 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_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, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); @@ -260,8 +262,25 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, { ngx_table_elt_t *h; ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", name); + + return NGX_OK; + } + } + if (name->len && name->data[0] == ':') { return ngx_http_v3_process_pseudo_header(r, name, value); } @@ -296,6 +315,57 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) -- cgit v1.2.3 From 52d0bf620a964bbb51bd7ff87e778ba72164b802 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 21 Dec 2020 17:35:13 +0000 Subject: HTTP/3: removed HTTP/3-specific code. The ngx_http_set_lingering_close() function is not called for HTTP/3. The change reduces diff to the default branch. --- src/http/ngx_http_request.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index abcc1bba8..b747b0206 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3451,13 +3451,11 @@ ngx_http_set_lingering_close(ngx_connection_t *c) } } - if (c->fd != NGX_INVALID_FILE) { - if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { - ngx_connection_error(c, ngx_socket_errno, - ngx_shutdown_socket_n " failed"); - ngx_http_close_request(r, 0); - return; - } + if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); + ngx_http_close_request(r, 0); + return; } ngx_add_timer(rev, clcf->lingering_timeout); -- cgit v1.2.3 From 68aa6fec77b06972a0a91223f08551569ecb5355 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 27 Jan 2021 13:09:45 +0300 Subject: README: reflect renaming of several transport parameter directives. Reported by Kyriakos Zarifis. --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 40ee3907f..5733c75ca 100644 --- a/README +++ b/README @@ -108,7 +108,7 @@ Experimental QUIC support for nginx quic_max_idle_timeout quic_max_ack_delay - quic_max_packet_size + quic_max_udp_payload_size quic_initial_max_data quic_initial_max_stream_data_bidi_local quic_initial_max_stream_data_bidi_remote @@ -116,7 +116,7 @@ Experimental QUIC support for nginx quic_initial_max_streams_bidi quic_initial_max_streams_uni quic_ack_delay_exponent - quic_active_migration + quic_disable_active_migration quic_active_connection_id_limit To enable address validation: -- cgit v1.2.3 From cd6253430051a823dc31b756e93aeecb5f674af3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 29 Jan 2021 19:42:47 +0300 Subject: HTTP/3: call ngx_handle_read_event() from client header handler. This function should be called at the end of an event handler to prepare the event for the next handler call. Particularly, the "active" flag is set or cleared depending on data availability. With this call missing in one code path, read handler was not called again after handling the initial part of the client request, if the request was too big to fit into a single STREAM frame. Now ngx_handle_read_event() is called in this code path. Also, read timer is restarted. --- src/http/v3/ngx_http_v3_request.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fb1626718..0b7954137 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -158,11 +158,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) if (b->pos == b->last) { - if (!rev->ready) { - break; - } + if (rev->ready) { + n = c->recv(c, b->start, b->end - b->start); - n = c->recv(c, b->start, b->end - b->start); + } else { + n = NGX_AGAIN; + } if (n == NGX_AGAIN) { if (!rev->timer_set) { -- cgit v1.2.3 From 89dda20510bf7dac952a2dc0b5f29deba376e25f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 29 Jan 2021 15:53:47 +0300 Subject: QUIC: stateless retry. Previously, quic connection object was created when Retry packet was sent. This is neither necessary nor convenient, and contradicts the idea of retry: protecting from bad clients and saving server resources. Now, the connection is not created, token is verified cryptographically instead of holding it in connection. --- src/event/quic/ngx_event_quic.c | 539 +++++++++++++++++------------- src/event/quic/ngx_event_quic.h | 12 +- src/event/quic/ngx_event_quic_transport.h | 2 + src/http/modules/ngx_http_quic_module.c | 6 +- src/stream/ngx_stream_quic_module.c | 6 +- 5 files changed, 319 insertions(+), 246 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 4da52a908..52397bcf5 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -9,6 +9,7 @@ #include #include #include +#include /* 0-RTT and 1-RTT data exist in the same packet number space, @@ -113,7 +114,6 @@ typedef struct { ngx_str_t scid; /* initial client ID */ ngx_str_t dcid; /* server (our own) ID */ ngx_str_t odcid; /* original server ID */ - ngx_str_t token; struct sockaddr *sockaddr; socklen_t socklen; @@ -175,8 +175,6 @@ typedef struct { unsigned closing:1; unsigned draining:1; unsigned key_phase:1; - unsigned in_retry:1; - unsigned initialized:1; unsigned validated:1; } ngx_quic_connection_t; @@ -235,10 +233,14 @@ static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); #if (NGX_QUIC_BPF) static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); #endif -static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c); -static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token); +static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, + ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); +static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, + u_char buf[20]); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, - ngx_quic_header_t *pkt); + u_char *key, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -253,7 +255,8 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf); static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_init_secrets(ngx_connection_t *c); +static ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, + ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, @@ -673,7 +676,6 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) 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" : ""); - p = ngx_slprintf(p, last, "%s", qc->in_retry ? " retry" : ""); p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); } else { @@ -1014,12 +1016,16 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) qc = ngx_quic_get_connection(c); - ngx_add_timer(c->read, qc->in_retry ? NGX_QUIC_RETRY_TIMEOUT - : qc->tp.max_idle_timeout); + if (qc == NULL) { + ngx_quic_close_connection(c, NGX_DONE); + return; + } + + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + ngx_quic_connstate_dbg(c); c->read->handler = ngx_quic_input_handler; - ngx_quic_connstate_dbg(c); return; } @@ -1123,8 +1129,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->congestion.ssthresh = (size_t) -1; qc->congestion.recovery_start = ngx_current_msec; - qc->odcid.len = pkt->dcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); + qc->odcid.len = pkt->odcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); if (qc->odcid.data == NULL) { return NULL; } @@ -1144,12 +1150,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, #endif qc->tp.initial_scid = qc->dcid; + 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->scid.len = pkt->scid.len; - qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid); if (qc->scid.data == NULL) { return NULL; } - ngx_memcpy(qc->scid.data, pkt->scid.data, qc->scid.len); cid = ngx_quic_alloc_client_id(c, qc); if (cid == NULL) { @@ -1166,6 +1179,26 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->server_seqnum = NGX_QUIC_UNSET_PN; + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid) + != NGX_OK) + { + return NULL; + } + + c->udp = &qc->udp; + + if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { + return NULL; + } + + qc->server_seqnum = 0; + + if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + return NULL; + } + + qc->validated = pkt->validated; + return qc; } @@ -1344,27 +1377,41 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) static ngx_int_t -ngx_quic_send_retry(ngx_connection_t *c) +ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *inpkt) { - ssize_t len; - ngx_str_t res, token; - ngx_quic_header_t pkt; - ngx_quic_connection_t *qc; - u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + time_t expires; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; - qc = ngx_quic_get_connection(c); + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + u_char dcid[NGX_QUIC_SERVER_CID_LEN]; - if (ngx_quic_new_token(c, &token) != NGX_OK) { + expires = ngx_time() + NGX_QUIC_RETRY_LIFETIME; + + if (ngx_quic_new_token(c, conf->token_key, &token, &inpkt->dcid, expires, 1) + != NGX_OK) + { return NGX_ERROR; } ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; - pkt.version = qc->version; + pkt.version = inpkt->version; pkt.log = c->log; - pkt.odcid = qc->odcid; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; + + pkt.odcid = inpkt->dcid; + pkt.dcid = inpkt->scid; + + /* TODO: generate routable dcid */ + if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; + pkt.scid.data = dcid; + pkt.token = token; res.data = buf; @@ -1383,71 +1430,46 @@ ngx_quic_send_retry(ngx_connection_t *c) return NGX_ERROR; } - qc->token = token; -#if (NGX_QUIC_DRAFT_VERSION < 28) - qc->tp.original_dcid = qc->odcid; -#endif - qc->tp.retry_scid = qc->dcid; - qc->in_retry = 1; - - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { - return NGX_ERROR; - } + ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); - return NGX_OK; + /* + * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry + * packet in response to a single UDP datagram. + * NGX_DONE will stop quic_input() from processing further + */ + return NGX_DONE; } static ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) +ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, + ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) { - int len, iv_len; - u_char *data, *p, *key, *iv; - ngx_msec_t now; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - struct sockaddr_in *sin; -#if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; -#endif - ngx_quic_connection_t *qc; - u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - break; -#endif + ngx_quic_address_hash(c, !is_retry, in); -#if (NGX_HAVE_UNIX_DOMAIN) - case AF_UNIX: + p = in + 20; - len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(now)); - data = c->addr_text.data; + p = ngx_cpymem(p, &exp, sizeof(time_t)); - break; -#endif + *p++ = is_retry ? 1 : 0; - default: /* AF_INET */ - sin = (struct sockaddr_in *) c->sockaddr; + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; - - break; + } else { + *p++ = 0; } - p = ngx_cpymem(in, data, len); - - now = ngx_current_msec; - len += sizeof(now); - ngx_memcpy(p, &now, sizeof(now)); + len = p - in; cipher = EVP_aes_256_cbc(); iv_len = EVP_CIPHER_iv_length(cipher); @@ -1463,8 +1485,6 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) return NGX_ERROR; } - qc = ngx_quic_get_connection(c); - key = qc->conf->token_key; iv = token->data; if (RAND_bytes(iv, iv_len) <= 0 @@ -1501,52 +1521,78 @@ ngx_quic_new_token(ngx_connection_t *c, ngx_str_t *token) } -static ngx_int_t -ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) +static void +ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) { - int len, tlen, iv_len; - u_char *key, *iv, *p, *data; - ngx_msec_t msec; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - struct sockaddr_in *sin; + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; #if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; + struct sockaddr_in6 *sin6; #endif - ngx_quic_connection_t *qc; - u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - qc = ngx_quic_get_connection(c); + len = (size_t) c->socklen; + data = (u_char *) c->sockaddr; - /* Retry token */ + if (no_port) { + switch (c->sockaddr->sa_family) { - if (qc->token.len) { - if (pkt->token.len != qc->token.len) { - goto bad_token; - } +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; - if (ngx_memcmp(pkt->token.data, qc->token.data, pkt->token.len) != 0) { - goto bad_token; - } + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; - return NGX_OK; + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } } - /* NEW_TOKEN in a previous connection */ + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +static ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + + /* Retry token or NEW_TOKEN in a previous connection */ cipher = EVP_aes_256_cbc(); - key = qc->conf->token_key; iv = pkt->token.data; iv_len = EVP_CIPHER_iv_length(cipher); /* sanity checks */ if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { - goto bad_token; + goto garbage; } if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { - goto bad_token; + goto garbage; } ctx = EVP_CIPHER_CTX_new(); @@ -1564,66 +1610,81 @@ ngx_quic_validate_token(ngx_connection_t *c, ngx_quic_header_t *pkt) if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { EVP_CIPHER_CTX_free(ctx); - goto bad_token; + goto garbage; } + total = len; if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { EVP_CIPHER_CTX_free(ctx); - goto bad_token; + goto garbage; } + total += tlen; EVP_CIPHER_CTX_free(ctx); - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; + p = tdec + 20; - break; -#endif + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); -#if (NGX_HAVE_UNIX_DOMAIN) - case AF_UNIX: + pkt->retried = (*p++ == 1); - len = ngx_min(c->addr_text.len, NGX_QUIC_MAX_TOKEN_SIZE - sizeof(msec)); - data = c->addr_text.data; - - break; -#endif + ngx_quic_address_hash(c, !pkt->retried, addr_hash); - default: /* AF_INET */ - sin = (struct sockaddr_in *) c->sockaddr; + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } - break; - } + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } - if (ngx_memcmp(tdec, data, len) != 0) { - goto bad_token; + odcid.data = p; + p += odcid.len; } - ngx_memcpy(&msec, tdec + len, sizeof(msec)); + now = ngx_time(); - if (ngx_current_msec - msec > NGX_QUIC_RETRY_LIFETIME) { + if (now > exp) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); return NGX_DECLINED; } + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); + if (pkt->odcid.data == NULL) { + return NGX_ERROR; + } + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + return NGX_OK; +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + bad_token: ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - qc->error = NGX_QUIC_ERR_INVALID_TOKEN; - qc->error_reason = "invalid_token"; - return NGX_DECLINED; } @@ -1817,8 +1878,10 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) qc = ngx_quic_get_connection(c); if (qc == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close connection early error"); + if (rc == NGX_ERROR) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close connection early error"); + } } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { return; @@ -2101,6 +2164,11 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) return NGX_ERROR; } + if (rc == NGX_DONE) { + /* stop further processing */ + return NGX_DECLINED; + } + if (rc == NGX_OK) { good = 1; } @@ -2216,60 +2284,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } - if (qc->in_retry) { - - c->log->action = "retrying quic connection"; - - if (pkt->level != ssl_encryption_initial) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic discard late retry packet"); - return NGX_DECLINED; - } - - if (!pkt->token.len) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic discard retry packet without token"); - return NGX_DECLINED; - } - - qc->odcid.len = pkt->dcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->dcid); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } - - ngx_quic_clear_temp_server_ids(c); - - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { - return NGX_ERROR; - } - - qc->server_seqnum = 0; - - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { - return NGX_ERROR; - } - - qc->tp.initial_scid = qc->dcid; - qc->in_retry = 0; - - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_validate_token(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - - qc->validated = 1; - } - } else { if (rc == NGX_ABORT) { @@ -2277,8 +2291,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->level == ssl_encryption_initial) { - - c->log->action = "creating quic connection"; + c->log->action = "processing initial packet"; if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { /* 7.2. Negotiating Connection IDs */ @@ -2288,49 +2301,56 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - qc = ngx_quic_new_connection(c, conf, pkt); - if (qc == NULL) { - return NGX_ERROR; - } - - c->udp = &qc->udp; - - if (ngx_terminate || ngx_exiting) { - qc->error = NGX_QUIC_ERR_CONNECTION_REFUSED; - return NGX_ERROR; - } + /* process retry and initialize connection IDs */ if (pkt->token.len) { - rc = ngx_quic_validate_token(c, pkt); - if (rc == NGX_OK) { - qc->validated = 1; + rc = ngx_quic_validate_token(c, conf->token_key, pkt); - } else if (rc == NGX_ERROR) { + if (rc == NGX_ERROR) { + /* internal error */ return NGX_ERROR; - } else { - /* NGX_DECLINED */ - if (conf->retry) { - return ngx_quic_send_retry(c); + } else if (rc == NGX_ABORT) { + /* token cannot be decrypted */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "cannot decrypt token"); + } else if (rc == NGX_DECLINED) { + /* token is invalid */ + + if (pkt->retried) { + /* invalid Retry token */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "invalid token"); + } else if (conf->retry) { + /* invalid NEW_TOKEN */ + return ngx_quic_send_retry(c, conf, pkt); } } + /* NGX_OK */ + } else if (conf->retry) { - return ngx_quic_send_retry(c); - } + return ngx_quic_send_retry(c, conf, pkt); - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_ERROR; + } else { + pkt->odcid = pkt->dcid; } - if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { + if (ngx_terminate || ngx_exiting) { + if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + } + return NGX_ERROR; } - qc->server_seqnum = 0; + c->log->action = "creating quic connection"; - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { return NGX_ERROR; } @@ -2411,19 +2431,76 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, static ngx_int_t -ngx_quic_init_secrets(ngx_connection_t *c) +ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, + ngx_uint_t err, const char *reason) { - ngx_quic_connection_t *qc; + ssize_t len; + ngx_str_t res; + ngx_quic_frame_t frame; + ngx_quic_header_t pkt; - qc = ngx_quic_get_connection(c); + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + frame.level = inpkt->level; + frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = err; + + frame.u.close.reason.data = (u_char *) reason; + frame.u.close.reason.len = ngx_strlen(reason); + + len = ngx_quic_create_frame(NULL, &frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid) + ngx_quic_log_frame(c->log, &frame, 1); + + len = ngx_quic_create_frame(src, &frame); + if (len == -1) { + return NGX_ERROR; + } + + pkt.keys = ngx_quic_keys_new(c->pool); + if (pkt.keys == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid) != NGX_OK) { return NGX_ERROR; } - qc->initialized = 1; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG + | NGX_QUIC_PKT_INITIAL; + + pkt.num_len = 1; + /* + * pkt.num = 0; + * pkt.trunc = 0; + */ + + pkt.version = inpkt->version; + pkt.log = c->log; + pkt.level = inpkt->level; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) { + return NGX_ERROR; + } return NGX_OK; } @@ -3156,13 +3233,6 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } - if (!qc->initialized) { - /* try to initialize secrets to send an early error */ - if (ngx_quic_init_secrets(c) != NGX_OK) { - return NGX_OK; - } - } - if (qc->closing && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) { @@ -3197,6 +3267,7 @@ ngx_quic_send_cc(ngx_connection_t *c) static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c) { + time_t expires; ngx_str_t token; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -3207,7 +3278,11 @@ ngx_quic_send_new_token(ngx_connection_t *c) return NGX_OK; } - if (ngx_quic_new_token(c, &token) != NGX_OK) { + expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; + + if (ngx_quic_new_token(c, qc->conf->token_key, &token, NULL, expires, 0) + != NGX_OK) + { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 59578feea..0c94707a5 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -29,12 +29,12 @@ #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 #define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 -#define NGX_QUIC_RETRY_TIMEOUT 3000 -#define NGX_QUIC_RETRY_LIFETIME 30000 -#define NGX_QUIC_RETRY_BUFFER_SIZE 128 - /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(44) */ -#define NGX_QUIC_MAX_TOKEN_SIZE 32 - /* sizeof(struct in6_addr) + sizeof(ngx_msec_t) up to AES-256 block size */ +#define NGX_QUIC_RETRY_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ /* quic-recovery, section 6.2.2, kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 1a3fe7766..7f82d949e 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -325,6 +325,8 @@ typedef struct { unsigned key_update:1; unsigned parsed:1; unsigned decrypted:1; + unsigned validated:1; + unsigned retried:1; } ngx_quic_header_t; diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 5314af35b..901d1a563 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -346,10 +346,8 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (conf->retry) { - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { - return NGX_CONF_ERROR; - } + if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + return NGX_CONF_ERROR; } ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index eaaaba89a..e6466bba4 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -305,10 +305,8 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (conf->retry) { - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { - return NGX_CONF_ERROR; - } + if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + return NGX_CONF_ERROR; } ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); -- cgit v1.2.3 From fef33604662c4722a2bf0d682f4a0526eb20a692 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 28 Jan 2021 12:35:18 +0300 Subject: QUIC: refactored packet processing. - split ngx_quic_process_packet() in two functions with the second one called ngx_quic_process_payload() in charge of decrypring and handling the payload - renamed ngx_quic_payload_handler() to ngx_quic_handle_frames() - moved error cleanup from ngx_quic_input() to ngx_quic_process_payload() - moved handling closed connection from ngx_quic_handle_frames() to ngx_quic_process_payload() - minor fixes --- src/event/quic/ngx_event_quic.c | 203 +++++++++++++++++++++------------------- 1 file changed, 107 insertions(+), 96 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 52397bcf5..742950b6c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -255,13 +255,15 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf); static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_process_payload(ngx_connection_t *c, + ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_payload_handler(ngx_connection_t *c, +static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt); @@ -2120,11 +2122,10 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { - u_char *p; - ngx_int_t rc; - ngx_uint_t good; - ngx_quic_header_t pkt; - ngx_quic_connection_t *qc; + u_char *p; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_header_t pkt; good = 0; @@ -2140,12 +2141,6 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) pkt.flags = p[0]; pkt.raw->pos++; - qc = ngx_quic_get_connection(c); - if (qc) { - qc->error = 0; - qc->error_reason = 0; - } - rc = ngx_quic_process_packet(c, conf, &pkt); #if (NGX_DEBUG) @@ -2212,11 +2207,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - c->log->action = "parsing quic packet"; rc = ngx_quic_parse_packet(pkt); @@ -2229,8 +2221,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, c->log->action = "processing quic packet"; - qc = ngx_quic_get_connection(c); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet rx dcid len:%uz %xV", pkt->dcid.len, &pkt->dcid); @@ -2249,6 +2239,8 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } #endif + qc = ngx_quic_get_connection(c); + if (qc) { if (rc == NGX_ABORT) { @@ -2284,84 +2276,103 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } - } else { + return ngx_quic_process_payload(c, pkt); + } - if (rc == NGX_ABORT) { - return ngx_quic_negotiate_version(c, pkt); - } + /* packet does not belong to a connection */ - if (pkt->level == ssl_encryption_initial) { - c->log->action = "processing initial packet"; + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } - if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { - /* 7.2. Negotiating Connection IDs */ - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic too short dcid in initial" - " packet: len:%i", pkt->dcid.len); - return NGX_ERROR; - } + if (pkt->level == ssl_encryption_application) { + return ngx_quic_send_stateless_reset(c, conf, pkt); + } - /* process retry and initialize connection IDs */ + if (pkt->level != ssl_encryption_initial) { + return NGX_ERROR; + } - if (pkt->token.len) { + c->log->action = "processing initial packet"; - rc = ngx_quic_validate_token(c, conf->token_key, pkt); + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: len:%i", pkt->dcid.len); + return NGX_ERROR; + } - if (rc == NGX_ERROR) { - /* internal error */ - return NGX_ERROR; + /* process retry and initialize connection IDs */ - } else if (rc == NGX_ABORT) { - /* token cannot be decrypted */ - return ngx_quic_send_early_cc(c, pkt, - NGX_QUIC_ERR_INVALID_TOKEN, - "cannot decrypt token"); - } else if (rc == NGX_DECLINED) { - /* token is invalid */ + if (pkt->token.len) { - if (pkt->retried) { - /* invalid Retry token */ - return ngx_quic_send_early_cc(c, pkt, - NGX_QUIC_ERR_INVALID_TOKEN, - "invalid token"); - } else if (conf->retry) { - /* invalid NEW_TOKEN */ - return ngx_quic_send_retry(c, conf, pkt); - } - } + rc = ngx_quic_validate_token(c, conf->token_key, pkt); - /* NGX_OK */ + if (rc == NGX_ERROR) { + /* internal error */ + return NGX_ERROR; + } else if (rc == NGX_ABORT) { + /* token cannot be decrypted */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "cannot decrypt token"); + } else if (rc == NGX_DECLINED) { + /* token is invalid */ + + if (pkt->retried) { + /* invalid Retry token */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "invalid token"); } else if (conf->retry) { + /* invalid NEW_TOKEN */ return ngx_quic_send_retry(c, conf, pkt); - - } else { - pkt->odcid = pkt->dcid; } + } - if (ngx_terminate || ngx_exiting) { - if (conf->retry) { - return ngx_quic_send_retry(c, conf, pkt); - } + /* NGX_OK */ - return NGX_ERROR; - } + } else if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); - c->log->action = "creating quic connection"; + } else { + pkt->odcid = pkt->dcid; + } - qc = ngx_quic_new_connection(c, conf, pkt); - if (qc == NULL) { - return NGX_ERROR; - } + if (ngx_terminate || ngx_exiting) { + if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + } - } else if (pkt->level == ssl_encryption_application) { - return ngx_quic_send_stateless_reset(c, conf, pkt); + return NGX_ERROR; + } - } else { - return NGX_ERROR; - } + c->log->action = "creating quic connection"; + + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; } + return ngx_quic_process_payload(c, pkt); +} + + +static ngx_int_t +ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + qc->error = 0; + qc->error_reason = 0; + c->log->action = "decrypting packet"; if (!ngx_quic_keys_available(qc->keys, pkt->level)) { @@ -2404,16 +2415,35 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } } + if (qc->closing) { + /* + * 10.1 Closing and Draining Connection States + * ... delayed or reordered packets are properly discarded. + * + * An endpoint retains only enough information to generate + * a packet containing a CONNECTION_CLOSE frame and to identify + * packets as belonging to the connection. + */ + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); + } + pkt->received = ngx_current_msec; c->log->action = "handling payload"; if (pkt->level != ssl_encryption_application) { - return ngx_quic_payload_handler(c, pkt); + return ngx_quic_handle_frames(c, pkt); } if (!pkt->key_update) { - return ngx_quic_payload_handler(c, pkt); + return ngx_quic_handle_frames(c, pkt); } /* switch keys and generate next on Key Phase change */ @@ -2421,7 +2451,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->key_phase ^= 1; ngx_quic_keys_switch(c, qc->keys); - rc = ngx_quic_payload_handler(c, pkt); + rc = ngx_quic_handle_frames(c, pkt); if (rc != NGX_OK) { return rc; } @@ -2581,7 +2611,7 @@ ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *end, *p; ssize_t len; @@ -2593,25 +2623,6 @@ ngx_quic_payload_handler(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); - if (qc->closing) { - /* - * 10.1 Closing and Draining Connection States - * ... delayed or reordered packets are properly discarded. - * - * An endpoint retains only enough information to generate - * a packet containing a CONNECTION_CLOSE frame and to identify - * packets as belonging to the connection. - */ - - qc->error_level = pkt->level; - qc->error = NGX_QUIC_ERR_NO_ERROR; - qc->error_reason = "connection is closing, packet discarded"; - qc->error_ftype = 0; - qc->error_app = 0; - - return ngx_quic_send_cc(c); - } - p = pkt->payload.data; end = p + pkt->payload.len; -- cgit v1.2.3 From a7cf99b10d29a60af561e59b4d2742f7ee34f4ca Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 1 Feb 2021 14:46:36 +0300 Subject: QUIC: fixed stateless reset recognition and send. Previously, if an unexpected packet was received on an existing QUIC connection, stateless reset token was neither recognized nor sent. --- src/event/quic/ngx_event_quic.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 742950b6c..6a156bb71 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -261,7 +261,7 @@ static ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level); -static ngx_int_t ngx_quic_check_peer(ngx_quic_connection_t *qc, +static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt); @@ -2250,30 +2250,28 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->level != ssl_encryption_application) { + if (pkt->version != qc->version) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic version mismatch: 0x%xD", pkt->version); return NGX_DECLINED; } - } - if (ngx_quic_check_peer(qc, pkt) != NGX_OK) { + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } - if (pkt->level == ssl_encryption_application) { - if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic stateless reset packet detected"); + } else { - qc->draining = 1; - ngx_quic_close_connection(c, NGX_OK); + if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); - return NGX_OK; - } + qc->draining = 1; + ngx_quic_close_connection(c, NGX_OK); - return ngx_quic_send_stateless_reset(c, qc->conf, pkt); + return NGX_OK; } - - return NGX_DECLINED; } return ngx_quic_process_payload(c, pkt); @@ -2583,15 +2581,11 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) static ngx_int_t -ngx_quic_check_peer(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { ngx_queue_t *q; ngx_quic_client_id_t *cid; - if (pkt->level == ssl_encryption_application) { - return NGX_OK; - } - for (q = ngx_queue_head(&qc->client_ids); q != ngx_queue_sentinel(&qc->client_ids); q = ngx_queue_next(q)) -- cgit v1.2.3 From 6f3c821d1f28c433f778fcc843bb764e45194f5c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 25 Jan 2021 16:16:47 +0300 Subject: HTTP/3: refactored request body parser. The change reduces diff to the default branch for src/http/ngx_http_request_body.c. Also, client Content-Length, if present, is now checked against the real body size sent by client. --- src/http/ngx_http.h | 3 - src/http/ngx_http_request_body.c | 64 ++--- src/http/v3/ngx_http_v3.h | 4 +- src/http/v3/ngx_http_v3_request.c | 503 ++++++++++++++++++++++++++++++++++---- 4 files changed, 488 insertions(+), 86 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 778744d71..a7cd51d53 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -66,9 +66,6 @@ struct ngx_http_chunked_s { ngx_uint_t state; off_t size; off_t length; -#if (NGX_HTTP_V3) - void *h3_parse; -#endif }; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index c0257a21e..05e990bfc 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -87,6 +87,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_request_body(r); + goto done; + } +#endif + preread = r->header_in->last - r->header_in->pos; if (preread) { @@ -229,6 +236,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_unbuffered_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; + } +#endif + if (r->connection->read->timedout) { r->connection->timedout = 1; return NGX_HTTP_REQUEST_TIME_OUT; @@ -333,10 +352,11 @@ ngx_http_do_read_client_request_body(ngx_http_request_t *r) } if (n == 0) { - rb->buf->last_buf = 1; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); } - if (n == NGX_ERROR) { + if (n == 0 || n == NGX_ERROR) { c->error = 1; return NGX_HTTP_BAD_REQUEST; } @@ -583,8 +603,8 @@ ngx_http_discard_request_body(ngx_http_request_t *r) } #endif -#if (NGX_HTTP_QUIC) - if (r->connection->quic) { +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { return NGX_OK; } #endif @@ -956,15 +976,6 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) break; } - size = cl->buf->last - cl->buf->pos; - - if (cl->buf->last_buf && (off_t) size < rb->rest) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client prematurely closed connection"); - r->connection->error = 1; - return NGX_HTTP_BAD_REQUEST; - } - tl = ngx_chain_get_free_buf(r->pool, &rb->free); if (tl == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -982,6 +993,8 @@ ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) b->end = cl->buf->end; b->flush = r->request_body_no_buffering; + size = cl->buf->last - cl->buf->pos; + if ((off_t) size < rb->rest) { cl->buf->pos = cl->buf->last; rb->rest -= size; @@ -1053,16 +1066,7 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->file_pos, cl->buf->file_last - cl->buf->file_pos); - switch (r->http_version) { -#if (NGX_HTTP_V3) - case NGX_HTTP_VERSION_30: - rc = ngx_http_v3_parse_request_body(r, cl->buf, rb->chunked); - break; -#endif - - default: /* HTTP/1.x */ - rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); - } + rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); if (rc == NGX_OK) { @@ -1146,20 +1150,6 @@ ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) continue; } - if (rc == NGX_AGAIN && cl->buf->last_buf) { - - /* last body buffer */ - - if (rb->chunked->length > 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client prematurely closed connection"); - r->connection->error = 1; - return NGX_HTTP_BAD_REQUEST; - } - - rc = NGX_DONE; - } - if (rc == NGX_DONE) { /* a whole response has been parsed successfully */ diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 0bb2c414b..510e66bb5 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -133,8 +133,8 @@ typedef struct { void ngx_http_v3_init(ngx_connection_t *c); -ngx_int_t ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, - ngx_http_chunked_t *ctx); +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); uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0b7954137..1c17efadb 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -19,6 +19,10 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); static const struct { @@ -625,12 +629,18 @@ failed: static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r) { + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + + c = r->connection; + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { return NGX_ERROR; } if (r->headers_in.server.len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent neither \":authority\" nor \"Host\" header"); goto failed; } @@ -642,7 +652,7 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) r->headers_in.server.len) != 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent \":authority\" and \"Host\" headers " "with different values"); goto failed; @@ -655,10 +665,32 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) r->headers_in.content_length->value.len); if (r->headers_in.content_length_n == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid \"Content-Length\" header"); goto failed; } + + } else { + b = r->header_in; + n = b->last - b->pos; + + if (n == 0) { + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (n > 0) { + b->pos = b->start; + b->last = b->start + n; + } + } + + if (n != 0) { + r->headers_in.chunked = 1; + } } return NGX_OK; @@ -671,74 +703,457 @@ failed: ngx_int_t -ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, - ngx_http_chunked_t *ctx) +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + size_t preread; + ngx_int_t rc; + ngx_chain_t *cl, out; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 client request body preread %uz", preread); + + out.buf = r->header_in; + out.next = NULL; + cl = &out; + + } else { + cl = NULL; + } + + rc = ngx_http_v3_request_body_filter(r, cl); + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + /* the whole request body was pre-read */ + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + if (rb->rest < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "negative request body rest"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return ngx_http_v3_do_read_client_request_body(r); +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) { + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; +} + + +static ngx_int_t +ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) +{ + off_t rest; + size_t size; + ssize_t n; ngx_int_t rc; + ngx_chain_t out; ngx_connection_t *c; - ngx_http_v3_parse_data_t *st; - enum { - sw_start = 0, - sw_skip - }; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; c = r->connection; - st = ctx->h3_parse; + rb = r->request_body; - if (st == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse request body"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); + + for ( ;; ) { + for ( ;; ) { + if (rb->buf->last == rb->buf->end) { + + /* update chains */ + + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + if (r->request_body_no_buffering) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + rest = rb->rest - (rb->buf->last - rb->buf->pos); + + if ((off_t) size > rest) { + size = (size_t) rest; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t)); + if (n == 0) { + rb->buf->last_buf = 1; + } + + if (n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + + /* pass buffer to request body filter chain */ + + out.buf = rb->buf; + out.next = NULL; + + rc = ngx_http_v3_request_body_filter(r, &out); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (rb->rest == 0) { + break; + } + + if (!c->read->ready) { + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (!r->request_body_no_buffering) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t max; + size_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t last; + ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_data_t *st; + + rb = r->request_body; + st = r->h3_parse; + + if (rb->rest == -1) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + st = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_data_t)); if (st == NULL) { - goto failed; + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ctx->h3_parse = st; + r->h3_parse = st; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = cscf->large_client_header_buffers.size; } - while (b->pos < b->last && ctx->size == 0) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - rc = ngx_http_v3_parse_data(c, st, *b->pos++); + max = r->headers_in.content_length_n; - if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request body"); - goto failed; + if (max == -1 && clcf->client_max_body_size) { + max = clcf->client_max_body_size; + } + + out = NULL; + ll = &out; + last = 0; + + for (cl = in; cl; cl = cl->next) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http3 body buf " + "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + + if (cl->buf->last_buf) { + last = 1; } - if (rc == NGX_ERROR) { - goto failed; + b = NULL; + + while (cl->buf->pos < cl->buf->last) { + + if (st->length == 0) { + r->request_length++; + + rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++); + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + last = 1; + goto done; + } + + if (rc > 0) { + ngx_http_v3_finalize_connection(r->connection, rc, + "client sent invalid body"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid body"); + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_ERROR) { + ngx_http_v3_finalize_connection(r->connection, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_OK */ + } + + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%uL bytes", + rb->received, st->length); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + if (b + && st->length <= 128 + && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) + { + rb->received += st->length; + r->request_length += st->length; + + if (st->length < 8) { + + while (st->length) { + *b->last++ = *cl->buf->pos++; + st->length--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, st->length); + b->last += st->length; + cl->buf->pos += st->length; + st->length = 0; + } + + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + *ll = tl; + ll = &tl->next; + + size = cl->buf->last - cl->buf->pos; + + if (size > st->length) { + cl->buf->pos += (size_t) st->length; + rb->received += st->length; + r->request_length += st->length; + st->length = 0; + + } else { + st->length -= size; + rb->received += size; + r->request_length += size; + cl->buf->pos = cl->buf->last; + } + + b->last = cl->buf->pos; } + } - if (rc == NGX_AGAIN) { - ctx->state = sw_skip; - continue; +done: + + if (last) { + + if (st->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; } - if (rc == NGX_DONE) { - return NGX_DONE; + if (r->headers_in.content_length_n == -1) { + r->headers_in.content_length_n = rb->received; + + } else if (r->headers_in.content_length_n != rb->received) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent less body data than expected: " + "%O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + return NGX_HTTP_BAD_REQUEST; } - /* rc == NGX_OK */ + rb->rest = 0; - ctx->size = st->length; - ctx->state = sw_start; - } + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } - if (ctx->state == sw_skip) { - ctx->length = 1; - return NGX_AGAIN; - } + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + ll = &tl->next; + + } else { - if (b->pos == b->last) { - ctx->length = ctx->size; - return NGX_AGAIN; + /* set rb->rest, amount of data we want to see next time */ + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = (off_t) cscf->large_client_header_buffers.size; } - return NGX_OK; + rc = ngx_http_top_request_body_filter(r, out); -failed: + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); - return NGX_ERROR; + return rc; } -- cgit v1.2.3 From a373d2851b33191e4f82cdec911914b04c4a4f23 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 1 Feb 2021 18:48:18 +0300 Subject: HTTP/3: fixed format specifier. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 1c17efadb..df58f383a 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1034,7 +1034,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) if (max != -1 && (uint64_t) (max - rb->received) < st->length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client intended to send too large " - "body: %O+%uL bytes", + "body: %O+%ui bytes", rb->received, st->length); return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; -- cgit v1.2.3 From 365c8b7914033c05fc1e564684dade448fc65671 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 2 Feb 2021 15:09:48 +0300 Subject: HTTP/3: reverted version check for keepalive flag. The flag is used in ngx_http_finalize_connection() to switch client connection to the keepalive mode. Since eaea7dac3292 this code is not executed for HTTP/3 which allows us to revert the change and get back to the default branch code. --- src/http/ngx_http_core_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index ef8b649ef..d4e1910bf 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -819,7 +819,7 @@ ngx_http_handler(ngx_http_request_t *r) if (!r->internal) { switch (r->headers_in.connection_type) { case 0: - r->keepalive = (r->http_version == NGX_HTTP_VERSION_11); + r->keepalive = (r->http_version > NGX_HTTP_VERSION_10); break; case NGX_HTTP_CONNECTION_CLOSE: -- cgit v1.2.3 From b51d0100299d0a2f35edfc1c00b3e31571bc88e5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 3 Feb 2021 12:39:41 +0300 Subject: QUIC: removed redundant "app" flag from ngx_quic_close_frame_t. The flag was introduced to create type-aware CONNECTION_CLOSE frames, and now is replaced with frame type information, directly accessible. Notably, this fixes type logging for received frames in b3d9e57d0f62. --- src/event/quic/ngx_event_quic.c | 7 ++++--- src/event/quic/ngx_event_quic_transport.c | 23 +++++++++++------------ src/event/quic/ngx_event_quic_transport.h | 1 - 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 6a156bb71..b0628545d 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -522,7 +522,8 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE_APP: p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", - f->u.close.app ? "_APP" : "", f->u.close.error_code); + f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", + f->u.close.error_code); if (f->u.close.reason.len) { p = ngx_slprintf(p, last, " %V", &f->u.close.reason); @@ -3251,10 +3252,10 @@ ngx_quic_send_cc(ngx_connection_t *c) } frame->level = qc->error_level; - frame->type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; frame->u.close.error_code = qc->error; frame->u.close.frame_type = qc->error_ftype; - frame->u.close.app = qc->error_app; if (qc->error_reason) { frame->u.close.reason.len = ngx_strlen(qc->error_reason); diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 2ecfac5a4..73b146731 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -116,7 +116,7 @@ static size_t ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *rcid); static size_t ngx_quic_create_retire_connection_id(u_char *p, ngx_quic_retire_cid_frame_t *rcid); -static size_t ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, ngx_quic_tp_t *dst); @@ -1249,7 +1249,7 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_CONNECTION_CLOSE: case NGX_QUIC_FT_CONNECTION_CLOSE_APP: f->need_ack = 0; - return ngx_quic_create_close(p, &f->u.close); + return ngx_quic_create_close(p, f); case NGX_QUIC_FT_MAX_STREAMS: return ngx_quic_create_max_streams(p, &f->u.max_streams); @@ -1956,20 +1956,19 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, static size_t -ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) +ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f) { - size_t len; - u_char *start; - ngx_uint_t type; + size_t len; + u_char *start; + ngx_quic_close_frame_t *cl; - type = cl->app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP - : NGX_QUIC_FT_CONNECTION_CLOSE; + cl = &f->u.close; if (p == NULL) { - len = ngx_quic_varint_len(type); + len = ngx_quic_varint_len(f->type); len += ngx_quic_varint_len(cl->error_code); - if (!cl->app) { + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { len += ngx_quic_varint_len(cl->frame_type); } @@ -1981,10 +1980,10 @@ ngx_quic_create_close(u_char *p, ngx_quic_close_frame_t *cl) start = p; - ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, f->type); ngx_quic_build_int(&p, cl->error_code); - if (!cl->app) { + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { ngx_quic_build_int(&p, cl->frame_type); } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 7f82d949e..e747aaa2c 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -197,7 +197,6 @@ typedef struct { uint64_t error_code; uint64_t frame_type; ngx_str_t reason; - ngx_uint_t app; /* unsigned app:1; */ } ngx_quic_close_frame_t; -- cgit v1.2.3 From 040a23bfc316403fa374e55ce52de70ace3cff38 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 4 Feb 2021 14:35:36 +0300 Subject: QUIC: use server ack_delay_exponent when sending ack. Previously, client one was used. --- src/event/quic/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index b0628545d..6a189d277 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -3173,7 +3173,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ack_delay = ngx_current_msec - ctx->largest_received; ack_delay *= 1000; - ack_delay >>= qc->ctp.ack_delay_exponent; + ack_delay >>= qc->tp.ack_delay_exponent; frame = ngx_quic_alloc_frame(c); if (frame == NULL) { -- cgit v1.2.3 From eab61bfc22b583031b5a6f42f9c277223ce18672 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 8 Feb 2021 16:49:33 +0300 Subject: QUIC: the "quic_host_key" directive. The token generation in QUIC is reworked. Single host key is used to generate all required keys of needed sizes using HKDF. The "quic_stateless_reset_token_key" directive is removed. Instead, the "quic_host_key" directive is used, which reads key from file, or sets it to random bytes if not specified. --- src/event/quic/ngx_event_quic.c | 49 ++++++++-- src/event/quic/ngx_event_quic.h | 11 ++- src/event/quic/ngx_event_quic_protection.c | 51 ++++------ src/event/quic/ngx_event_quic_protection.h | 8 +- src/http/modules/ngx_http_quic_module.c | 149 +++++++++++++++++++++++++---- src/stream/ngx_stream_quic_module.c | 146 +++++++++++++++++++++++++--- 6 files changed, 336 insertions(+), 78 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 6a189d277..b3a20ecf1 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -225,6 +225,8 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, @@ -1245,7 +1247,7 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; - if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token) + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) != NGX_OK) { return NGX_ERROR; @@ -1257,6 +1259,32 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, } +static ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); +#endif + + return NGX_OK; +} + + static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -1391,9 +1419,10 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; u_char dcid[NGX_QUIC_SERVER_CID_LEN]; - expires = ngx_time() + NGX_QUIC_RETRY_LIFETIME; + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, conf->token_key, &token, &inpkt->dcid, expires, 1) + if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, + expires, 1) != NGX_OK) { return NGX_ERROR; @@ -1723,7 +1752,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif - if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, + if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, qc->tp.sr_token) != NGX_OK) { @@ -2235,7 +2264,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, if (pkt->level == ssl_encryption_initial) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic token len:%uz %xV", + "quic address validation token len:%uz %xV", pkt->token.len, &pkt->token); } #endif @@ -2306,7 +2335,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, if (pkt->token.len) { - rc = ngx_quic_validate_token(c, conf->token_key, pkt); + rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); if (rc == NGX_ERROR) { /* internal error */ @@ -2321,10 +2350,10 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, /* token is invalid */ if (pkt->retried) { - /* invalid Retry token */ + /* invalid address validation token */ return ngx_quic_send_early_cc(c, pkt, NGX_QUIC_ERR_INVALID_TOKEN, - "invalid token"); + "invalid address validation token"); } else if (conf->retry) { /* invalid NEW_TOKEN */ return ngx_quic_send_retry(c, conf, pkt); @@ -3286,7 +3315,7 @@ ngx_quic_send_new_token(ngx_connection_t *c) expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, qc->conf->token_key, &token, NULL, expires, 0) + if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) != NGX_OK) { return NGX_ERROR; @@ -4667,7 +4696,7 @@ ngx_quic_issue_server_ids(ngx_connection_t *c) frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); - if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key, + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, frame->u.ncid.srt) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 0c94707a5..fd99321d1 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -27,9 +27,11 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 -#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 +#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 +#define NGX_QUIC_SR_KEY_LEN 32 +#define NGX_QUIC_AV_KEY_LEN 32 -#define NGX_QUIC_RETRY_LIFETIME 3 /* seconds */ +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ #define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ #define NGX_QUIC_RETRY_BUFFER_SIZE 256 /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ @@ -96,8 +98,9 @@ typedef struct { ngx_quic_tp_t tp; ngx_flag_t retry; ngx_flag_t require_alpn; - u_char token_key[32]; /* AES 256 */ - ngx_str_t sr_token_key; /* stateless reset token key */ + ngx_str_t host_key; + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 253f43482..130637aaf 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -942,58 +942,49 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_int_t -ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, - u_char *token) +ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, + ngx_str_t *salt, u_char *out, size_t len) { + size_t is_len, info_len; uint8_t *p; - size_t is_len, key_len, info_len; - ngx_str_t label; const EVP_MD *digest; - uint8_t info[20]; - uint8_t is[SHA256_DIGEST_LENGTH]; - uint8_t key[SHA256_DIGEST_LENGTH]; - /* 10.4.2. Calculating a Stateless Reset Token */ + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t info[20]; digest = EVP_sha256(); - ngx_str_set(&label, "sr_token_key"); if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - cid->data, cid->len) + salt->data, salt->len) != NGX_OK) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_extract(%V) failed", &label); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_extract(%s) failed", label); return NGX_ERROR; } - key_len = SHA256_DIGEST_LENGTH; + info[0] = 0; + info[1] = len; + info[2] = ngx_strlen(label); - info_len = 2 + 1 + label.len + 1; + info_len = 2 + 1 + info[2] + 1; - info[0] = 0; - info[1] = key_len; - info[2] = label.len; + if (info_len >= 20) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "ngx_quic_create_key label \"%s\" too long", label); + return NGX_ERROR; + } - p = ngx_cpymem(&info[3], label.data, label.len); + p = ngx_cpymem(&info[3], label, info[2]); *p = '\0'; - if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len) - != NGX_OK) + if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(%V) failed", &label); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%s) failed", label); return NGX_ERROR; } - ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); - -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif - return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 4e39ea57a..60f9b6ae7 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -11,6 +11,8 @@ #include #include +#include + #define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) @@ -27,10 +29,8 @@ void ngx_quic_keys_discard(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level); void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); - -ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - ngx_str_t *key, u_char *token); - +ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 901d1a563..8106e3e0a 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -9,6 +9,8 @@ #include #include +#include + static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -20,7 +22,8 @@ static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data); - +static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = { ngx_http_quic_max_ack_delay }; @@ -125,11 +128,11 @@ static ngx_command_t ngx_http_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, - { ngx_string("quic_stateless_reset_token_key"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_str_slot, + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, sr_token_key), + 0, NULL }, ngx_null_command @@ -174,6 +177,8 @@ static ngx_http_variable_t ngx_http_quic_vars[] = { ngx_http_null_variable }; +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + ngx_int_t ngx_http_quic_init(ngx_connection_t *c) @@ -270,7 +275,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.sr_token = { 0 } * conf->tp.sr_enabled = 0 * conf->tp.preferred_address = NULL - * conf->sr_token_key = { 0, NULL } + * conf->host_key = { 0, NULL } */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; @@ -346,25 +351,39 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { - return NGX_CONF_ERROR; - } - - ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); - if (conf->sr_token_key.len == 0) { - conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; + if (conf->host_key.len == 0) { - conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); - if (conf->sr_token_key.data == NULL) { + conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); + if (conf->host_key.data == NULL) { return NGX_CONF_ERROR; } - if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { + if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { return NGX_CONF_ERROR; } } + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->host_key, &ngx_http_quic_salt, + conf->av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->host_key, &ngx_http_quic_salt, + conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); conf->ssl = &sscf->ssl; @@ -407,3 +426,101 @@ ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) return NGX_CONF_OK; } + + +static char * +ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_quic_conf_t *qcf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +} diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index e6466bba4..6a67680b2 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -9,6 +9,8 @@ #include #include +#include + static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); @@ -20,6 +22,8 @@ static char *ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data); static char *ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_conf_post_t ngx_stream_quic_max_ack_delay_post = @@ -126,11 +130,11 @@ static ngx_command_t ngx_stream_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, - { ngx_string("quic_stateless_reset_token_key"), + { ngx_string("quic_host_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_str_slot, + ngx_stream_quic_host_key, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, sr_token_key), + 0, NULL }, ngx_null_command @@ -172,6 +176,8 @@ static ngx_stream_variable_t ngx_stream_quic_vars[] = { ngx_stream_null_variable }; +static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); + static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, @@ -229,7 +235,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.initial_scid = { 0, NULL }; * conf->tp.retry_scid = { 0, NULL }; * conf->tp.preferred_address = NULL - * conf->sr_token_key = { 0, NULL } + * conf->host_key = { 0, NULL } * conf->require_alpn = 0; */ @@ -305,25 +311,39 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { - return NGX_CONF_ERROR; - } + ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); - ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); + if (conf->host_key.len == 0) { - if (conf->sr_token_key.len == 0) { - conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; - - conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); - if (conf->sr_token_key.data == NULL) { + conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); + if (conf->host_key.data == NULL) { return NGX_CONF_ERROR; } - if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { + if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { return NGX_CONF_ERROR; } } + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->host_key, &ngx_stream_quic_salt, + conf->av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->host_key, &ngx_stream_quic_salt, + conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); conf->ssl = &scf->ssl; @@ -366,3 +386,101 @@ ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) return NGX_CONF_OK; } + + +static char * +ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_quic_conf_t *qcf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +} -- cgit v1.2.3 From 56a11126e8ce2bc643806a2e1263a512a595fde0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 9 Feb 2021 14:31:36 +0300 Subject: QUIC: fixed logging ACK frames. Previously, the wrong end pointer was used, which could lead to errors "quic failed to parse ack frame gap". --- src/event/quic/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index b3a20ecf1..6159e87e1 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -464,7 +464,7 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) if (f->data) { pos = f->data->buf->pos; - end = f->data->buf->end; + end = f->data->buf->last; } else { pos = NULL; -- cgit v1.2.3 From dbd812efd2bf08067651f73fc820c2bdce767382 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 4 Feb 2021 20:39:47 +0300 Subject: QUIC: disabled non-immediate ACKs for Initial and Handshake. As per quic-transport-33: An endpoint MUST acknowledge all ack-eliciting Initial and Handshake packets immediately If a packet carrying Initial or Handshake ACK was lost, a non-immediate ACK should not be sent later. Instead, client is expected to send a new packet to acknowledge. Sending non-immediate ACKs for Initial packets can cause the client to generate an inflated RTT sample. --- src/event/quic/ngx_event_quic.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 6159e87e1..7e6c60ef0 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -5527,8 +5527,11 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) switch (f->type) { case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: - /* force generation of most recent acknowledgment */ - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + ngx_quic_free_frame(c, f); break; -- cgit v1.2.3 From 8084a829d022ddb5ebc16393a82e5374edb1a8f5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 12 Feb 2021 14:40:33 +0300 Subject: QUIC: improved setting the lost timer. Setting the timer is brought into compliance with quic-recovery-34. Now it's set from a single function ngx_quic_set_lost_timer() that takes into account both loss detection and PTO. The following issues are fixed with this change: - when in loss detection mode, discarding a context could turn off the timer forever after switching to the PTO mode - when in loss detection mode, sending a packet resulted in rescheduling the timer as if it's always in the PTO mode --- src/event/quic/ngx_event_quic.c | 110 +++++++++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 25 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 7e6c60ef0..58986da9a 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -24,6 +24,10 @@ : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ : &((qc)->send_ctx[2])) +#define ngx_quic_lost_threshold(qc) \ + ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ + NGX_QUIC_TIME_GRANULARITY) + #define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) /* @@ -357,6 +361,7 @@ static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, static void ngx_quic_pto_handler(ngx_event_t *ev); static void ngx_quic_lost_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); +static void ngx_quic_set_lost_timer(ngx_connection_t *c); static void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static void ngx_quic_push_handler(ngx_event_t *ev); @@ -2607,6 +2612,8 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) } ctx->send_ack = 0; + + ngx_quic_set_lost_timer(c); } @@ -4920,6 +4927,8 @@ ngx_quic_output(ngx_connection_t *c) } } + ngx_quic_set_lost_timer(c); + return NGX_OK; } @@ -5167,12 +5176,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_queue_remove(q); ngx_queue_insert_tail(&ctx->sent, q); } while (--nframes); - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - ngx_add_timer(&qc->pto, ngx_quic_pto(c, ctx)); } cg->in_flight += res.len; @@ -5423,7 +5426,7 @@ static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c) { ngx_uint_t i; - ngx_msec_t now, wait, min_wait, thr; + ngx_msec_t now, wait, thr; ngx_queue_t *q; ngx_quic_frame_t *start; ngx_quic_send_ctx_t *ctx; @@ -5431,11 +5434,7 @@ ngx_quic_detect_lost(ngx_connection_t *c) qc = ngx_quic_get_connection(c); now = ngx_current_msec; - - min_wait = 0; - - thr = NGX_QUIC_TIME_THR * ngx_max(qc->latest_rtt, qc->avg_rtt); - thr = ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); + thr = ngx_quic_lost_threshold(qc); for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { @@ -5463,11 +5462,6 @@ ngx_quic_detect_lost(ngx_connection_t *c) if ((ngx_msec_int_t) wait > 0 && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) { - - if (min_wait == 0 || wait < min_wait) { - min_wait = wait; - } - break; } @@ -5475,22 +5469,88 @@ ngx_quic_detect_lost(ngx_connection_t *c) } } - /* no more preceeding packets */ + ngx_quic_set_lost_timer(c); - if (min_wait == 0) { - qc->pto.handler = ngx_quic_pto_handler; - return NGX_OK; - } + return NGX_OK; +} - qc->pto.handler = ngx_quic_lost_handler; + +static void +ngx_quic_set_lost_timer(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + lost = -1; + pto = -1; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); + + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); + + if (w < 0) { + w = 0; + } + + if (pto == -1 || w < pto) { + pto = w; + } + } if (qc->pto.timer_set) { ngx_del_timer(&qc->pto); } - ngx_add_timer(&qc->pto, min_wait); + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); - return NGX_OK; + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; + } + + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); } -- cgit v1.2.3 From 5d4e864e0d83c4eae10d26cfca3be1bba415982a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 12 Feb 2021 14:51:53 +0300 Subject: QUIC: send PING frames on PTO expiration. Two PING frames are sent per level that generate two UDP datagrams. --- src/event/quic/ngx_event_quic.c | 82 +++++++++++++++++++++++++++---- src/event/quic/ngx_event_quic_transport.c | 21 ++++++++ src/event/quic/ngx_event_quic_transport.h | 4 +- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 58986da9a..2ffa1a810 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -5099,6 +5099,10 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, f->plen = 0; nframes++; + + if (f->flush) { + break; + } } if (nframes == 0) { @@ -5346,9 +5350,10 @@ static void ngx_quic_pto_handler(ngx_event_t *ev) { ngx_uint_t i; - ngx_queue_t *q; + ngx_msec_t now; + ngx_queue_t *q, *next; ngx_connection_t *c; - ngx_quic_frame_t *start; + ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -5356,8 +5361,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) c = ev->data; qc = ngx_quic_get_connection(c); - - qc->pto_count++; + now = ngx_current_msec; for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { @@ -5368,21 +5372,79 @@ ngx_quic_pto_handler(ngx_event_t *ev) } q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (start->pnum <= ctx->largest_ack + if (f->pnum <= ctx->largest_ack && ctx->largest_ack != NGX_QUIC_UNSET_PN) { continue; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pto pnum:%uL pto_count:%ui level:%d", - start->pnum, qc->pto_count, start->level); + if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } - ngx_quic_resend_frames(c, ctx); + q = next; + } + + for (q = ngx_queue_head(&ctx->sent); + q != ngx_queue_sentinel(&ctx->sent); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_quic_congestion_lost(c, f); + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + q = next; + } + + /* enforce 2 udp datagrams */ + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->flush = 1; + + ngx_quic_queue_frame(qc, f); + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, f); } + qc->pto_count++; + ngx_quic_connstate_dbg(c); } diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 73b146731..bba1a9b39 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -93,6 +93,7 @@ static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); +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); static size_t ngx_quic_create_stop_sending(u_char *p, @@ -1220,6 +1221,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) f->need_ack = 1; switch (f->type) { + case NGX_QUIC_FT_PING: + return ngx_quic_create_ping(p); + case NGX_QUIC_FT_ACK: f->need_ack = 0; return ngx_quic_create_ack(p, &f->u.ack, f->data); @@ -1276,6 +1280,23 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) } +static size_t +ngx_quic_create_ping(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_PING); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PING); + + return p - start; +} + + static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) { diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index e747aaa2c..163ef3779 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -263,8 +263,8 @@ struct ngx_quic_frame_s { ngx_msec_t first; ngx_msec_t last; ssize_t len; - ngx_uint_t need_ack; - /* unsigned need_ack:1; */ + unsigned need_ack:1; + unsigned flush:1; ngx_chain_t *data; union { -- cgit v1.2.3 From 8a3c4c6d8cecd9d777341199cfcec32bbe9bf518 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Feb 2021 14:10:14 +0300 Subject: QUIC: distinguish reserved transport parameters in logging. 18.1. Reserved Transport Parameters Transport parameters with an identifier of the form "31 * N + 27" for integer values of N are reserved to exercise the requirement that unknown transport parameters be ignored. These transport parameters have no semantics, and can carry arbitrary values. --- src/event/quic/ngx_event_quic_transport.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index bba1a9b39..cc9b35114 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1652,7 +1652,8 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, if (rc == NGX_DECLINED) { ngx_log_error(NGX_LOG_INFO, log, 0, - "quic unknown transport param id:0x%xL, skipped", id); + "quic %s transport param id:0x%xL, skipped", + (id % 31 == 27) ? "reserved" : "unknown", id); } p += len; -- cgit v1.2.3 From da5b655f6c246119f99db652c675690f349cdcd7 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 8 Feb 2021 20:48:25 +0300 Subject: QUIC: updated list of transport parameters to be sent. The "max_ack_delay", "ack_delay_exponent", and "max_udp_payload_size" transport parameters were not communicated to client. The "disable_active_migration" and "active_connection_id_limit" parameters were not saved into zero-rtt context. --- src/event/quic/ngx_event_quic_transport.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index cc9b35114..a849a20dc 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1894,9 +1894,8 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); - if (clen) { - *clen = len; - } + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); if (tp->disable_active_migration) { len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); @@ -1906,6 +1905,17 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, tp->active_connection_id_limit); + /* transport parameters listed above will be saved in 0-RTT context */ + if (clen) { + *clen = len; + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + #if (NGX_QUIC_DRAFT_VERSION >= 28) 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); @@ -1948,6 +1958,9 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout); + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + if (tp->disable_active_migration) { ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); ngx_quic_build_int(&p, 0); @@ -1956,6 +1969,12 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, tp->active_connection_id_limit); + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + #if (NGX_QUIC_DRAFT_VERSION >= 28) ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); -- cgit v1.2.3 From f86c1e1de1fed4e6e92ecee1ed1609d31e52224c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 15 Feb 2021 14:05:46 +0300 Subject: QUIC: added check of client transport parameters. Parameters sent by client are verified and defaults are set for parameters omitted by client. --- src/event/quic/ngx_event_quic.c | 122 ++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 36 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 2ffa1a810..3501fa23a 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -225,6 +225,8 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, + ngx_quic_tp_t *ctp); static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, @@ -832,6 +834,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, u_char *p, *end; size_t client_params_len; const uint8_t *client_params; + ngx_quic_tp_t ctp; ngx_quic_frame_t *frame; ngx_connection_t *c; ngx_quic_connection_t *qc; @@ -888,7 +891,10 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, p = (u_char *) client_params; end = p + client_params_len; - if (ngx_quic_parse_transport_params(p, end, &qc->ctp, c->log) + /* 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, c->log) != NGX_OK) { qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; @@ -897,44 +903,10 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, return 0; } - if (qc->ctp.max_idle_timeout > 0 - && qc->ctp.max_idle_timeout < qc->tp.max_idle_timeout) - { - qc->tp.max_idle_timeout = qc->ctp.max_idle_timeout; - } - - if (qc->ctp.max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE - || qc->ctp.max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "invalid maximum packet size"; - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic maximum packet size is invalid"); + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { return 0; } - if (qc->ctp.max_udp_payload_size > ngx_quic_max_udp_payload(c)) { - qc->ctp.max_udp_payload_size = ngx_quic_max_udp_payload(c); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic client maximum packet size truncated"); - } - -#if (NGX_QUIC_DRAFT_VERSION >= 28) - if (qc->scid.len != qc->ctp.initial_scid.len - || ngx_memcmp(qc->scid.data, qc->ctp.initial_scid.data, - qc->scid.len) != 0) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic client initial_source_connection_id " - "mismatch"); - return 0; - } -#endif - - qc->streams.server_max_streams_bidi = qc->ctp.initial_max_streams_bidi; - qc->streams.server_max_streams_uni = qc->ctp.initial_max_streams_uni; - qc->client_tp_done = 1; } @@ -1010,6 +982,81 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, } +static ngx_int_t +ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + +#if (NGX_QUIC_DRAFT_VERSION >= 28) + if (qc->scid.len != ctp->initial_scid.len + || ngx_memcmp(qc->scid.data, ctp->initial_scid.data, qc->scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id mismatch"); + return NGX_ERROR; + } +#endif + + if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return NGX_ERROR; + + } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { + ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic client maximum packet size truncated"); + } + + if (ctp->active_connection_id_limit < 2) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid active_connection_id_limit"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active_connection_id_limit is invalid"); + return NGX_ERROR; + } + + if (ctp->ack_delay_exponent > 20) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid ack_delay_exponent"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ack_delay_exponent is invalid"); + return NGX_ERROR; + } + + if (ctp->max_ack_delay > 16384) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid max_ack_delay"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic max_ack_delay is invalid"); + return NGX_ERROR; + } + + if (ctp->max_idle_timeout > 0 + && ctp->max_idle_timeout < qc->tp.max_idle_timeout) + { + 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; + + ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); + + return NGX_OK; +} + + void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) { @@ -1124,9 +1171,12 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } ctp = &qc->ctp; + + /* defaults to be used before actual client parameters are received */ ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + ctp->active_connection_id_limit = 2; qc->streams.recv_max_data = qc->tp.initial_max_data; -- cgit v1.2.3 From 407c47074da59f016f99cf04c823080cf7a0b00c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 15 Feb 2021 14:54:28 +0300 Subject: QUIC: fixed indentation. --- src/event/quic/ngx_event_quic.c | 29 ++++++++++++++--------------- src/event/quic/ngx_event_quic_bpf.c | 4 ++-- src/event/quic/ngx_event_quic_protection.c | 8 ++++---- src/event/quic/ngx_event_quic_protection.h | 4 ++-- src/event/quic/ngx_event_quic_transport.c | 12 ++++++------ 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 3501fa23a..61e48fe7b 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -542,7 +542,6 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); } - break; case NGX_QUIC_FT_STREAM0: @@ -589,7 +588,7 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) break; case NGX_QUIC_FT_RESET_STREAM: - p = ngx_slprintf(p, last, "RESET_STREAM" + p = ngx_slprintf(p, last, "RESET_STREAM" " id:0x%xL error_code:0x%xL final_size:0x%xL", f->u.reset_stream.id, f->u.reset_stream.error_code, f->u.reset_stream.final_size); @@ -1012,7 +1011,7 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) } else if (ctp->max_udp_payload_size > ngx_quic_max_udp_payload(c)) { ctp->max_udp_payload_size = ngx_quic_max_udp_payload(c); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic client maximum packet size truncated"); + "quic client maximum packet size truncated"); } if (ctp->active_connection_id_limit < 2) { @@ -1518,7 +1517,7 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, } ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic retry packet sent to %xV", &pkt.dcid); + "quic retry packet sent to %xV", &pkt.dcid); /* * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry @@ -1967,7 +1966,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) if (qc == NULL) { if (rc == NGX_ERROR) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close connection early error"); + "quic close connection early error"); } } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { @@ -2040,9 +2039,9 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) : ssl_encryption_initial; if (rc == NGX_OK) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close drain:%d", - qc->draining); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic immediate close drain:%d", + qc->draining); qc->close.log = c->log; qc->close.data = c; @@ -3477,7 +3476,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (gap + 2 > min) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); + "quic invalid range:%ui in ack frame", i); return NGX_ERROR; } @@ -3486,7 +3485,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (range > max) { qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); + "quic invalid range:%ui in ack frame", i); return NGX_ERROR; } @@ -3757,8 +3756,8 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, fs->total -= f->length; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic skipped buffered frame, total:%ui", - fs->total); + "quic skipped buffered frame, total:%ui", + fs->total); ngx_quic_free_frame(c, frame); continue; } @@ -3784,7 +3783,7 @@ ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_queue_remove(q); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic consumed buffered frame, total:%ui", fs->total); + "quic consumed buffered frame, total:%ui", fs->total); ngx_quic_free_frame(c, frame); @@ -3872,8 +3871,8 @@ ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, fs->total += f->length; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ordered frame with unexpected offset:" - " buffered total:%ui", fs->total); + "quic ordered frame with unexpected offset:" + " buffered total:%ui", fs->total); if (ngx_queue_empty(&fs->frames)) { ngx_queue_insert_after(&fs->frames, &dst->queue); diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c index f449793ea..4d5c3adae 100644 --- a/src/event/quic/ngx_event_quic_bpf.c +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -400,8 +400,8 @@ ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, - "quic bpf sockmap fd duplicated old:%i new:%i", - ogrp->map_fd, grp->map_fd); + "quic bpf sockmap fd duplicated old:%i new:%i", + ogrp->map_fd, grp->map_fd); return grp; } diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 130637aaf..4617fe83c 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -184,7 +184,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, }; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic ngx_quic_set_initial_secret"); + "quic ngx_quic_set_initial_secret"); #ifdef NGX_QUIC_DEBUG_CRYPTO ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); @@ -242,7 +242,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, &server->secret, }, { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), &server->hp, &server->secret, @@ -724,7 +724,7 @@ ngx_quic_keys_available(ngx_quic_keys_t *keys, void ngx_quic_keys_discard(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level) + enum ssl_encryption_level_t level) { keys->secrets[level].client.key.len = 0; } @@ -956,7 +956,7 @@ ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, salt->data, salt->len) - != NGX_OK) + != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "ngx_hkdf_extract(%s) failed", label); diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 60f9b6ae7..2eeb38d7f 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -24,9 +24,9 @@ int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level); + enum ssl_encryption_level_t level); void ngx_quic_keys_discard(ngx_quic_keys_t *keys, - enum ssl_encryption_level_t level); + enum ssl_encryption_level_t level); void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index a849a20dc..01fcc9582 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -492,9 +492,9 @@ ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) pkt->level = ssl_encryption_handshake; } else { - ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic bad packet type"); - return NGX_DECLINED; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; } p = ngx_quic_parse_int(p, end, &varint); @@ -738,7 +738,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (p == NULL) { pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; ngx_log_error(NGX_LOG_INFO, pkt->log, 0, - "quic failed to obtain quic frame type"); + "quic failed to obtain quic frame type"); return NGX_ERROR; } @@ -1636,8 +1636,8 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, p = ngx_quic_parse_int(p, end, &len); if (p == NULL) { ngx_log_error(NGX_LOG_INFO, log, 0, - "quic failed to parse" - " transport param id:0x%xL length", id); + "quic failed to parse" + " transport param id:0x%xL length", id); return NGX_ERROR; } -- cgit v1.2.3 From b93b056261114022b08f11413f959d3af687b7a7 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 17 Feb 2021 14:25:07 +0300 Subject: QUIC: added ability to reset a stream. --- src/event/quic/ngx_event_quic.c | 40 +++++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_transport.c | 30 +++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 61e48fe7b..f99b0f854 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -2203,6 +2203,40 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) } +ngx_int_t +ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = qs->id; + frame->u.reset_stream.error_code = err; + frame->u.reset_stream.final_size = c->sent; + + ngx_quic_queue_frame(qc, frame); + + wev = c->write; + wev->error = 1; + wev->ready = 1; + + return NGX_OK; +} + + static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { @@ -6408,6 +6442,10 @@ ngx_quic_stream_cleanup_handler(void *data) } } + if (c->write->error) { + goto error; + } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL send fin", qs->id); @@ -6429,6 +6467,8 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_queue_frame(qc, frame); +error: + (void) ngx_quic_output(pc); } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index fd99321d1..c66f6ceab 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -131,6 +131,7 @@ void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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); +ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); uint32_t ngx_quic_version(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); diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 01fcc9582..0c6d73eb3 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -96,6 +96,8 @@ static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, 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); +static size_t ngx_quic_create_reset_stream(u_char *p, + ngx_quic_reset_stream_frame_t *rs); static size_t ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss); static size_t ngx_quic_create_crypto(u_char *p, @@ -1228,6 +1230,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) f->need_ack = 0; return ngx_quic_create_ack(p, &f->u.ack, f->data); + case NGX_QUIC_FT_RESET_STREAM: + return ngx_quic_create_reset_stream(p, &f->u.reset_stream); + case NGX_QUIC_FT_STOP_SENDING: return ngx_quic_create_stop_sending(p, &f->u.stop_sending); @@ -1333,6 +1338,31 @@ ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) } +static size_t +ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); + len += ngx_quic_varint_len(rs->id); + len += ngx_quic_varint_len(rs->error_code); + len += ngx_quic_varint_len(rs->final_size); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); + ngx_quic_build_int(&p, rs->id); + ngx_quic_build_int(&p, rs->error_code); + ngx_quic_build_int(&p, rs->final_size); + + return p - start; +} + + static size_t ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) { -- cgit v1.2.3 From c83be09720cf8dff041db6581d9df26c88bd3463 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 16 Feb 2021 18:50:01 +0300 Subject: HTTP/3: removed http3_max_field_size. Instead, size of one large_client_header_buffers buffer is used. --- src/http/v3/ngx_http_v3.h | 2 -- src/http/v3/ngx_http_v3_module.c | 12 ------------ src/http/v3/ngx_http_v3_parse.c | 10 +++++----- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 510e66bb5..5223d8f75 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -46,7 +46,6 @@ #define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 #define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 -#define NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE 4096 #define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 #define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 #define NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES 10 @@ -86,7 +85,6 @@ typedef struct { ngx_quic_tp_t quic; - size_t max_field_size; size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 89748f5f4..18da90aab 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -24,13 +24,6 @@ static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_v3_commands[] = { - { ngx_string("http3_max_field_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_field_size), - NULL }, - { ngx_string("http3_max_table_capacity"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -157,7 +150,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) return NULL; } - h3scf->max_field_size = NGX_CONF_UNSET_SIZE; h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; @@ -172,10 +164,6 @@ 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_conf_merge_size_value(conf->max_field_size, - prev->max_field_size, - NGX_HTTP_V3_DEFAULT_MAX_FIELD_SIZE); - ngx_conf_merge_size_value(conf->max_table_capacity, prev->max_table_capacity, NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 7c22b482c..b7cf242ba 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -488,8 +488,8 @@ static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, u_char ch) { - ngx_uint_t n; - ngx_http_v3_srv_conf_t *h3scf; + ngx_uint_t n; + ngx_http_core_srv_conf_t *cscf; enum { sw_start = 0, sw_value @@ -505,11 +505,11 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, n = st->length; - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); - if (n > h3scf->max_field_size) { + if (n > cscf->large_client_header_buffers.size) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client exceeded http3_max_field_size limit"); + "client sent too large header field"); return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; } -- cgit v1.2.3 From ffb099bf52e70c0cbdb1ed5555645f12ec6b2322 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Feb 2021 15:56:34 +0300 Subject: HTTP/3: introduced ngx_http_v3_parse_t structure. The structure is used to parse an HTTP/3 request. An object of this type is added to ngx_http_request_t instead of h3_parse generic pointer. Also, the new field is located outside of the request ephemeral zone to keep it safe after request headers are parsed. --- src/http/ngx_http.h | 1 + src/http/ngx_http_request.h | 5 +---- src/http/v3/ngx_http_v3.h | 6 ++++++ src/http/v3/ngx_http_v3_request.c | 27 ++++++++------------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index a7cd51d53..f5d772e65 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -20,6 +20,7 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t; typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 4121e3c3b..5231ad6f2 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -447,6 +447,7 @@ struct ngx_http_request_s { ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_parse_t *v3_parse; ngx_http_log_handler_pt log_handler; @@ -596,10 +597,6 @@ struct ngx_http_request_s { u_char *port_start; u_char *port_end; -#if (NGX_HTTP_V3) - void *h3_parse; -#endif - unsigned http_minor:16; unsigned http_major:16; }; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 5223d8f75..2b0693975 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -98,6 +98,12 @@ typedef struct { } ngx_http_v3_loc_conf_t; +struct ngx_http_v3_parse_s { + ngx_http_v3_parse_headers_t headers; + ngx_http_v3_parse_data_t body; +}; + + typedef struct { ngx_str_t name; ngx_str_t value; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index df58f383a..ef3053689 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -112,6 +112,12 @@ ngx_http_v3_init(ngx_connection_t *c) r->http_version = NGX_HTTP_VERSION_30; + r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (r->v3_parse == NULL) { + ngx_http_close_connection(c); + return; + } + c->data = r; rev = c->read; @@ -144,17 +150,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) return; } - st = r->h3_parse; - - if (st == NULL) { - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); - if (st == NULL) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return; - } - - r->h3_parse = st; - } + st = &r->v3_parse->headers; b = r->header_in; @@ -949,20 +945,13 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_http_v3_parse_data_t *st; rb = r->request_body; - st = r->h3_parse; + st = &r->v3_parse->body; if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 request body filter"); - st = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_data_t)); - if (st == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - r->h3_parse = st; - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); rb->rest = cscf->large_client_header_buffers.size; -- cgit v1.2.3 From e0425791d484b8e1e77cf39f6ca4da33b5c6e3a3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Feb 2021 11:58:32 +0300 Subject: HTTP/3: limited client header size. The limit is the size of all large client header buffers. Client header size is the total size of all client header names and values. --- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_request.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 2b0693975..4c5c8e66c 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -99,6 +99,7 @@ typedef struct { struct ngx_http_v3_parse_s { + size_t header_limit; ngx_http_v3_parse_headers_t headers; ngx_http_v3_parse_data_t body; }; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ef3053689..689d9fc61 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -118,6 +118,9 @@ ngx_http_v3_init(ngx_connection_t *c) return; } + r->v3_parse->header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + c->data = r; rev = c->read; @@ -261,11 +264,23 @@ static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { + size_t len; ngx_table_elt_t *h; ngx_http_header_t *hh; ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + 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; + } + + 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; -- cgit v1.2.3 From edc2c75c75daaa3eb940efd9c5ce535826c8b090 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 18 Feb 2021 12:22:28 +0300 Subject: QUIC: set idle timer when sending an ack-eliciting packet. As per quic-transport-34: An endpoint also restarts its idle timer when sending an ack-eliciting packet if no other ack-eliciting packets have been sent since last receiving and processing a packet. Previously, the timer was set for any packet. --- src/event/quic/ngx_event_quic.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index f99b0f854..0484909cf 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -4948,17 +4948,21 @@ static ngx_int_t ngx_quic_output(ngx_connection_t *c) { off_t max; - size_t len, min; + size_t len, min, in_flight; ssize_t n; u_char *p; ngx_uint_t i, pad; ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; c->log->action = "sending frames"; qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; for ( ;; ) { p = dst; @@ -5003,11 +5007,11 @@ ngx_quic_output(ngx_connection_t *c) if (n == NGX_ERROR) { return NGX_ERROR; } + } - if (!qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - } + if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); } ngx_quic_set_lost_timer(c); -- cgit v1.2.3 From cd276b5ed6f19d39d0e5e3ca615a31931c1f7ddf Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 18 Feb 2021 19:21:09 +0300 Subject: QUIC: removed support prior to draft-29. --- src/event/quic/ngx_event_quic.c | 4 ---- src/event/quic/ngx_event_quic_protection.c | 13 +++---------- src/event/quic/ngx_event_quic_transport.c | 23 +---------------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 0484909cf..e6ad61083 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -988,7 +988,6 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) qc = ngx_quic_get_connection(c); -#if (NGX_QUIC_DRAFT_VERSION >= 28) if (qc->scid.len != ctp->initial_scid.len || ngx_memcmp(qc->scid.data, ctp->initial_scid.data, qc->scid.len) != 0) { @@ -996,7 +995,6 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) "quic client initial_source_connection_id mismatch"); return NGX_ERROR; } -#endif if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) @@ -1204,9 +1202,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } -#if (NGX_QUIC_DRAFT_VERSION >= 28) qc->tp.original_dcid = qc->odcid; -#endif qc->tp.initial_scid = qc->dcid; if (pkt->validated && pkt->retried) { diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 4617fe83c..b6e6e8861 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -155,12 +155,9 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, #if (NGX_QUIC_DRAFT_VERSION >= 33) "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; -#elif (NGX_QUIC_DRAFT_VERSION >= 29) +#else "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; -#else - "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" - "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; #endif client = &keys->secrets[ssl_encryption_initial].client; @@ -894,18 +891,14 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) static u_char key[16] = #if (NGX_QUIC_DRAFT_VERSION >= 33) "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; -#elif (NGX_QUIC_DRAFT_VERSION >= 29) - "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; #else - "\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30"; + "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; #endif static u_char nonce[12] = #if (NGX_QUIC_DRAFT_VERSION >= 33) "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; -#elif (NGX_QUIC_DRAFT_VERSION >= 29) - "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; #else - "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75"; + "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; #endif static ngx_str_t in = ngx_string(""); diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 0c6d73eb3..2d540fe9f 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -130,13 +130,11 @@ uint32_t ngx_quic_versions[] = { /* QUICv1 */ 0x00000001, NGX_QUIC_VERSION(33), -#elif (NGX_QUIC_DRAFT_VERSION >= 29) +#else NGX_QUIC_VERSION(29), NGX_QUIC_VERSION(30), NGX_QUIC_VERSION(31), NGX_QUIC_VERSION(32), -#else - NGX_QUIC_VERSION(NGX_QUIC_DRAFT_VERSION) #endif }; @@ -1125,13 +1123,8 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) /* RETIRE_CONNECTION_ID */ 0x3, /* PATH_CHALLENGE */ 0x3, /* PATH_RESPONSE */ 0x3, -#if (NGX_QUIC_DRAFT_VERSION >= 28) /* CONNECTION_CLOSE */ 0xF, /* CONNECTION_CLOSE2 */ 0x3, -#else - /* CONNECTION_CLOSE */ 0xD, - /* CONNECTION_CLOSE2 */ 0x1, -#endif /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ }; @@ -1745,11 +1738,9 @@ ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, "quic tp active_connection_id_limit:%ui", tp->active_connection_id_limit); -#if (NGX_QUIC_DRAFT_VERSION >= 28) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp initial source_connection_id len:%uz %xV", tp->initial_scid.len, &tp->initial_scid); -#endif return NGX_OK; } @@ -1946,18 +1937,12 @@ 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); -#if (NGX_QUIC_DRAFT_VERSION >= 28) 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); } -#else - if (tp->original_dcid.len) { - len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); - } -#endif len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); @@ -2005,18 +1990,12 @@ 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); -#if (NGX_QUIC_DRAFT_VERSION >= 28) 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); } -#else - if (tp->original_dcid.len) { - ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); - } -#endif ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); -- cgit v1.2.3 From be98da0731a1c9cf24088d0b8f9a2137f3b91cfa Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 19 Feb 2021 17:27:19 +0300 Subject: QUIC: multiple versions support. Draft-29 and beyond are now supported simultaneously, no need to recompile. --- src/event/quic/ngx_event_quic.c | 6 ++++-- src/event/quic/ngx_event_quic_protection.c | 22 +++++++++------------- src/event/quic/ngx_event_quic_protection.h | 2 +- src/event/quic/ngx_event_quic_transport.c | 4 ---- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index e6ad61083..1cb9e276c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1234,7 +1234,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->server_seqnum = NGX_QUIC_UNSET_PN; - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid) + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid, + qc->version) != NGX_OK) { return NULL; @@ -2611,7 +2612,8 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid) + if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid, + inpkt->version) != 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 b6e6e8861..1e2818388 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -142,7 +142,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, - ngx_str_t *secret) + ngx_str_t *secret, uint32_t version) { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -152,13 +152,11 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_quic_secret_t *client, *server; static const uint8_t salt[20] = -#if (NGX_QUIC_DRAFT_VERSION >= 33) "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; -#else + static const uint8_t salt29[20] = "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; -#endif client = &keys->secrets[ssl_encryption_initial].client; server = &keys->secrets[ssl_encryption_initial].server; @@ -169,7 +167,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, digest = EVP_sha256(); if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - salt, sizeof(salt)) + (version & 0xff000000) ? salt29 : salt, sizeof(salt)) != NGX_OK) { return NGX_ERROR; @@ -889,17 +887,13 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) /* 5.8. Retry Packet Integrity */ static u_char key[16] = -#if (NGX_QUIC_DRAFT_VERSION >= 33) "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; -#else + static u_char key29[16] = "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; -#endif static u_char nonce[12] = -#if (NGX_QUIC_DRAFT_VERSION >= 33) "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; -#else + static u_char nonce29[12] = "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; -#endif static ngx_str_t in = ngx_string(""); ad.data = res->data; @@ -918,10 +912,12 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } secret.key.len = sizeof(key); - secret.key.data = key; + secret.key.data = (pkt->version & 0xff000000) ? key29 : key; secret.iv.len = sizeof(nonce); - if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) + if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, + (pkt->version & 0xff000000) ? nonce29 : nonce, + &in, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 2eeb38d7f..a351c9b1b 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -19,7 +19,7 @@ ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, - ngx_quic_keys_t *keys, ngx_str_t *secret); + ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version); int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 2d540fe9f..01c245f65 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -126,16 +126,12 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint32_t ngx_quic_versions[] = { -#if (NGX_QUIC_DRAFT_VERSION >= 33) /* QUICv1 */ 0x00000001, - NGX_QUIC_VERSION(33), -#else NGX_QUIC_VERSION(29), NGX_QUIC_VERSION(30), NGX_QUIC_VERSION(31), NGX_QUIC_VERSION(32), -#endif }; #define NGX_QUIC_NVERSIONS \ -- cgit v1.2.3 From d72221b8260d563f8b96ecc9c541f40cd3a5ee0f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 19 Feb 2021 17:27:41 +0300 Subject: Updated the list of supported drafts. --- README | 5 +---- src/event/quic/ngx_event_quic.h | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/README b/README index 5733c75ca..ddaceb4b8 100644 --- a/README +++ b/README @@ -34,12 +34,9 @@ Experimental QUIC support for nginx What works now: - Currently we support IETF-QUIC draft-27 through draft-32. + Currently we support IETF-QUIC draft-29 through draft-34. Earlier drafts are NOT supported as they have incompatible wire format. - You may look at src/event/ngx_event_quic.h for alternative values of the - NGX_QUIC_DRAFT_VERSION macro used to select IETF draft version number. - nginx should be able to respond to HTTP/3 requests over QUIC and it should be possible to upload and download big files without errors. diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index c66f6ceab..be0eec699 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -12,7 +12,6 @@ #include -/* Supported drafts: 27, 28, 29 */ #ifndef NGX_QUIC_DRAFT_VERSION #define NGX_QUIC_DRAFT_VERSION 29 #endif -- cgit v1.2.3 From 449ce52f973c6e08fbcab13f06c4efd12a801ad8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sun, 7 Mar 2021 00:23:23 +0300 Subject: README: bump browsers' version after 81bb3a690c10 (old drafts rip). --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index ddaceb4b8..0e8403059 100644 --- a/README +++ b/README @@ -172,7 +172,7 @@ Example configuration: * Browsers - Known to work: Firefox 75+ and Chrome 83+ + Known to work: Firefox 80+ and Chrome 85+ (QUIC draft 29+) Beware of strange issues: sometimes browser may decide to ignore QUIC Cache clearing/restart might help. Always check access.log and -- cgit v1.2.3 From b7433b15fcdd97cc0d8b45407a4af1520663e54f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sun, 7 Mar 2021 00:23:25 +0300 Subject: README: http3_max_field_size was removed in ae2e68f206f9. --- README | 1 - 1 file changed, 1 deletion(-) diff --git a/README b/README index 0e8403059..880463836 100644 --- a/README +++ b/README @@ -130,7 +130,6 @@ Experimental QUIC support for nginx A number of directives were added that configure HTTP/3: - http3_max_field_size http3_max_table_capacity http3_max_blocked_streams http3_max_concurrent_pushes -- cgit v1.2.3 From 7f348b2d1f3bca54227c3d24fb017ec8787cd8a2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 10 Mar 2021 17:56:34 +0300 Subject: HTTP/3: fixed server push. --- src/http/v3/ngx_http_v3_filter_module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 9a2313a14..8d601c978 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -884,6 +884,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ngx_str_set(&r->http_protocol, "HTTP/3.0"); + r->http_version = NGX_HTTP_VERSION_30; r->method_name = ngx_http_core_get_method; r->method = NGX_HTTP_GET; -- cgit v1.2.3 From b8fd5dc640d809e87314fca2afbb43f6da28ea92 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 11 Mar 2021 14:43:01 +0300 Subject: QUIC: added error handling to ngx_hkdf_extract()/ngx_hkdf_expand(). The OpenSSL variant of functions lacked proper error processing. --- src/event/quic/ngx_event_quic_protection.c | 58 +++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 1e2818388..4b29869ce 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -305,44 +305,57 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) { #ifdef OPENSSL_IS_BORINGSSL + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) == 0) { return NGX_ERROR; } + + return NGX_OK; + #else EVP_PKEY_CTX *pctx; pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { - return NGX_ERROR; + goto failed; } -#endif - return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif } @@ -352,45 +365,58 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, size_t salt_len) { #ifdef OPENSSL_IS_BORINGSSL + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, salt_len) == 0) { return NGX_ERROR; } + + return NGX_OK; + #else EVP_PKEY_CTX *pctx; pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } if (EVP_PKEY_derive_init(pctx) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { - return NGX_ERROR; + goto failed; } if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { - return NGX_ERROR; + goto failed; } -#endif - return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif } -- cgit v1.2.3 From 6a0bea5361d0edfa69fce916739b8b1b96c7fb4f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 16 Mar 2021 13:48:28 +0300 Subject: QUIC: fixed expected TLS codepoint with final draft and BoringSSL. A reasonable codepoint is always set[1] explicitly so that it doesn't depend on the default library value that may change[2] in the future. [1] https://boringssl.googlesource.com/boringssl/+/3d8b8c3d [2] https://boringssl.googlesource.com/boringssl/+/c47bfce0 --- src/event/quic/ngx_event_quic.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 1cb9e276c..cb6aff3fb 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1803,6 +1803,10 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif +#if BORINGSSL_API_VERSION >= 13 + SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); +#endif + if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, qc->tp.sr_token) != NGX_OK) -- cgit v1.2.3 From 780de6de44b066a253a455a061efb09ff8060d09 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 16 Mar 2021 13:48:28 +0300 Subject: QUIC: fixed hq ALPN id for the final draft. It was an agreement to use "hq-interop"[1] for interoperability testing. [1] https://github.com/quicwg/base-drafts/wiki/ALPN-IDs-used-with-QUIC --- src/http/modules/ngx_http_quic_module.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h index bc75dd501..21d4a40a1 100644 --- a/src/http/modules/ngx_http_quic_module.h +++ b/src/http/modules/ngx_http_quic_module.h @@ -14,7 +14,7 @@ #include -#define NGX_HTTP_QUIC_ALPN_ADVERTISE "\x02hq" +#define NGX_HTTP_QUIC_ALPN_ADVERTISE "\x0Ahq-interop" #define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" -- cgit v1.2.3 From e522bb69f9e4fa480dfe875a559445ec7831cec9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 16 Mar 2021 13:48:29 +0300 Subject: HTTP/3: do not push until a MAX_PUSH_ID frame is received. Fixes interop with quic-go that doesn't send MAX_PUSH_ID. --- src/http/v3/ngx_http_v3_filter_module.c | 6 ++++-- src/http/v3/ngx_http_v3_streams.c | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 8d601c978..30e38fcfb 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -787,7 +787,7 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", + "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", path, h3c->npushing, h3scf->max_concurrent_pushes, h3c->next_push_id, h3c->max_push_id); @@ -797,7 +797,9 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, return NGX_DECLINED; } - if (h3c->next_push_id > h3c->max_push_id) { + if (h3c->max_push_id == (uint64_t) -1 + || h3c->next_push_id > h3c->max_push_id) + { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 abort pushes due to max_push_id"); return NGX_ABORT; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 871914065..c27fa16dc 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -62,6 +62,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c->hc = *phc; h3c->hc.http3 = 1; + h3c->max_push_id = (uint64_t) -1; ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); @@ -762,7 +763,7 @@ ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 MAX_PUSH_ID:%uL", max_push_id); - if (max_push_id < h3c->max_push_id) { + if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { return NGX_HTTP_V3_ERR_ID_ERROR; } -- cgit v1.2.3 From bb44bfa631f2fefff0e9399383b06c9e09668556 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 15 Mar 2021 19:05:38 +0300 Subject: QUIC: fixed key extraction in bpf. In case of long header packets, dcid length was not read correctly. While there, macros to parse uint64 was fixed as well as format specifiers to print it in debug mode. Thanks to Gao Yan . --- src/event/quic/bpf/ngx_quic_reuseport_helper.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/event/quic/bpf/ngx_quic_reuseport_helper.c index 05919aaa9..2de9b2864 100644 --- a/src/event/quic/bpf/ngx_quic_reuseport_helper.c +++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -58,10 +58,10 @@ char _license[] SEC("license") = LICENSE; ((__u64)(p)[1] << 48) | \ ((__u64)(p)[2] << 40) | \ ((__u64)(p)[3] << 32) | \ - (p)[4] << 24 | \ - (p)[5] << 16 | \ - (p)[6] << 8 | \ - (p)[7]) + ((__u64)(p)[4] << 24) | \ + ((__u64)(p)[5] << 16) | \ + ((__u64)(p)[6] << 8) | \ + ((__u64)(p)[7])) /* * actual map object is created by the "bpf" system call, @@ -82,12 +82,14 @@ int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) end = (unsigned char *) ctx->data_end; offset = 0; - advance_data(sizeof(struct udphdr)); /* skip UDP header */ - advance_data(1); /* QUIC flags */ + advance_data(sizeof(struct udphdr)); /* data at UDP header */ + advance_data(1); /* data at QUIC flags */ if (data[0] & NGX_QUIC_PKT_LONG) { - advance_data(4); /* skip QUIC version */ + advance_data(4); /* data at QUIC version */ + advance_data(1); /* data at DCID len */ + len = data[0]; /* read DCID length */ if (len < 8) { @@ -95,8 +97,6 @@ int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) return SK_PASS; } - advance_data(1); /* skip DCID len */ - } else { len = NGX_QUIC_SERVER_CID_LEN; } @@ -115,17 +115,17 @@ int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) switch (rc) { case 0: - debugmsg("nginx quic socket selected by key 0x%x", key); + debugmsg("nginx quic socket selected by key 0x%llx", key); return SK_PASS; /* kernel returns positive error numbers, errno.h defines positive */ case -ENOENT: - debugmsg("nginx quic default route for key 0x%x", key); + debugmsg("nginx quic default route for key 0x%llx", key); /* let the default reuseport logic decide which socket to choose */ return SK_PASS; default: - debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%x", + debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", rc, key); goto failed; } -- cgit v1.2.3 From 19c461a522cfdf6e6b4251472fd170973501e279 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 16 Mar 2021 18:17:25 +0300 Subject: QUIC: bpf code regenerated. --- src/event/quic/ngx_event_quic_bpf_code.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/event/quic/ngx_event_quic_bpf_code.c b/src/event/quic/ngx_event_quic_bpf_code.c index 9cbeb3432..990686545 100644 --- a/src/event/quic/ngx_event_quic_bpf_code.c +++ b/src/event/quic/ngx_event_quic_bpf_code.c @@ -7,7 +7,7 @@ static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { - { "ngx_quic_sockmap", 56 }, + { "ngx_quic_sockmap", 55 }, }; static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { @@ -16,10 +16,10 @@ static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, - { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 55, 0x0 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 }, { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, - { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 52, 0x0 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 }, { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, @@ -28,20 +28,20 @@ static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, - { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 43, 0x0 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 }, { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, - { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 40, 0x0 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 }, { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, - { 0x71, BPF_REG_5, BPF_REG_4, (int16_t) 12, 0x0 }, + { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 }, { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, - { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 36, 0x0 }, + { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 }, { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, - { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 33, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 }, { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, - { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 30, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 }, { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, @@ -54,8 +54,7 @@ static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, - { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, - { 0xc7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 }, { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, -- cgit v1.2.3 From d8fd0b31619ed7302690abf1b27a7c7ec99fbc9d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 23 Mar 2021 10:58:18 +0300 Subject: Core: fixed build with BPF on non-64bit platforms (ticket #2152). --- src/core/ngx_bpf.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/core/ngx_bpf.c b/src/core/ngx_bpf.c index 6b2611708..363a02c7d 100644 --- a/src/core/ngx_bpf.c +++ b/src/core/ngx_bpf.c @@ -45,14 +45,14 @@ ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) ngx_memzero(&attr, sizeof(union bpf_attr)); - attr.license = (uint64_t) program->license; + attr.license = (uintptr_t) program->license; attr.prog_type = program->type; - attr.insns = (uint64_t) program->ins; + attr.insns = (uintptr_t) program->ins; attr.insn_cnt = program->nins; #if (NGX_DEBUG) /* for verifier errors */ - attr.log_buf = (uint64_t) buf; + attr.log_buf = (uintptr_t) buf; attr.log_size = NGX_BPF_LOGBUF_SIZE; attr.log_level = 1; #endif @@ -106,8 +106,8 @@ ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) ngx_memzero(&attr, sizeof(union bpf_attr)); attr.map_fd = fd; - attr.key = (uint64_t) key; - attr.value = (uint64_t) value; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; attr.flags = flags; return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); @@ -122,7 +122,7 @@ ngx_bpf_map_delete(int fd, const void *key) ngx_memzero(&attr, sizeof(union bpf_attr)); attr.map_fd = fd; - attr.key = (uint64_t) key; + attr.key = (uintptr_t) key; return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); } @@ -136,8 +136,8 @@ ngx_bpf_map_lookup(int fd, const void *key, void *value) ngx_memzero(&attr, sizeof(union bpf_attr)); attr.map_fd = fd; - attr.key = (uint64_t) key; - attr.value = (uint64_t) value; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); } -- cgit v1.2.3 From 190b5d961c0c9b0942dd1a2d8cd609416d0d5114 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 19:26:04 +0300 Subject: HTTP/3: send GOAWAY when last request is accepted. The last request in connection is determined according to the keepalive_requests directive. Requests beyond keepalive_requests are rejected. --- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_request.c | 21 +++++++++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 4c5c8e66c..a8a5c5cd4 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -161,6 +161,7 @@ ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); void ngx_http_v3_init_uni_stream(ngx_connection_t *c); ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id); +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 689d9fc61..0c055ba0e 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -52,10 +52,12 @@ void ngx_http_v3_init(ngx_connection_t *c) { size_t size; + uint64_t n; ngx_buf_t *b; ngx_event_t *rev; ngx_http_request_t *r; ngx_http_connection_t *hc; + ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; if (ngx_http_v3_init_session(c) != NGX_OK) { @@ -74,6 +76,25 @@ ngx_http_v3_init(ngx_connection_t *c) hc = c->data; + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + ngx_http_close_connection(c); + return; + } + + if (n + 1 == clcf->keepalive_requests) { + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "goaway error"); + ngx_http_close_connection(c); + return; + } + } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index c27fa16dc..e09556c93 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -522,6 +522,40 @@ failed: } +ngx_int_t +ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_DECLINED; + } + + n = ngx_http_v3_encode_varlen_int(NULL, id); + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); + n = p - buf; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) -- cgit v1.2.3 From 9533df5b728833dd516f44d18953a3731c29e787 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 16:39:33 +0300 Subject: QUIC: connection shutdown. The function ngx_quic_shutdown_connection() waits until all non-cancelable streams are closed, and then closes the connection. In HTTP/3 cancelable streams are all unidirectional streams except push streams. The function is called from HTTP/3 when client reaches keepalive_requests. --- src/event/quic/ngx_event_quic.c | 78 ++++++++++++++++++++++++++++++++++++--- src/event/quic/ngx_event_quic.h | 3 ++ src/http/v3/ngx_http_v3.h | 3 ++ src/http/v3/ngx_http_v3_request.c | 3 ++ src/http/v3/ngx_http_v3_streams.c | 4 ++ 5 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cb6aff3fb..f38b15910 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -174,9 +174,13 @@ typedef struct { 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 closing:1; + unsigned shutdown:1; unsigned draining:1; unsigned key_phase:1; unsigned validated:1; @@ -384,6 +388,7 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); +static void ngx_quic_shutdown_quic(ngx_connection_t *c); static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); @@ -684,6 +689,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->closing ? " closing" : ""); p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); @@ -2138,6 +2144,21 @@ ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, } +void +ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + 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_timer_handler(ngx_event_t *ev) { @@ -5945,6 +5966,10 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) qc = ngx_quic_get_connection(c); + if (qc->shutdown) { + return NGX_QUIC_STREAM_GONE; + } + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { @@ -6016,6 +6041,10 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) } sn->c->listening->handler(sn->c); + + if (qc->shutdown) { + return NGX_QUIC_STREAM_GONE; + } } return ngx_quic_create_stream(c, id, n); @@ -6410,7 +6439,7 @@ ngx_quic_stream_cleanup_handler(void *data) if (!c->read->pending_eof && !c->read->error) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { - return; + goto done; } frame->level = ssl_encryption_application; @@ -6425,7 +6454,7 @@ ngx_quic_stream_cleanup_handler(void *data) if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { - return; + goto done; } frame->level = ssl_encryption_application; @@ -6444,12 +6473,12 @@ ngx_quic_stream_cleanup_handler(void *data) if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { /* do not send fin for client unidirectional streams */ - return; + goto done; } } if (c->write->error) { - goto error; + goto done; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -6457,7 +6486,7 @@ ngx_quic_stream_cleanup_handler(void *data) frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { - return; + goto done; } frame->level = ssl_encryption_application; @@ -6473,9 +6502,46 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_queue_frame(qc, frame); -error: +done: (void) ngx_quic_output(pc); + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + } +} + + +static void +ngx_quic_shutdown_quic(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); + + if (qc->closing) { + return; + } + + 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_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index be0eec699..0a38d911c 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -120,6 +120,7 @@ struct ngx_quic_stream_s { uint64_t send_max_data; ngx_buf_t *b; ngx_quic_frames_stream_t fs; + ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; @@ -130,6 +131,8 @@ void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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, + const char *reason); ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); uint32_t ngx_quic_version(ngx_connection_t *c); ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index a8a5c5cd4..be0ed127d 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -82,6 +82,9 @@ #define ngx_http_v3_finalize_connection(c, code, reason) \ ngx_quic_finalize_connection(c->quic->parent, code, reason) +#define ngx_http_v3_shutdown_connection(c, code, reason) \ + ngx_quic_shutdown_connection(c->quic->parent, code, reason) + typedef struct { ngx_quic_tp_t quic; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0c055ba0e..d4a5faccf 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -93,6 +93,9 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_http_close_connection(c); return; } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "reached maximum number of requests"); } cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index e09556c93..eac49a659 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -80,6 +80,8 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + c->quic->cancelable = 1; + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { ngx_http_close_connection(c); @@ -436,6 +438,8 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) return NULL; } + sc->quic->cancelable = 1; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create uni stream, type:%ui", type); -- cgit v1.2.3 From f4ab680bcb34d832a1dfd086e3641bf78c0a207c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 30 Mar 2021 16:48:38 +0300 Subject: HTTP/3: keepalive timeout. This timeout limits the time when no client request streams exist. --- src/http/v3/ngx_http_v3.h | 8 ++++++++ src/http/v3/ngx_http_v3_request.c | 36 ++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index be0ed127d..7fba09056 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -74,6 +74,11 @@ #define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 +#define ngx_http_v3_get_module_loc_conf(c, module) \ + ngx_http_get_module_loc_conf( \ + ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ + module) + #define ngx_http_v3_get_module_srv_conf(c, module) \ ngx_http_get_module_srv_conf( \ ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ @@ -128,6 +133,9 @@ typedef struct { ngx_http_connection_t hc; ngx_http_v3_dynamic_table_t table; + ngx_event_t keepalive; + ngx_uint_t nrequests; + ngx_queue_t blocked; ngx_uint_t nblocked; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index d4a5faccf..b997c29a1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,7 @@ #include +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); @@ -55,8 +56,10 @@ ngx_http_v3_init(ngx_connection_t *c) uint64_t n; ngx_buf_t *b; ngx_event_t *rev; + ngx_pool_cleanup_t *cln; ngx_http_request_t *r; ngx_http_connection_t *hc; + ngx_http_v3_connection_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -98,6 +101,22 @@ ngx_http_v3_init(ngx_connection_t *c) "reached maximum number of requests"); } + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = c; + + h3c = c->quic->parent->data; + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; @@ -154,6 +173,23 @@ ngx_http_v3_init(ngx_connection_t *c) } +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_core_loc_conf_t *clcf; + ngx_http_v3_connection_t *h3c; + + h3c = c->quic->parent->data; + + 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); + } +} + + static void ngx_http_v3_process_request(ngx_event_t *rev) { diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index eac49a659..f2036982f 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -29,6 +29,8 @@ typedef struct { } ngx_http_v3_push_t; +static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); +static void ngx_http_v3_cleanup_session(void *data); static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); @@ -43,6 +45,7 @@ ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c) { ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; ngx_http_connection_t *phc; ngx_http_v3_connection_t *h3c; @@ -67,12 +70,50 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); + h3c->keepalive.log = pc->log; + h3c->keepalive.data = pc; + h3c->keepalive.handler = ngx_http_v3_keepalive_handler; + h3c->keepalive.cancelable = 1; + + cln = ngx_pool_cleanup_add(pc->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_cleanup_session; + cln->data = h3c; + pc->data = h3c; return ngx_http_v3_send_settings(c); } +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_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); +} + + +static void +ngx_http_v3_cleanup_session(void *data) +{ + ngx_http_v3_connection_t *h3c = data; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } +} + + void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { -- cgit v1.2.3 From 25a74b52d14fa0e0bbac4c0d1b3e432bbb31ca97 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 22 Mar 2021 15:51:14 +0300 Subject: HTTP/3: set initial_max_streams_uni default value to 3. The maximum number of HTTP/3 unidirectional client streams we can handle is 3: control, decode and encode. These streams are never closed. --- src/http/modules/ngx_http_quic_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 8106e3e0a..5282bf4bb 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -337,7 +337,7 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->tp.initial_max_streams_bidi, 16); ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni, - prev->tp.initial_max_streams_uni, 16); + prev->tp.initial_max_streams_uni, 3); ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent, prev->tp.ack_delay_exponent, -- cgit v1.2.3 From 7d1cf8ffb442727bc8e54630dd565c8139cead67 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 16:25:54 +0300 Subject: HTTP/3: fixed $connection_requests. Previously, the value was always "1". --- src/http/v3/ngx_http_v3_request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b997c29a1..4dc673078 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -165,6 +165,7 @@ ngx_http_v3_init(ngx_connection_t *c) * cscf->large_client_header_buffers.num; c->data = r; + c->requests = n + 1; rev = c->read; rev->handler = ngx_http_v3_process_request; -- cgit v1.2.3 From 18f9330cd6ce76469d4ffa81040af8d634280fd9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 31 Mar 2021 21:43:17 +0300 Subject: QUIC: HKDF API compatibility with OpenSSL master branch. OpenSSL 3.0 started to require HKDF-Extract output PRK length pointer used to represent the amount of data written to contain the length of the key buffer before the call. EVP_PKEY_derive() documents this. See HKDF_Extract() internal implementation update in this change: https://github.com/openssl/openssl/commit/5a285ad --- src/event/quic/ngx_event_quic_protection.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 4b29869ce..efc15a22b 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -165,6 +165,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, (version & 0xff000000) ? salt29 : salt, sizeof(salt)) @@ -968,6 +969,7 @@ ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, uint8_t info[20]; digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, salt->data, salt->len) -- cgit v1.2.3 From 496a43485417afe7c9b5191b683f60ed7bcd4101 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 Mar 2021 15:25:11 +0300 Subject: QUIC: do not copy input data. Previously, when a new datagram arrived, data were copied from the UDP layer to the QUIC layer via c->recv() interface. Now UDP buffer is accessed directly. --- src/event/quic/ngx_event_quic.c | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index f38b15910..22317db25 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1878,21 +1878,13 @@ ngx_quic_max_udp_payload(ngx_connection_t *c) static void ngx_quic_input_handler(ngx_event_t *rev) { - ssize_t n; ngx_int_t rc; - ngx_buf_t b; + ngx_buf_t *b; ngx_connection_t *c; ngx_quic_connection_t *qc; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); - ngx_memzero(&b, sizeof(ngx_buf_t)); - b.start = buf; - b.end = buf + sizeof(buf); - b.pos = b.last = b.start; - b.memory = 1; - c = rev->data; qc = ngx_quic_get_connection(c); @@ -1911,21 +1903,13 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - n = c->recv(c, b.start, b.end - b.start); - - if (n == NGX_AGAIN) { + if (!rev->ready) { if (qc->closing) { ngx_quic_close_connection(c, NGX_OK); } return; } - if (n == NGX_ERROR) { - c->read->eof = 1; - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - if (qc->tp.disable_active_migration) { if (c->socklen != qc->socklen || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0) @@ -1936,10 +1920,11 @@ ngx_quic_input_handler(ngx_event_t *rev) } } - b.last += n; - qc->received += n; + b = c->udp->buffer; + + qc->received += (b->last - b->pos); - rc = ngx_quic_input(c, &b, NULL); + rc = ngx_quic_input(c, b, NULL); if (rc == NGX_ERROR) { ngx_quic_close_connection(c, NGX_ERROR); -- cgit v1.2.3 From daf9c643d1ee3ee0f2b00bf9b75966f055e67821 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 Mar 2021 15:22:18 +0300 Subject: QUIC: do not reallocate c->sockaddr. When a connection is created, enough memory is allocated to accomodate any future address change. --- src/event/ngx_event_udp.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index 884b1cd51..a15e31f41 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -24,6 +24,7 @@ static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls, void ngx_event_recvmsg(ngx_event_t *ev) { + size_t len; ssize_t n; ngx_str_t key; ngx_buf_t buf; @@ -302,7 +303,15 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } - c->sockaddr = ngx_palloc(c->pool, socklen); + len = socklen; + +#if (NGX_QUIC) + if (ls->quic) { + len = NGX_SOCKADDRLEN; + } +#endif + + c->sockaddr = ngx_palloc(c->pool, len); if (c->sockaddr == NULL) { ngx_close_accepted_udp_connection(c); return; @@ -704,13 +713,6 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, } #endif - if (c->socklen < socklen) { - c->sockaddr = ngx_palloc(c->pool, socklen); - if (c->sockaddr == NULL) { - return c; - } - } - ngx_memcpy(c->sockaddr, sockaddr, socklen); c->socklen = socklen; } -- cgit v1.2.3 From 6b70513bd8809f4addd059886a070bb7ed6cb612 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 30 Mar 2021 14:33:47 +0300 Subject: QUIC: do not handle empty dcid. When a QUIC datagram arrives, its DCID is never empty. Previously, the case of empty DCID was handled. Now this code is simplified. --- src/event/ngx_event_udp.c | 95 ++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index a15e31f41..d0c766062 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -17,8 +17,7 @@ static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c); static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls, - ngx_str_t *key, struct sockaddr *sockaddr, socklen_t socklen, - struct sockaddr *local_sockaddr, socklen_t local_socklen); + ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); void @@ -224,7 +223,24 @@ ngx_event_recvmsg(ngx_event_t *ev) #endif - ngx_str_null(&key); + key.data = (u_char *) sockaddr; + key.len = socklen; + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + key.len = 0; + } + } + +#endif #if (NGX_QUIC) if (ls->quic) { @@ -234,8 +250,7 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - c = ngx_lookup_udp_connection(ls, &key, sockaddr, socklen, - local_sockaddr, local_socklen); + c = ngx_lookup_udp_connection(ls, &key, local_sockaddr, local_socklen); if (c) { @@ -253,6 +268,13 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif +#if (NGX_QUIC) + if (ls->quic) { + c->socklen = socklen; + ngx_memcpy(c->sockaddr, sockaddr, socklen); + } +#endif + ngx_memzero(&buf, sizeof(ngx_buf_t)); buf.pos = buffer; @@ -508,11 +530,6 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, rc = ngx_memn2cmp(udp->key.data, udpt->key.data, udp->key.len, udpt->key.len); - if (rc == 0 && udp->key.len == 0) { - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); - } - if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, ct->local_sockaddr, ct->local_socklen, 1); @@ -566,7 +583,8 @@ ngx_create_udp_connection(ngx_connection_t *c) cln->data = c; cln->handler = ngx_delete_udp_connection; - key.len = 0; + key.data = (u_char *) c->sockaddr; + key.len = c->socklen; ngx_insert_udp_connection(c, udp, &key); @@ -586,10 +604,6 @@ ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, ngx_crc32_update(&hash, key->data, key->len); - if (key->len == 0) { - ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen); - } - if (c->listening->wildcard) { ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); } @@ -621,7 +635,6 @@ ngx_delete_udp_connection(void *data) static ngx_connection_t * ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, - struct sockaddr *sockaddr, socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen) { uint32_t hash; @@ -630,33 +643,16 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, ngx_rbtree_node_t *node, *sentinel; ngx_udp_connection_t *udp; -#if (NGX_HAVE_UNIX_DOMAIN) - - if (sockaddr->sa_family == AF_UNIX) { - struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; - - if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) - || saun->sun_path[0] == '\0') - { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, - "unbound unix socket"); - return NULL; - } + if (key->len == 0) { + return NULL; } -#endif - node = ls->rbtree.root; sentinel = ls->rbtree.sentinel; ngx_crc32_init(hash); - ngx_crc32_update(&hash, key->data, key->len); - if (key->len == 0) { - ngx_crc32_update(&hash, (u_char *) sockaddr, socklen); - } - if (ls->wildcard) { ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen); } @@ -683,41 +679,12 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len); - if (rc == 0 && key->len == 0) { - rc = ngx_cmp_sockaddr(sockaddr, socklen, - c->sockaddr, c->socklen, 1); - } - if (rc == 0 && ls->wildcard) { rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, c->local_sockaddr, c->local_socklen, 1); } if (rc == 0) { - if (key->len) { - rc = ngx_cmp_sockaddr(sockaddr, socklen, - c->sockaddr, c->socklen, 1); - - if (rc) { -#if (NGX_DEBUG) - if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { - ngx_str_t addr; - u_char text[NGX_SOCKADDR_STRLEN]; - - addr.data = text; - addr.len = ngx_sock_ntop(sockaddr, socklen, text, - NGX_SOCKADDR_STRLEN, 1); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "client migrated to %V", &addr); - } -#endif - - ngx_memcpy(c->sockaddr, sockaddr, socklen); - c->socklen = socklen; - } - } - return c; } -- cgit v1.2.3 From d9e4f8e28865e8cb801c6166e78bd6d94e3cccd6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 30 Mar 2021 14:33:43 +0300 Subject: QUIC: fixed udp buffer initialization. The start field is used to check if the QUIC packet is first in the datagram. This fixes stateless reset detection. --- src/event/ngx_event_udp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index d0c766062..d6b11d0b6 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -279,6 +279,8 @@ ngx_event_recvmsg(ngx_event_t *ev) buf.pos = buffer; buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); rev = c->read; -- cgit v1.2.3 From f3489441b202b40a119e4c4dd4e580c3f7b97721 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 2 Apr 2021 18:58:19 +0300 Subject: UDP: extended datagram context. Sometimes it is required to process datagram properties at higher level (i.e. QUIC is interested in source address which may change and IP options). The patch adds ngx_udp_dgram_t structure used to pass packet-related information in c->udp. --- src/event/ngx_event.h | 9 ++++++- src/event/ngx_event_udp.c | 53 +++++++++++++++++++---------------------- src/event/quic/ngx_event_quic.c | 2 +- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index 17e9d58d0..e023af027 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -169,11 +169,18 @@ struct ngx_event_aio_s { #if !(NGX_WIN32) +typedef struct { + ngx_buf_t *buffer; + struct sockaddr *sockaddr; + socklen_t socklen; +} ngx_udp_dgram_t; + + struct ngx_udp_connection_s { ngx_rbtree_node_t node; ngx_connection_t *connection; ngx_str_t key; - ngx_buf_t *buffer; + ngx_udp_dgram_t *dgram; }; #endif diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index d6b11d0b6..ca70e3bd6 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -29,12 +29,13 @@ ngx_event_recvmsg(ngx_event_t *ev) ngx_buf_t buf; ngx_log_t *log; ngx_err_t err; - socklen_t socklen, local_socklen; + socklen_t local_socklen; ngx_event_t *rev, *wev; struct iovec iov[1]; struct msghdr msg; ngx_sockaddr_t sa, lsa; - struct sockaddr *sockaddr, *local_sockaddr; + ngx_udp_dgram_t dgram; + struct sockaddr *local_sockaddr; ngx_listening_t *ls; ngx_event_conf_t *ecf; ngx_connection_t *c, *lc; @@ -131,21 +132,21 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - sockaddr = msg.msg_name; - socklen = msg.msg_namelen; + dgram.sockaddr = msg.msg_name; + dgram.socklen = msg.msg_namelen; - if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { - socklen = sizeof(ngx_sockaddr_t); + if (dgram.socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + dgram.socklen = sizeof(ngx_sockaddr_t); } - if (socklen == 0) { + if (dgram.socklen == 0) { /* * on Linux recvmsg() returns zero msg_namelen * when receiving packets from unbound AF_UNIX sockets */ - socklen = sizeof(struct sockaddr); + dgram.socklen = sizeof(struct sockaddr); ngx_memzero(&sa, sizeof(struct sockaddr)); sa.sockaddr.sa_family = ls->sockaddr->sa_family; } @@ -223,15 +224,16 @@ ngx_event_recvmsg(ngx_event_t *ev) #endif - key.data = (u_char *) sockaddr; - key.len = socklen; + key.data = (u_char *) dgram.sockaddr; + key.len = dgram.socklen; #if (NGX_HAVE_UNIX_DOMAIN) - if (sockaddr->sa_family == AF_UNIX) { - struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + if (dgram.sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) dgram.sockaddr; - if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + if (dgram.socklen <= (socklen_t) offsetof(struct sockaddr_un, + sun_path) || saun->sun_path[0] == '\0') { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, @@ -268,13 +270,6 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif -#if (NGX_QUIC) - if (ls->quic) { - c->socklen = socklen; - ngx_memcpy(c->sockaddr, sockaddr, socklen); - } -#endif - ngx_memzero(&buf, sizeof(ngx_buf_t)); buf.pos = buffer; @@ -284,7 +279,9 @@ ngx_event_recvmsg(ngx_event_t *ev) rev = c->read; - c->udp->buffer = &buf; + dgram.buffer = &buf; + + c->udp->dgram = &dgram; rev->ready = 1; rev->active = 0; @@ -292,7 +289,7 @@ ngx_event_recvmsg(ngx_event_t *ev) rev->handler(rev); if (c->udp) { - c->udp->buffer = NULL; + c->udp->dgram = NULL; } rev->ready = 0; @@ -315,7 +312,7 @@ ngx_event_recvmsg(ngx_event_t *ev) c->shared = 1; c->type = SOCK_DGRAM; - c->socklen = socklen; + c->socklen = dgram.socklen; #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, 1); @@ -327,7 +324,7 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } - len = socklen; + len = dgram.socklen; #if (NGX_QUIC) if (ls->quic) { @@ -341,7 +338,7 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } - ngx_memcpy(c->sockaddr, sockaddr, socklen); + ngx_memcpy(c->sockaddr, dgram.sockaddr, dgram.socklen); log = ngx_palloc(c->pool, sizeof(ngx_log_t)); if (log == NULL) { @@ -483,17 +480,17 @@ ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size) ssize_t n; ngx_buf_t *b; - if (c->udp == NULL || c->udp->buffer == NULL) { + if (c->udp == NULL || c->udp->dgram == NULL) { return NGX_AGAIN; } - b = c->udp->buffer; + b = c->udp->dgram->buffer; n = ngx_min(b->last - b->pos, (ssize_t) size); ngx_memcpy(buf, b->pos, n); - c->udp->buffer = NULL; + c->udp->dgram = NULL; c->read->ready = 0; c->read->active = 1; diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 22317db25..77f0eeda8 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1920,7 +1920,7 @@ ngx_quic_input_handler(ngx_event_t *rev) } } - b = c->udp->buffer; + b = c->udp->dgram->buffer; qc->received += (b->last - b->pos); -- cgit v1.2.3 From 20f3d107df1ee8c565af8763176e48cb6589adab Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 2 Apr 2021 11:31:37 +0300 Subject: QUIC: simplified quic connection dispatching. Currently listener contains rbtree with multiple nodes for single QUIC connection: each corresponding to specific server id. Each udp node points to same ngx_connection_t, which points to QUIC connection via c->udp field. Thus when an event handler is called, it only gets ngx_connection_t with c->udp pointing to QUIC connection. This makes it hard to obtain actual node which was used to dispatch packet (it requires to repeat DCID lookup). Additionally, ngx_quic_connection_t->udp field is only needed to keep a pointer in c->udp. The node is not added into the tree and does not carry useful information. --- src/event/ngx_event_udp.c | 7 +++++++ src/event/quic/ngx_event_quic.c | 29 +++++++++++++++-------------- src/event/quic/ngx_event_quic.h | 3 ++- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index ca70e3bd6..95895571d 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -684,6 +684,13 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, } if (rc == 0) { + +#if (NGX_QUIC) + if (ls->quic && c->udp != udp) { + c->udp = udp; + } +#endif + return c; } diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 77f0eeda8..d07c3ed49 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -112,8 +112,6 @@ typedef struct { typedef struct { - ngx_udp_connection_t udp; - uint32_t version; ngx_str_t scid; /* initial client ID */ ngx_str_t dcid; /* server (our own) ID */ @@ -198,6 +196,7 @@ typedef struct { typedef struct { ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; ngx_queue_t queue; uint64_t seqnum; size_t len; @@ -342,7 +341,7 @@ static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, - ngx_str_t *id); + ngx_quic_connection_t *qc, ngx_str_t *id); static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, @@ -1096,6 +1095,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, { ngx_uint_t i; ngx_quic_tp_t *ctp; + ngx_quic_server_id_t *sid; ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; @@ -1247,18 +1247,19 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } - c->udp = &qc->udp; - - if (ngx_quic_insert_server_id(c, &qc->odcid) == NULL) { + if (ngx_quic_insert_server_id(c, qc, &qc->odcid) == NULL) { return NULL; } qc->server_seqnum = 0; - if (ngx_quic_insert_server_id(c, &qc->dcid) == NULL) { + sid = ngx_quic_insert_server_id(c, qc, &qc->dcid); + if (sid == NULL) { return NULL; } + c->udp = &sid->udp; + qc->validated = pkt->validated; return qc; @@ -4777,7 +4778,7 @@ ngx_quic_issue_server_ids(ngx_connection_t *c) dcid.len = NGX_QUIC_SERVER_CID_LEN; dcid.data = id; - sid = ngx_quic_insert_server_id(c, &dcid); + sid = ngx_quic_insert_server_id(c, qc, &dcid); if (sid == NULL) { return NGX_ERROR; } @@ -4840,19 +4841,19 @@ ngx_quic_clear_temp_server_ids(ngx_connection_t *c) static ngx_quic_server_id_t * -ngx_quic_insert_server_id(ngx_connection_t *c, ngx_str_t *id) +ngx_quic_insert_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_str_t *id) { - ngx_str_t dcid; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); + ngx_str_t dcid; + ngx_quic_server_id_t *sid; sid = ngx_quic_alloc_server_id(c, qc); if (sid == NULL) { return NULL; } + sid->quic = qc; + sid->seqnum = qc->server_seqnum; if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 0a38d911c..865e5f08c 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -64,7 +64,8 @@ #define NGX_QUIC_BUFFER_SIZE 4096 -#define ngx_quic_get_connection(c) ((ngx_quic_connection_t *)(c)->udp) +#define ngx_quic_get_connection(c) \ + (((c)->udp) ? (((ngx_quic_server_id_t *)((c)->udp))->quic) : NULL) typedef struct { -- cgit v1.2.3 From bd90c0ab796a3a321d17262c189bde334746acc3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 31 Mar 2021 14:56:16 +0300 Subject: QUIC: separate header for ngx_quic_connection_t. --- auto/modules | 3 +- src/event/quic/ngx_event_quic.c | 165 +------------------------- src/event/quic/ngx_event_quic_connection.h | 179 +++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 162 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_connection.h diff --git a/auto/modules b/auto/modules index 5b0c2d689..079b09bc4 100644 --- a/auto/modules +++ b/auto/modules @@ -1341,7 +1341,8 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then ngx_module_incs= ngx_module_deps="src/event/quic/ngx_event_quic.h \ src/event/quic/ngx_event_quic_transport.h \ - src/event/quic/ngx_event_quic_protection.h" + src/event/quic/ngx_event_quic_protection.h \ + src/event/quic/ngx_event_quic_connection.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c" diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index d07c3ed49..4fedf755b 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -28,8 +29,6 @@ ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ NGX_QUIC_TIME_GRANULARITY) -#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) - /* * 7.4. Cryptographic Message Buffering * Implementations MUST support buffering at least 4096 bytes of data @@ -53,157 +52,6 @@ #define NGX_QUIC_MAX_ACK_GAP 2 -typedef struct { - ngx_rbtree_t tree; - ngx_rbtree_node_t sentinel; - - uint64_t received; - uint64_t sent; - uint64_t recv_max_data; - 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; -} ngx_quic_streams_t; - - -typedef struct { - size_t in_flight; - size_t window; - size_t ssthresh; - ngx_msec_t recovery_start; -} ngx_quic_congestion_t; - - -/* - * 12.3. Packet Numbers - * - * Conceptually, a packet number space is the context in which a packet - * can be processed and acknowledged. Initial packets can only be sent - * with Initial packet protection keys and acknowledged in packets which - * are also Initial packets. -*/ -typedef struct { - enum ssl_encryption_level_t level; - - uint64_t pnum; /* to be sent */ - uint64_t largest_ack; /* received from peer */ - uint64_t largest_pn; /* received from peer */ - - ngx_queue_t frames; - ngx_queue_t sent; - - uint64_t pending_ack; /* non sent ack-eliciting */ - uint64_t largest_range; - uint64_t first_range; - ngx_msec_t largest_received; - ngx_msec_t ack_delay_start; - ngx_uint_t nranges; - ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; - ngx_uint_t send_ack; -} ngx_quic_send_ctx_t; - - -typedef struct { - uint32_t version; - ngx_str_t scid; /* initial client ID */ - ngx_str_t dcid; /* server (our own) ID */ - ngx_str_t odcid; /* original server ID */ - - struct sockaddr *sockaddr; - socklen_t socklen; - - ngx_queue_t client_ids; - ngx_queue_t server_ids; - ngx_queue_t free_client_ids; - ngx_queue_t free_server_ids; - ngx_uint_t nclient_ids; - ngx_uint_t nserver_ids; - uint64_t max_retired_seqnum; - uint64_t client_seqnum; - uint64_t server_seqnum; - - ngx_uint_t client_tp_done; - ngx_quic_tp_t tp; - ngx_quic_tp_t ctp; - - ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; - - ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; - - ngx_quic_keys_t *keys; - - ngx_quic_conf_t *conf; - - ngx_event_t push; - ngx_event_t pto; - ngx_event_t close; - ngx_msec_t last_cc; - - ngx_msec_t latest_rtt; - ngx_msec_t avg_rtt; - ngx_msec_t min_rtt; - ngx_msec_t rttvar; - - ngx_uint_t pto_count; - - ngx_queue_t free_frames; - ngx_chain_t *free_bufs; - ngx_buf_t *free_shadow_bufs; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_uint_t nframes; - ngx_uint_t nbufs; -#endif - - ngx_quic_streams_t streams; - ngx_quic_congestion_t congestion; - off_t received; - - ngx_uint_t error; - enum ssl_encryption_level_t error_level; - 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 closing:1; - unsigned shutdown:1; - unsigned draining:1; - unsigned key_phase:1; - unsigned validated:1; -} ngx_quic_connection_t; - - -typedef struct { - ngx_queue_t queue; - uint64_t seqnum; - size_t len; - u_char id[NGX_QUIC_CID_LEN_MAX]; - u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; -} ngx_quic_client_id_t; - - -typedef struct { - ngx_udp_connection_t udp; - ngx_quic_connection_t *quic; - ngx_queue_t queue; - uint64_t seqnum; - size_t len; - u_char id[NGX_QUIC_CID_LEN_MAX]; -} ngx_quic_server_id_t; - - typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); @@ -256,7 +104,6 @@ static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); -static void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); static void ngx_quic_close_timer_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, @@ -347,9 +194,6 @@ static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc); -static void ngx_quic_queue_frame(ngx_quic_connection_t *qc, - ngx_quic_frame_t *frame); - static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, @@ -388,7 +232,6 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); static void ngx_quic_shutdown_quic(ngx_connection_t *c); -static ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_ack(ngx_connection_t *c, @@ -1945,7 +1788,7 @@ ngx_quic_input_handler(ngx_event_t *rev) } -static void +void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) { ngx_pool_t *pool; @@ -4933,7 +4776,7 @@ ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) } -static void +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { ngx_quic_send_ctx_t *ctx; @@ -6531,7 +6374,7 @@ ngx_quic_shutdown_quic(ngx_connection_t *c) } -static ngx_quic_frame_t * +ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c) { ngx_queue_t *q; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h new file mode 100644 index 000000000..298857a83 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connection.h @@ -0,0 +1,179 @@ +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ + + +#include +#include +#include +#include +#include + + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + + +typedef struct ngx_quic_connection_s ngx_quic_connection_t; + + +typedef struct { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_client_id_t; + + +typedef struct { + ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +} ngx_quic_server_id_t; + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + + uint64_t received; + uint64_t sent; + uint64_t recv_max_data; + 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; +} ngx_quic_streams_t; + + +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + +/* + * 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets which + * are also Initial packets. +*/ +typedef struct { + enum ssl_encryption_level_t level; + + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ + + ngx_queue_t frames; + ngx_queue_t sent; + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + ngx_uint_t send_ack; +} ngx_quic_send_ctx_t; + + +struct ngx_quic_connection_s { + uint32_t version; + + ngx_str_t scid; /* initial client ID */ + ngx_str_t dcid; /* server (our own) ID */ + ngx_str_t odcid; /* original server ID */ + + struct sockaddr *sockaddr; + socklen_t socklen; + + ngx_queue_t client_ids; + ngx_queue_t server_ids; + ngx_queue_t free_client_ids; + ngx_queue_t free_server_ids; + ngx_uint_t nclient_ids; + ngx_uint_t nserver_ids; + uint64_t max_retired_seqnum; + uint64_t client_seqnum; + uint64_t server_seqnum; + + ngx_uint_t client_tp_done; + ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; + + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; + + ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; + + ngx_quic_keys_t *keys; + + ngx_quic_conf_t *conf; + + ngx_event_t push; + ngx_event_t pto; + ngx_event_t close; + ngx_msec_t last_cc; + + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + + ngx_uint_t pto_count; + + ngx_queue_t free_frames; + ngx_chain_t *free_bufs; + ngx_buf_t *free_shadow_bufs; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_uint_t nframes; + ngx_uint_t nbufs; +#endif + + ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; + off_t received; + + ngx_uint_t error; + enum ssl_encryption_level_t error_level; + 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 closing:1; + unsigned shutdown:1; + unsigned draining:1; + unsigned key_phase:1; + unsigned validated:1; +}; + + +ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); +void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); + +#endif -- cgit v1.2.3 From 79b66760a19599fa4b2cb3b9aa3c3e7e937df8ae Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 31 Mar 2021 14:57:15 +0300 Subject: QUIC: distinct files for connection migration. The connection migration-related code from quic.c with dependencies is moved into separate file. --- auto/modules | 6 ++- src/event/quic/ngx_event_quic.c | 61 +++++++----------------------- src/event/quic/ngx_event_quic_connection.h | 13 +++++++ src/event/quic/ngx_event_quic_migration.c | 46 ++++++++++++++++++++++ src/event/quic/ngx_event_quic_migration.h | 20 ++++++++++ 5 files changed, 96 insertions(+), 50 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_migration.c create mode 100644 src/event/quic/ngx_event_quic_migration.h diff --git a/auto/modules b/auto/modules index 079b09bc4..0098d4c54 100644 --- a/auto/modules +++ b/auto/modules @@ -1342,10 +1342,12 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then ngx_module_deps="src/event/quic/ngx_event_quic.h \ src/event/quic/ngx_event_quic_transport.h \ src/event/quic/ngx_event_quic_protection.h \ - src/event/quic/ngx_event_quic_connection.h" + src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_migration.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ - src/event/quic/ngx_event_quic_protection.c" + src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_migration.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 4fedf755b..4129aa316 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -10,21 +10,10 @@ #include #include #include +#include #include -/* 0-RTT and 1-RTT data exist in the same packet number space, - * so we have 3 packet number spaces: - * - * 0 - Initial - * 1 - Handshake - * 2 - 0-RTT and 1-RTT - */ -#define ngx_quic_get_send_ctx(qc, level) \ - ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ - : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ - : &((qc)->send_ctx[2])) - #define ngx_quic_lost_threshold(qc) \ ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ NGX_QUIC_TIME_GRANULARITY) @@ -141,8 +130,6 @@ static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_msec_t *send_time); static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, enum ssl_encryption_level_t level, ngx_msec_t send_time); -static ngx_inline ngx_msec_t ngx_quic_pto(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f); @@ -177,8 +164,6 @@ static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); static 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); -static ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, @@ -2755,6 +2740,17 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) break; + case NGX_QUIC_FT_PATH_RESPONSE: + + if (ngx_quic_handle_path_response_frame(c, pkt, + &frame.u.path_response) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_NEW_CONNECTION_ID: if (ngx_quic_handle_new_connection_id_frame(c, pkt, &frame.u.ncid) @@ -2776,13 +2772,6 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) break; - case NGX_QUIC_FT_PATH_RESPONSE: - - /* TODO: handle */ - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic frame handler not implemented"); - break; - default: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic missing frame handler"); @@ -3510,7 +3499,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, } -static ngx_inline ngx_msec_t +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { ngx_msec_t duration; @@ -4377,30 +4366,6 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, } -static ngx_int_t -ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_PATH_RESPONSE; - frame->u.path_response = *f; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 298857a83..acbec5133 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -19,6 +19,18 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) + typedef struct { ngx_queue_t queue; @@ -175,5 +187,6 @@ struct ngx_quic_connection_s { ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); #endif diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c new file mode 100644 index 000000000..de3d6885d --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.c @@ -0,0 +1,46 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include +#include + + +ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_PATH_RESPONSE; + frame->u.path_response = *f; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + /* TODO */ + return NGX_OK; +} + diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h new file mode 100644 index 000000000..3231b7e3b --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); + +#endif -- cgit v1.2.3 From 0ad83da4f7ea94976e1d5684a62ed1ad2ff4517c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 23 Mar 2021 11:58:43 +0300 Subject: QUIC: PATH_CHALLENGE frame creation. --- src/event/quic/ngx_event_quic_transport.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 01c245f65..3fcc9fec9 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -113,6 +113,8 @@ static size_t ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms); static size_t ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_challenge(u_char *p, + ngx_quic_path_challenge_frame_t *pc); static size_t ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc); static size_t ngx_quic_create_new_connection_id(u_char *p, @@ -1258,6 +1260,9 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_MAX_DATA: return ngx_quic_create_max_data(p, &f->u.max_data); + case NGX_QUIC_FT_PATH_CHALLENGE: + return ngx_quic_create_path_challenge(p, &f->u.path_challenge); + case NGX_QUIC_FT_PATH_RESPONSE: return ngx_quic_create_path_response(p, &f->u.path_response); @@ -1786,6 +1791,27 @@ ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) } +static size_t +ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + static size_t ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) { -- cgit v1.2.3 From b61176b9f7e8c8649e14cbb8532de6e3cf560f63 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 7 Apr 2021 15:14:41 +0300 Subject: QUIC: fixed memory leak in ngx_hkdf_extract()/ngx_hkdf_expand(). This fixes leak on successful path when built with OpenSSL. --- src/event/quic/ngx_event_quic_protection.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index efc15a22b..12dd233b8 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -348,6 +348,8 @@ ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, goto failed; } + EVP_PKEY_CTX_free(pctx); + return NGX_OK; failed: @@ -409,6 +411,8 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, goto failed; } + EVP_PKEY_CTX_free(pctx); + return NGX_OK; failed: -- cgit v1.2.3 From 8ba7adf037cc32300e19034e371a7add5222e47e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 12 Apr 2021 12:30:30 +0300 Subject: HTTP/3: removed h3scf->quic leftover after 0d2b2664b41c. --- src/http/v3/ngx_http_v3.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 7fba09056..18b7a7636 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -92,7 +92,6 @@ typedef struct { - ngx_quic_tp_t quic; size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; -- cgit v1.2.3 From 2fd50ca589d37b4dd66006254d99918f44617cf0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 16 Apr 2021 19:42:03 +0300 Subject: HTTP/3: keepalive_time support. --- src/http/v3/ngx_http_v3.h | 2 ++ src/http/v3/ngx_http_v3_request.c | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 18b7a7636..45d1a3671 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -143,6 +143,8 @@ typedef struct { uint64_t next_push_id; uint64_t max_push_id; + ngx_uint_t goaway; /* unsigned goaway:1; */ + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; } ngx_http_v3_connection_t; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4dc673078..c459efef5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,15 +81,22 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - n = c->quic->id >> 2; + h3c = c->quic->parent->data; - if (n >= clcf->keepalive_requests) { + if (h3c->goaway) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); ngx_http_close_connection(c); return; } - if (n + 1 == clcf->keepalive_requests) { + n = c->quic->id >> 2; + + if (n + 1 == clcf->keepalive_requests + || ngx_current_msec - c->quic->parent->start_time + > clcf->keepalive_time) + { + h3c->goaway = 1; + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, "goaway error"); @@ -110,7 +117,6 @@ ngx_http_v3_init(ngx_connection_t *c) cln->handler = ngx_http_v3_cleanup_request; cln->data = c; - h3c = c->quic->parent->data; h3c->nrequests++; if (h3c->keepalive.timer_set) { -- cgit v1.2.3 From a6c52268ebeca4e52ac44cb1b702e9a491e4a4fe Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 5 Apr 2021 11:31:03 +0300 Subject: QUIC: added error codes and messages from latest drafts. The AEAD_LIMIT_REACHED was addeded in draft-31. The NO_VIABLE_PATH was added in draft-33. --- src/event/quic/ngx_event_quic_transport.c | 2 ++ src/event/quic/ngx_event_quic_transport.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 3fcc9fec9..27dbf92af 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -157,6 +157,8 @@ static char *ngx_quic_errors[] = { "APPLICATION_ERROR", "CRYPTO_BUFFER_EXCEEDED", "KEY_UPDATE_ERROR", + "AEAD_LIMIT_REACHED", + "NO_VIABLE_PATH", }; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 163ef3779..415bbc75c 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -98,8 +98,10 @@ #define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C #define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D #define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E +#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F +#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 -#define NGX_QUIC_ERR_LAST 0x0F +#define NGX_QUIC_ERR_LAST 0x11 #define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 #define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) -- cgit v1.2.3 From 5e6ee4b50acfb00ecc16a1f0c64e3951132209b7 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 5 Apr 2021 11:35:46 +0300 Subject: QUIC: fixed debug message macro. --- src/event/quic/ngx_event_quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 4129aa316..52104fa12 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1348,8 +1348,8 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic retry packet sent to %xV", &pkt.dcid); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); /* * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry -- cgit v1.2.3 From 6e945f235ec532cb79fcc0f70ec007f89a603e17 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 7 Apr 2021 13:09:26 +0300 Subject: QUIC: fixed ngx_quic_send_ack_range() function. Created frame was not added to the output queue. --- src/event/quic/ngx_event_quic.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 52104fa12..cc66bcce4 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -3042,7 +3042,10 @@ static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest) { - ngx_quic_frame_t *frame; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); frame = ngx_quic_alloc_frame(c); if (frame == NULL) { @@ -3056,6 +3059,8 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, frame->u.ack.range_count = 0; frame->u.ack.first_range = largest - smallest; + ngx_quic_queue_frame(qc, frame); + return NGX_OK; } -- cgit v1.2.3 From 9495ea7cdabe302d9f2cdb1eeb0192be56b17ff5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 9 Apr 2021 11:33:10 +0300 Subject: QUIC: separate function for connection ids initialization. The function correctly cleans up resources in case of failure to create initial server id: it removes previously created udp node for odcid from listening rbtree. --- src/event/quic/ngx_event_quic.c | 102 ++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 40 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cc66bcce4..771837a57 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -69,6 +69,8 @@ static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp); static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_setup_connection_ids(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, @@ -923,8 +925,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, { ngx_uint_t i; ngx_quic_tp_t *ctp; - ngx_quic_server_id_t *sid; - ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); @@ -960,10 +960,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } ngx_queue_init(&qc->free_frames); - ngx_queue_init(&qc->client_ids); - ngx_queue_init(&qc->server_ids); - ngx_queue_init(&qc->free_client_ids); - ngx_queue_init(&qc->free_server_ids); qc->avg_rtt = NGX_QUIC_INITIAL_RTT; qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; @@ -971,9 +967,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, /* * qc->latest_rtt = 0 - * qc->nclient_ids = 0 - * qc->nserver_ids = 0 - * qc->max_retired_seqnum = 0 */ qc->received = pkt->raw->last - pkt->raw->start; @@ -1020,42 +1013,78 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->congestion.ssthresh = (size_t) -1; qc->congestion.recovery_start = ngx_current_msec; - qc->odcid.len = pkt->odcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); - if (qc->odcid.data == NULL) { - return NULL; + 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->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid, + qc->version) + != NGX_OK) + { return NULL; } - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + qc->validated = pkt->validated; + + if (ngx_quic_setup_connection_ids(c, qc, pkt) != NGX_OK) { return NULL; } - qc->tp.original_dcid = qc->odcid; - qc->tp.initial_scid = qc->dcid; + return qc; +} - 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; - } + +static ngx_int_t +ngx_quic_setup_connection_ids(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_server_id_t *sid, *osid; + ngx_quic_client_id_t *cid; + + /* + * qc->nclient_ids = 0 + * qc->nserver_ids = 0 + * qc->max_retired_seqnum = 0 + */ + + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->server_ids); + ngx_queue_init(&qc->free_client_ids); + ngx_queue_init(&qc->free_server_ids); + + qc->odcid.len = pkt->odcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->odcid.data == NULL) { + return NGX_ERROR; } + qc->tp.original_dcid = qc->odcid; + qc->scid.len = pkt->scid.len; qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid); if (qc->scid.data == NULL) { - return NULL; + return NGX_ERROR; + } + + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; } + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + return NGX_ERROR; + } + + qc->tp.initial_scid = qc->dcid; + cid = ngx_quic_alloc_client_id(c, qc); if (cid == NULL) { - return NULL; + return NGX_ERROR; } cid->seqnum = 0; @@ -1068,29 +1097,22 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->server_seqnum = NGX_QUIC_UNSET_PN; - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid, - qc->version) - != NGX_OK) - { - return NULL; - } - - if (ngx_quic_insert_server_id(c, qc, &qc->odcid) == NULL) { - return NULL; + osid = ngx_quic_insert_server_id(c, qc, &qc->odcid); + if (osid == NULL) { + return NGX_ERROR; } qc->server_seqnum = 0; sid = ngx_quic_insert_server_id(c, qc, &qc->dcid); if (sid == NULL) { - return NULL; + ngx_rbtree_delete(&c->listening->rbtree, &osid->udp.node); + return NGX_ERROR; } c->udp = &sid->udp; - qc->validated = pkt->validated; - - return qc; + return NGX_OK; } -- cgit v1.2.3 From 232fcba34bb6e64587318f877d25cc3977fe5ac6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 14 Apr 2021 14:47:37 +0300 Subject: QUIC: headers cleanup. The "ngx_event_quic.h" header file now contains only public definitions, used by modules. All internal definitions are moved into the "ngx_event_quic_connection.h" header file. --- README | 2 +- src/event/quic/ngx_event_quic.c | 5 +--- src/event/quic/ngx_event_quic.h | 44 +--------------------------- src/event/quic/ngx_event_quic_connection.h | 47 ++++++++++++++++++++++++++++-- src/event/quic/ngx_event_quic_migration.c | 2 -- src/event/quic/ngx_event_quic_migration.h | 2 +- src/event/quic/ngx_event_quic_transport.h | 2 ++ 7 files changed, 50 insertions(+), 54 deletions(-) diff --git a/README b/README index 880463836..e82bb9a82 100644 --- a/README +++ b/README @@ -229,7 +229,7 @@ Example configuration: be easily filtered out. + If you want to investigate deeper, you may want to enable - additional debugging in src/event/ngx_event_quic.h: + additional debugging in src/event/ngx_event_quic_connection.h: #define NGX_QUIC_DEBUG_PACKETS #define NGX_QUIC_DEBUG_FRAMES diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 771837a57..ae16fe166 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -7,11 +7,8 @@ #include #include #include -#include -#include -#include -#include #include +#include #define ngx_quic_lost_threshold(qc) \ diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 865e5f08c..0066c96e6 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -16,13 +16,7 @@ #define NGX_QUIC_DRAFT_VERSION 29 #endif -#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -#define NGX_QUIC_MAX_LONG_HEADER 56 - /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ - #define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 @@ -30,23 +24,7 @@ #define NGX_QUIC_SR_KEY_LEN 32 #define NGX_QUIC_AV_KEY_LEN 32 -#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ -#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ -#define NGX_QUIC_RETRY_BUFFER_SIZE 256 - /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ -#define NGX_QUIC_MAX_TOKEN_SIZE 64 - /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ - -/* quic-recovery, section 6.2.2, kInitialRtt */ -#define NGX_QUIC_INITIAL_RTT 333 /* ms */ - -/* quic-recovery, section 6.1.1, Packet Threshold */ -#define NGX_QUIC_PKT_THR 3 /* packets */ -/* quic-recovery, section 6.1.2, Time Threshold */ -#define NGX_QUIC_TIME_THR 1.125 -#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ - -#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ +#define NGX_QUIC_SR_TOKEN_LEN 16 #define NGX_QUIC_MIN_INITIAL_SIZE 1200 @@ -55,18 +33,6 @@ #define NGX_QUIC_STREAM_BUFSIZE 65536 -#define NGX_QUIC_MAX_CID_LEN 20 -#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN - -#define NGX_QUIC_SR_TOKEN_LEN 16 - -#define NGX_QUIC_MAX_SERVER_IDS 8 - -#define NGX_QUIC_BUFFER_SIZE 4096 - -#define ngx_quic_get_connection(c) \ - (((c)->udp) ? (((ngx_quic_server_id_t *)((c)->udp))->quic) : NULL) - typedef struct { /* configurable */ @@ -139,12 +105,4 @@ uint32_t ngx_quic_version(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); - -/********************************* DEBUG *************************************/ - -/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ -/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ -/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ -/* #define NGX_QUIC_DEBUG_CRYPTO */ - #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index acbec5133..0c31aa6a0 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -10,14 +10,45 @@ #include #include #include + #include #include +typedef struct ngx_quic_connection_s ngx_quic_connection_t; + +#include -#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) +#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ +#define NGX_QUIC_MAX_LONG_HEADER 56 + /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ -typedef struct ngx_quic_connection_s ngx_quic_connection_t; +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 + +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + +/* quic-recovery, section 6.2.2, kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + +/* quic-recovery, section 6.1.1, Packet Threshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* quic-recovery, section 6.1.2, Time Threshold */ +#define NGX_QUIC_TIME_THR 1.125 +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + +#define NGX_QUIC_MAX_SERVER_IDS 8 + +#define NGX_QUIC_BUFFER_SIZE 4096 + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) /* 0-RTT and 1-RTT data exist in the same packet number space, * so we have 3 packet number spaces: @@ -31,6 +62,9 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ : &((qc)->send_ctx[2])) +#define ngx_quic_get_connection(c) \ + (((c)->udp) ? (((ngx_quic_server_id_t *)((c)->udp))->quic) : NULL) + typedef struct { ngx_queue_t queue; @@ -189,4 +223,11 @@ void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); -#endif +/********************************* DEBUG *************************************/ + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + +#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index de3d6885d..2adf9d4e7 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include ngx_int_t diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 3231b7e3b..5f27c278d 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -17,4 +17,4 @@ ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); -#endif +#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 415bbc75c..2cda8088f 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -47,6 +47,8 @@ : (lvl == ssl_encryption_initial) ? "init" \ : (lvl == ssl_encryption_handshake) ? "hs" : "early" +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN /* 12.4. Frames and Frame Types */ #define NGX_QUIC_FT_PADDING 0x00 -- cgit v1.2.3 From 118775761caaf4fdc04eac57638ce772239c92ab Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:37:41 +0300 Subject: QUIC: separate files for connection id related processing. --- auto/modules | 2 + src/event/quic/ngx_event_quic.c | 529 +--------------------------- src/event/quic/ngx_event_quic_connection.h | 8 +- src/event/quic/ngx_event_quic_connid.c | 532 +++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_connid.h | 25 ++ 5 files changed, 566 insertions(+), 530 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_connid.c create mode 100644 src/event/quic/ngx_event_quic_connid.h diff --git a/auto/modules b/auto/modules index 0098d4c54..8a36a50e5 100644 --- a/auto/modules +++ b/auto/modules @@ -1343,10 +1343,12 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_transport.h \ src/event/quic/ngx_event_quic_protection.h \ src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c" ngx_module_libs= diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index ae16fe166..818ef342c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -23,8 +23,6 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 -#define NGX_QUIC_UNSET_PN (uint64_t) -1 - /* * Endpoints MUST discard packets that are too small to be valid QUIC * packets. With the set of AEAD functions defined in [QUIC-TLS], @@ -66,20 +64,12 @@ static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp); static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_setup_connection_ids(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - u_char *secret, u_char *token); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); -static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); -#if (NGX_QUIC_BPF) -static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); -#endif static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, @@ -163,20 +153,6 @@ static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); static 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); -static ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); -static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum); -static ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); -static ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); -static void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); -static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_str_t *id); -static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, - ngx_quic_connection_t *qc); -static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, - ngx_quic_connection_t *qc); static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); @@ -1035,84 +1011,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } -static ngx_int_t -ngx_quic_setup_connection_ids(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_quic_header_t *pkt) -{ - ngx_quic_server_id_t *sid, *osid; - ngx_quic_client_id_t *cid; - - /* - * qc->nclient_ids = 0 - * qc->nserver_ids = 0 - * qc->max_retired_seqnum = 0 - */ - - ngx_queue_init(&qc->client_ids); - ngx_queue_init(&qc->server_ids); - ngx_queue_init(&qc->free_client_ids); - ngx_queue_init(&qc->free_server_ids); - - qc->odcid.len = pkt->odcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } - - qc->tp.original_dcid = qc->odcid; - - qc->scid.len = pkt->scid.len; - qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid); - if (qc->scid.data == NULL) { - return NGX_ERROR; - } - - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { - return NGX_ERROR; - } - - qc->tp.initial_scid = qc->dcid; - - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NGX_ERROR; - } - - cid->seqnum = 0; - cid->len = pkt->scid.len; - ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); - - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - qc->client_seqnum = 0; - - qc->server_seqnum = NGX_QUIC_UNSET_PN; - - osid = ngx_quic_insert_server_id(c, qc, &qc->odcid); - if (osid == NULL) { - return NGX_ERROR; - } - - qc->server_seqnum = 0; - - sid = ngx_quic_insert_server_id(c, qc, &qc->dcid); - if (sid == NULL) { - ngx_rbtree_delete(&c->listening->rbtree, &osid->udp.node); - return NGX_ERROR; - } - - c->udp = &sid->udp; - - return NGX_OK; -} - - static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) @@ -1164,7 +1062,7 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, } -static ngx_int_t +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token) { @@ -1262,56 +1160,6 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) } -static ngx_int_t -ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) -{ - if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { - return NGX_ERROR; - } - -#if (NGX_QUIC_BPF) - if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "quic bpf failed to generate socket key"); - /* ignore error, things still may work */ - } -#endif - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic create server id %*xs", - (size_t) NGX_QUIC_SERVER_CID_LEN, id); - return NGX_OK; -} - - -#if (NGX_QUIC_BPF) - -static ngx_int_t -ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) -{ - int fd; - uint64_t cookie; - socklen_t optlen; - - fd = c->listening->fd; - - optlen = sizeof(cookie); - - if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { - ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, - "quic getsockopt(SO_COOKIE) failed"); - - return NGX_ERROR; - } - - ngx_quic_dcid_encode_key(id, cookie); - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *inpkt) @@ -4390,381 +4238,6 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, } -static ngx_int_t -ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) -{ - ngx_queue_t *q; - ngx_quic_client_id_t *cid, *item; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (f->seqnum < qc->max_retired_seqnum) { - /* - * An endpoint that receives a NEW_CONNECTION_ID frame with - * a sequence number smaller than the Retire Prior To field - * of a previously received NEW_CONNECTION_ID frame MUST send - * a corresponding RETIRE_CONNECTION_ID frame that retires - * the newly received connection ID, unless it has already - * done so for that sequence number. - */ - - if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { - return NGX_ERROR; - } - - goto retire; - } - - cid = NULL; - - for (q = ngx_queue_head(&qc->client_ids); - q != ngx_queue_sentinel(&qc->client_ids); - q = ngx_queue_next(q)) - { - item = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - if (item->seqnum == f->seqnum) { - cid = item; - break; - } - } - - if (cid) { - /* - * Transmission errors, timeouts and retransmissions might cause the - * same NEW_CONNECTION_ID frame to be received multiple times - */ - - if (cid->len != f->len - || ngx_strncmp(cid->id, f->cid, f->len) != 0 - || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) - { - /* - * ..a sequence number is used for different connection IDs, - * the endpoint MAY treat that receipt as a connection error - * of type PROTOCOL_VIOLATION. - */ - qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - qc->error_reason = "seqnum refers to different connection id/token"; - return NGX_ERROR; - } - - } else { - - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NGX_ERROR; - } - - cid->seqnum = f->seqnum; - cid->len = f->len; - ngx_memcpy(cid->id, f->cid, f->len); - - ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); - - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - - /* always use latest available connection id */ - if (f->seqnum > qc->client_seqnum) { - qc->scid.len = cid->len; - qc->scid.data = cid->id; - qc->client_seqnum = f->seqnum; - } - } - -retire: - - if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { - /* - * Once a sender indicates a Retire Prior To value, smaller values sent - * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver - * MUST ignore any Retire Prior To fields that do not increase the - * largest received Retire Prior To value. - */ - goto done; - } - - qc->max_retired_seqnum = f->retire; - - q = ngx_queue_head(&qc->client_ids); - - while (q != ngx_queue_sentinel(&qc->client_ids)) { - - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - q = ngx_queue_next(q); - - if (cid->seqnum >= f->retire) { - continue; - } - - /* this connection id must be retired */ - - if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_queue_remove(&cid->queue); - ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); - qc->nclient_ids--; - } - -done: - - if (qc->nclient_ids > qc->tp.active_connection_id_limit) { - /* - * After processing a NEW_CONNECTION_ID frame and - * adding and retiring active connection IDs, if the number of active - * connection IDs exceeds the value advertised in its - * active_connection_id_limit transport parameter, an endpoint MUST - * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. - */ - qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; - qc->error_reason = "too many connection ids received"; - return NGX_ERROR; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = level; - frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; - frame->u.retire_cid.sequence_number = seqnum; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) -{ - ngx_queue_t *q; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); - q = ngx_queue_next(q)) - { - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - if (sid->seqnum == f->sequence_number) { - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - break; - } - } - - return ngx_quic_issue_server_ids(c); -} - - -static ngx_int_t -ngx_quic_issue_server_ids(ngx_connection_t *c) -{ - ngx_str_t dcid; - ngx_uint_t n; - ngx_quic_frame_t *frame; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - u_char id[NGX_QUIC_SERVER_CID_LEN]; - - qc = ngx_quic_get_connection(c); - - n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); - - while (qc->nserver_ids < n) { - if (ngx_quic_create_server_id(c, id) != NGX_OK) { - return NGX_ERROR; - } - - dcid.len = NGX_QUIC_SERVER_CID_LEN; - dcid.data = id; - - sid = ngx_quic_insert_server_id(c, qc, &dcid); - if (sid == NULL) { - return NGX_ERROR; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; - frame->u.ncid.seqnum = sid->seqnum; - frame->u.ncid.retire = 0; - frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; - ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); - - if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, - frame->u.ncid.srt) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_quic_queue_frame(qc, frame); - } - - return NGX_OK; -} - - -static void -ngx_quic_clear_temp_server_ids(ngx_connection_t *c) -{ - ngx_queue_t *q, *next; - ngx_quic_server_id_t *sid; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear temp server ids"); - - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); - q = next) - { - next = ngx_queue_next(q); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - if (sid->seqnum != NGX_QUIC_UNSET_PN) { - continue; - } - - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - } -} - - -static ngx_quic_server_id_t * -ngx_quic_insert_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_str_t *id) -{ - ngx_str_t dcid; - ngx_quic_server_id_t *sid; - - sid = ngx_quic_alloc_server_id(c, qc); - if (sid == NULL) { - return NULL; - } - - sid->quic = qc; - - sid->seqnum = qc->server_seqnum; - - if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { - qc->server_seqnum++; - } - - sid->len = id->len; - ngx_memcpy(sid->id, id->data, id->len); - - ngx_queue_insert_tail(&qc->server_ids, &sid->queue); - qc->nserver_ids++; - - dcid.data = sid->id; - dcid.len = sid->len; - - ngx_insert_udp_connection(c, &sid->udp, &dcid); - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic insert server id seqnum:%uL id len:%uz %xV", - sid->seqnum, id->len, id); - - return sid; -} - - -static ngx_quic_client_id_t * -ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_queue_t *q; - ngx_quic_client_id_t *cid; - - if (!ngx_queue_empty(&qc->free_client_ids)) { - - q = ngx_queue_head(&qc->free_client_ids); - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - - ngx_queue_remove(&cid->queue); - - ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); - - } else { - - cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); - if (cid == NULL) { - return NULL; - } - } - - return cid; -} - - -static ngx_quic_server_id_t * -ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_queue_t *q; - ngx_quic_server_id_t *sid; - - if (!ngx_queue_empty(&qc->free_server_ids)) { - - q = ngx_queue_head(&qc->free_server_ids); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - ngx_queue_remove(&sid->queue); - - ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); - - } else { - - sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); - if (sid == NULL) { - return NULL; - } - } - - return sid; -} - - void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) { diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 0c31aa6a0..e026ee643 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -17,6 +17,7 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; #include +#include #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ @@ -44,10 +45,10 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ -#define NGX_QUIC_MAX_SERVER_IDS 8 - #define NGX_QUIC_BUFFER_SIZE 4096 +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + #define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) /* 0-RTT and 1-RTT data exist in the same packet number space, @@ -223,6 +224,9 @@ void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); + /********************************* DEBUG *************************************/ /* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c new file mode 100644 index 000000000..faf5d9c83 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.c @@ -0,0 +1,532 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_SERVER_IDS 8 + + +static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); +#if (NGX_QUIC_BPF) +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); +#endif +static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum); +static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_str_t *id); +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); +static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); + + +ngx_int_t +ngx_quic_setup_connection_ids(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_server_id_t *sid, *osid; + ngx_quic_client_id_t *cid; + + /* + * qc->nclient_ids = 0 + * qc->nserver_ids = 0 + * qc->max_retired_seqnum = 0 + */ + + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->server_ids); + ngx_queue_init(&qc->free_client_ids); + ngx_queue_init(&qc->free_server_ids); + + qc->odcid.len = pkt->odcid.len; + qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->odcid.data == NULL) { + return NGX_ERROR; + } + + qc->tp.original_dcid = qc->odcid; + + qc->scid.len = pkt->scid.len; + qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid); + if (qc->scid.data == NULL) { + return NGX_ERROR; + } + + qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { + return NGX_ERROR; + } + + qc->tp.initial_scid = qc->dcid; + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NGX_ERROR; + } + + cid->seqnum = 0; + cid->len = pkt->scid.len; + ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + qc->client_seqnum = 0; + + qc->server_seqnum = NGX_QUIC_UNSET_PN; + + osid = ngx_quic_insert_server_id(c, qc, &qc->odcid); + if (osid == NULL) { + return NGX_ERROR; + } + + qc->server_seqnum = 0; + + sid = ngx_quic_insert_server_id(c, qc, &qc->dcid); + if (sid == NULL) { + ngx_rbtree_delete(&c->listening->rbtree, &osid->udp.node); + return NGX_ERROR; + } + + c->udp = &sid->udp; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +{ + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + +#if (NGX_QUIC_BPF) + if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic bpf failed to generate socket key"); + /* ignore error, things still may work */ + } +#endif + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create server id %*xs", + (size_t) NGX_QUIC_SERVER_CID_LEN, id); + return NGX_OK; +} + + +#if (NGX_QUIC_BPF) + +static ngx_int_t +ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) +{ + int fd; + uint64_t cookie; + socklen_t optlen; + + fd = c->listening->fd; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic getsockopt(SO_COOKIE) failed"); + + return NGX_ERROR; + } + + ngx_quic_dcid_encode_key(id, cookie); + + return NGX_OK; +} + +#endif + + + + +ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { + return NGX_ERROR; + } + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) + { + /* + * ..a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NGX_ERROR; + } + + cid->seqnum = f->seqnum; + cid->len = f->len; + ngx_memcpy(cid->id, f->cid, f->len); + + ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + /* always use latest available connection id */ + if (f->seqnum > qc->client_seqnum) { + qc->scid.len = cid->len; + qc->scid.data = cid->id; + qc->client_seqnum = f->seqnum; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + /* this connection id must be retired */ + + if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + qc->nclient_ids--; + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_connection_id(ngx_connection_t *c, + enum ssl_encryption_level_t level, uint64_t seqnum) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = seqnum; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = ngx_queue_next(q)) + { + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum == f->sequence_number) { + ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + break; + } + } + + return ngx_quic_issue_server_ids(c); +} + + +ngx_int_t +ngx_quic_issue_server_ids(ngx_connection_t *c) +{ + ngx_str_t dcid; + ngx_uint_t n; + ngx_quic_frame_t *frame; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + u_char id[NGX_QUIC_SERVER_CID_LEN]; + + qc = ngx_quic_get_connection(c); + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); + + while (qc->nserver_ids < n) { + if (ngx_quic_create_server_id(c, id) != NGX_OK) { + return NGX_ERROR; + } + + dcid.len = NGX_QUIC_SERVER_CID_LEN; + dcid.data = id; + + sid = ngx_quic_insert_server_id(c, qc, &dcid); + if (sid == NULL) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} + + +void +ngx_quic_clear_temp_server_ids(ngx_connection_t *c) +{ + ngx_queue_t *q, *next; + ngx_quic_server_id_t *sid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clear temp server ids"); + + for (q = ngx_queue_head(&qc->server_ids); + q != ngx_queue_sentinel(&qc->server_ids); + q = next) + { + next = ngx_queue_next(q); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + if (sid->seqnum != NGX_QUIC_UNSET_PN) { + continue; + } + + ngx_queue_remove(q); + ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); + ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); + qc->nserver_ids--; + } +} + + +static ngx_quic_server_id_t * +ngx_quic_insert_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_str_t *id) +{ + ngx_str_t dcid; + ngx_quic_server_id_t *sid; + + sid = ngx_quic_alloc_server_id(c, qc); + if (sid == NULL) { + return NULL; + } + + sid->quic = qc; + + sid->seqnum = qc->server_seqnum; + + if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { + qc->server_seqnum++; + } + + sid->len = id->len; + ngx_memcpy(sid->id, id->data, id->len); + + ngx_queue_insert_tail(&qc->server_ids, &sid->queue); + qc->nserver_ids++; + + dcid.data = sid->id; + dcid.len = sid->len; + + ngx_insert_udp_connection(c, &sid->udp, &dcid); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic insert server id seqnum:%uL id len:%uz %xV", + sid->seqnum, id->len, id); + + return sid; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +static ngx_quic_server_id_t * +ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_server_id_t *sid; + + if (!ngx_queue_empty(&qc->free_server_ids)) { + + q = ngx_queue_head(&qc->free_server_ids); + sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + + ngx_queue_remove(&sid->queue); + + ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); + + } else { + + sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); + if (sid == NULL) { + return NULL; + } + } + + return sid; +} diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h new file mode 100644 index 000000000..cec09829d --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.h @@ -0,0 +1,25 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_setup_connection_ids(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); +void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); +ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); + +ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); +ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); + +#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ -- cgit v1.2.3 From 87ca8c087ae28ddf10e44a80714deb5570b07903 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:38:46 +0300 Subject: QUIC: separate files for frames related processing. --- auto/modules | 2 + src/event/quic/ngx_event_quic.c | 911 +--------------------------- src/event/quic/ngx_event_quic_connection.h | 4 +- src/event/quic/ngx_event_quic_frames.c | 912 +++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_frames.h | 42 ++ 5 files changed, 959 insertions(+), 912 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_frames.c create mode 100644 src/event/quic/ngx_event_quic_frames.h diff --git a/auto/modules b/auto/modules index 8a36a50e5..a35185741 100644 --- a/auto/modules +++ b/auto/modules @@ -1343,11 +1343,13 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_transport.h \ src/event/quic/ngx_event_quic_protection.h \ src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_frames.h \ src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_frames.c \ src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c" diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 818ef342c..cc4425044 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -36,10 +36,6 @@ #define NGX_QUIC_MAX_ACK_GAP 2 -typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - - #if BORINGSSL_API_VERSION >= 10 static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, @@ -122,17 +118,9 @@ static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, static void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f); -static ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, - ngx_quic_frame_handler_pt handler, void *data); -static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, - ngx_quic_frame_t *f, uint64_t offset_in); -static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); - static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, +ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); @@ -160,9 +148,6 @@ static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); -static ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, - size_t len); -static void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, @@ -192,22 +177,12 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); static void ngx_quic_shutdown_quic(ngx_connection_t *c); -static void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *frame); -static ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); -static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); -static ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, - size_t len); -static ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, - size_t limit); -static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, - size_t len); - static ngx_core_module_t ngx_quic_module_ctx = { ngx_string("quic"), @@ -247,225 +222,6 @@ static SSL_QUIC_METHOD quic_method = { #if (NGX_DEBUG) -static void -ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) -{ - u_char *p, *last, *pos, *end; - ssize_t n; - uint64_t gap, range, largest, smallest; - ngx_uint_t i; - u_char buf[NGX_MAX_ERROR_STR]; - - p = buf; - last = buf + sizeof(buf); - - switch (f->type) { - - case NGX_QUIC_FT_CRYPTO: - p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", - f->u.crypto.length, f->u.crypto.offset); - break; - - case NGX_QUIC_FT_PADDING: - p = ngx_slprintf(p, last, "PADDING"); - break; - - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - - p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", - f->u.ack.range_count, f->u.ack.delay); - - if (f->data) { - pos = f->data->buf->pos; - end = f->data->buf->last; - - } else { - pos = NULL; - end = NULL; - } - - largest = f->u.ack.largest; - smallest = f->u.ack.largest - f->u.ack.first_range; - - if (largest == smallest) { - p = ngx_slprintf(p, last, "%uL", largest); - - } else { - p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); - } - - for (i = 0; i < f->u.ack.range_count; i++) { - n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); - if (n == NGX_ERROR) { - break; - } - - pos += n; - - largest = smallest - gap - 2; - smallest = largest - range; - - if (largest == smallest) { - p = ngx_slprintf(p, last, " %uL", largest); - - } else { - p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); - } - } - - if (f->type == NGX_QUIC_FT_ACK_ECN) { - p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", - f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); - } - break; - - case NGX_QUIC_FT_PING: - p = ngx_slprintf(p, last, "PING"); - break; - - case NGX_QUIC_FT_NEW_CONNECTION_ID: - p = ngx_slprintf(p, last, - "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", - f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); - break; - - case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", - f->u.retire_cid.sequence_number); - break; - - case NGX_QUIC_FT_CONNECTION_CLOSE: - case NGX_QUIC_FT_CONNECTION_CLOSE_APP: - p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", - f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", - f->u.close.error_code); - - if (f->u.close.reason.len) { - p = ngx_slprintf(p, last, " %V", &f->u.close.reason); - } - - if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { - p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); - } - - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - - p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); - - if (f->u.stream.off) { - p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); - } - - if (f->u.stream.len) { - p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); - } - - if (f->u.stream.fin) { - p = ngx_slprintf(p, last, " fin:1"); - } - -#ifdef NGX_QUIC_DEBUG_FRAMES - { - ngx_chain_t *cl; - - p = ngx_slprintf(p, last, " data:"); - - for (cl = f->data; cl; cl = cl->next) { - p = ngx_slprintf(p, last, "%*xs", - cl->buf->last - cl->buf->pos, cl->buf->pos); - } - } -#endif - - break; - - case NGX_QUIC_FT_MAX_DATA: - p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", - f->u.max_data.max_data); - break; - - case NGX_QUIC_FT_RESET_STREAM: - p = ngx_slprintf(p, last, "RESET_STREAM" - " id:0x%xL error_code:0x%xL final_size:0x%xL", - f->u.reset_stream.id, f->u.reset_stream.error_code, - f->u.reset_stream.final_size); - break; - - case NGX_QUIC_FT_STOP_SENDING: - p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", - f->u.stop_sending.id, f->u.stop_sending.error_code); - break; - - case NGX_QUIC_FT_STREAMS_BLOCKED: - case NGX_QUIC_FT_STREAMS_BLOCKED2: - p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", - f->u.streams_blocked.limit, f->u.streams_blocked.bidi); - break; - - case NGX_QUIC_FT_MAX_STREAMS: - case NGX_QUIC_FT_MAX_STREAMS2: - p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", - f->u.max_streams.limit, f->u.max_streams.bidi); - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", - f->u.max_stream_data.id, f->u.max_stream_data.limit); - break; - - - case NGX_QUIC_FT_DATA_BLOCKED: - p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", - f->u.data_blocked.limit); - break; - - case NGX_QUIC_FT_STREAM_DATA_BLOCKED: - p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", - f->u.stream_data_blocked.id, - f->u.stream_data_blocked.limit); - break; - - case NGX_QUIC_FT_PATH_CHALLENGE: - p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", - sizeof(f->u.path_challenge.data), - f->u.path_challenge.data); - break; - - case NGX_QUIC_FT_PATH_RESPONSE: - p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", - sizeof(f->u.path_challenge.data), - f->u.path_challenge.data); - break; - - case NGX_QUIC_FT_NEW_TOKEN: - p = ngx_slprintf(p, last, "NEW_TOKEN"); - break; - - case NGX_QUIC_FT_HANDSHAKE_DONE: - p = ngx_slprintf(p, last, "HANDSHAKE DONE"); - break; - - default: - p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); - break; - } - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", - tx ? "tx" : "rx", ngx_quic_level_name(f->level), - p - buf, buf); -} - - static void ngx_quic_connstate_dbg(ngx_connection_t *c) { @@ -531,7 +287,6 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) #else -#define ngx_quic_log_frame(log, f, tx) #define ngx_quic_connstate_dbg(c) #endif @@ -3429,222 +3184,6 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) } -static ngx_int_t -ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) -{ - size_t full_len; - ngx_int_t rc; - ngx_queue_t *q; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - if (f->offset > fs->received) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic out-of-order frame: expecting:%uL got:%uL", - fs->received, f->offset); - - return ngx_quic_buffer_frame(c, fs, frame); - } - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; - } - - /* intersecting data range, frame modified */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; - } - - /* rc == NGX_OK */ - - fs->received += f->length; - - /* now check the queue if we can continue with buffered frames */ - - do { - q = ngx_queue_head(&fs->frames); - if (q == ngx_queue_sentinel(&fs->frames)) { - break; - } - - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &frame->u.ord; - - if (f->offset > fs->received) { - /* gap found, nothing more to do */ - break; - } - - full_len = f->length; - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - ngx_queue_remove(q); - fs->total -= f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic skipped buffered frame, total:%ui", - fs->total); - ngx_quic_free_frame(c, frame); - continue; - } - - /* frame was adjusted, proceed to input */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; - } - - fs->received += f->length; - fs->total -= full_len; - - ngx_queue_remove(q); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic consumed buffered frame, total:%ui", fs->total); - - ngx_quic_free_frame(c, frame); - - } while (1); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, - uint64_t offset_in) -{ - size_t tail, n; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - tail = offset_in - f->offset; - - if (tail >= f->length) { - /* range preceeding already received data or duplicate, ignore */ - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic old or duplicate data in ordered frame, ignored"); - return NGX_DONE; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic adjusted ordered frame data start to expected offset"); - - /* intersecting range: adjust data size */ - - f->offset += tail; - f->length -= tail; - - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - n = ngx_buf_size(b); - - if (n >= tail) { - b->pos += tail; - break; - } - - cl->buf->pos = cl->buf->last; - tail -= n; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame) -{ - ngx_queue_t *q; - ngx_quic_frame_t *dst, *item; - ngx_quic_ordered_frame_t *f, *df; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_buffer_frame"); - - f = &frame->u.ord; - - /* frame start offset is in the future, buffer it */ - - dst = ngx_quic_alloc_frame(c); - if (dst == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); - - dst->data = ngx_quic_copy_chain(c, frame->data, 0); - if (dst->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - df = &dst->u.ord; - - fs->total += f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ordered frame with unexpected offset:" - " buffered total:%ui", fs->total); - - if (ngx_queue_empty(&fs->frames)) { - ngx_queue_insert_after(&fs->frames, &dst->queue); - return NGX_OK; - } - - for (q = ngx_queue_last(&fs->frames); - q != ngx_queue_sentinel(&fs->frames); - q = ngx_queue_prev(q)) - { - item = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &item->u.ord; - - if (f->offset < df->offset) { - ngx_queue_insert_after(q, &dst->queue); - return NGX_OK; - } - } - - ngx_queue_insert_after(&fs->frames, &dst->queue); - - return NGX_OK; -} - - static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) @@ -3693,7 +3232,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } -static ngx_int_t +ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { int n, sslerr; @@ -4238,26 +3777,6 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, } -void -ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) -{ - ngx_quic_send_ctx_t *ctx; - - ctx = ngx_quic_get_send_ctx(qc, frame->level); - - ngx_queue_insert_tail(&ctx->frames, &frame->queue); - - frame->len = ngx_quic_create_frame(NULL, frame); - /* always succeeds */ - - if (qc->closing) { - return; - } - - ngx_post_event(&qc->push, &ngx_posted_events); -} - - static ngx_int_t ngx_quic_output(ngx_connection_t *c) { @@ -4601,97 +4120,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } -static ngx_int_t -ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) -{ - size_t shrink; - ngx_quic_frame_t *nf; - ngx_quic_ordered_frame_t *of, *onf; - - switch (f->type) { - case NGX_QUIC_FT_CRYPTO: - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - break; - - default: - return NGX_DECLINED; - } - - if ((size_t) f->len <= len) { - return NGX_OK; - } - - shrink = f->len - len; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic split frame now:%uz need:%uz shrink:%uz", - f->len, len, shrink); - - of = &f->u.ord; - - if (of->length <= shrink) { - return NGX_DECLINED; - } - - of->length -= shrink; - f->len = ngx_quic_create_frame(NULL, f); - - if ((size_t) f->len > len) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); - return NGX_ERROR; - } - - nf = ngx_quic_alloc_frame(c); - if (nf == NULL) { - return NGX_ERROR; - } - - *nf = *f; - onf = &nf->u.ord; - onf->offset += of->length; - onf->length = shrink; - nf->len = ngx_quic_create_frame(NULL, nf); - - nf->data = ngx_quic_split_bufs(c, f->data, of->length); - if (nf->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - ngx_queue_insert_after(&f->queue, &nf->queue); - - return NGX_OK; -} - - -static void -ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - - do { - q = ngx_queue_head(frames); - - if (q == ngx_queue_sentinel(frames)) { - break; - } - - ngx_queue_remove(q); - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_quic_free_frame(c, f); - } while (1); -} - - static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) { @@ -5836,46 +5264,6 @@ ngx_quic_shutdown_quic(ngx_connection_t *c) } -ngx_quic_frame_t * -ngx_quic_alloc_frame(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!ngx_queue_empty(&qc->free_frames)) { - - q = ngx_queue_head(&qc->free_frames); - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_queue_remove(&frame->queue); - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic reuse frame n:%ui", qc->nframes); -#endif - - } else { - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); - if (frame == NULL) { - return NULL; - } - -#ifdef NGX_QUIC_DEBUG_ALLOC - ++qc->nframes; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic alloc frame n:%ui", qc->nframes); -#endif - } - - ngx_memzero(frame, sizeof(ngx_quic_frame_t)); - - return frame; -} - static void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) @@ -5970,26 +5358,6 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) } -static void -ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) -{ - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (frame->data) { - ngx_quic_free_bufs(c, frame->data); - } - - ngx_queue_insert_head(&qc->free_frames, &frame->queue); - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic free frame n:%ui", qc->nframes); -#endif -} - - uint32_t ngx_quic_version(ngx_connection_t *c) { @@ -6002,278 +5370,3 @@ ngx_quic_version(ngx_connection_t *c) return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; } - - -static ngx_chain_t * -ngx_quic_alloc_buf(ngx_connection_t *c) -{ - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->free_bufs) { - cl = qc->free_bufs; - qc->free_bufs = cl->next; - - b = cl->buf; - b->pos = b->start; - b->last = b->start; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic reuse buffer n:%ui", qc->nbufs); -#endif - - return cl; - } - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); - if (b == NULL) { - return NULL; - } - - b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; - - cl->buf = b; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ++qc->nbufs; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic alloc buffer n:%ui", qc->nbufs); -#endif - - return cl; -} - - -static void -ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) -{ - ngx_buf_t *b, *shadow; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - while (in) { -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic free buffer n:%ui", qc->nbufs); -#endif - - cl = in; - in = in->next; - b = cl->buf; - - if (b->shadow) { - if (!b->last_shadow) { - b->recycled = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - do { - shadow = b->shadow; - b->shadow = qc->free_shadow_bufs; - qc->free_shadow_bufs = b; - b = shadow; - } while (b->recycled); - - if (b->shadow) { - b->last_shadow = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - cl->buf = b; - } - - cl->next = qc->free_bufs; - qc->free_bufs = cl; - } -} - - -static ngx_chain_t * -ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (len) { - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - b = cl->buf; - n = ngx_min((size_t) (b->end - b->last), len); - - b->last = ngx_cpymem(b->last, data, n); - - data += n; - len -= n; - - *ll = cl; - ll = &cl->next; - } - - *ll = NULL; - - return out; -} - - -static ngx_chain_t * -ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (in) { - if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { - in = in->next; - continue; - } - - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - *ll = cl; - ll = &cl->next; - - b = cl->buf; - - while (in && b->last != b->end) { - - n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); - - if (limit > 0 && n > limit) { - n = limit; - } - - b->last = ngx_cpymem(b->last, in->buf->pos, n); - - in->buf->pos += n; - if (in->buf->pos == in->buf->last) { - in = in->next; - } - - if (limit > 0) { - if (limit == n) { - goto done; - } - - limit -= n; - } - } - - } - -done: - - *ll = NULL; - - return out; -} - - -static ngx_chain_t * -ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *out; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - while (in) { - n = ngx_buf_size(in->buf); - - if (n == len) { - out = in->next; - in->next = NULL; - return out; - } - - if (n > len) { - break; - } - - len -= n; - in = in->next; - } - - if (in == NULL) { - return NULL; - } - - /* split in->buf by creating shadow bufs which reference it */ - - if (in->buf->shadow == NULL) { - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - return NGX_CHAIN_ERROR; - } - } - - *b = *in->buf; - b->shadow = in->buf; - b->last_shadow = 1; - in->buf = b; - } - - out = ngx_alloc_chain_link(c->pool); - if (out == NULL) { - return NGX_CHAIN_ERROR; - } - - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - ngx_free_chain(c->pool, out); - return NGX_CHAIN_ERROR; - } - } - - out->buf = b; - out->next = in->next; - in->next = NULL; - - *b = *in->buf; - b->last_shadow = 0; - b->pos = b->pos + len; - - in->buf->shadow = b; - in->buf->last = in->buf->pos + len; - - return out; -} diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index e026ee643..a14bd65b4 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -16,6 +16,7 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; +#include #include #include @@ -45,7 +46,6 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ -#define NGX_QUIC_BUFFER_SIZE 4096 #define NGX_QUIC_UNSET_PN (uint64_t) -1 @@ -219,8 +219,6 @@ struct ngx_quic_connection_s { }; -ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); -void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c new file mode 100644 index 000000000..33af5ddd3 --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.c @@ -0,0 +1,912 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_BUFFER_SIZE 4096 + + +static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); +static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, + size_t len); + +static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); +static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, + ngx_quic_frame_t *f, uint64_t offset_in); + + +ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse frame n:%ui", qc->nframes); +#endif + + } else { + frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ++qc->nframes; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc frame n:%ui", qc->nframes); +#endif + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + return frame; +} + + +void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (frame->data) { + ngx_quic_free_bufs(c, frame->data); + } + + ngx_queue_insert_head(&qc->free_frames, &frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free frame n:%ui", qc->nframes); +#endif +} + + +static void +ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_buf_t *b, *shadow; + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer n:%ui", qc->nbufs); +#endif + + cl = in; + in = in->next; + b = cl->buf; + + if (b->shadow) { + if (!b->last_shadow) { + b->recycled = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + do { + shadow = b->shadow; + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + b = shadow; + } while (b->recycled); + + if (b->shadow) { + b->last_shadow = 1; + ngx_free_chain(c->pool, cl); + continue; + } + + cl->buf = b; + } + + cl->next = qc->free_bufs; + qc->free_bufs = cl; + } +} + + +void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + +void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_send_ctx_t *ctx; + + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_queue_insert_tail(&ctx->frames, &frame->queue); + + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +ngx_int_t +ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) +{ + size_t shrink; + ngx_quic_frame_t *nf; + ngx_quic_ordered_frame_t *of, *onf; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + nf = ngx_quic_alloc_frame(c); + if (nf == NULL) { + return NGX_ERROR; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + + nf->data = ngx_quic_split_bufs(c, f->data, of->length); + if (nf->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *out; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (in) { + n = ngx_buf_size(in->buf); + + if (n == len) { + out = in->next; + in->next = NULL; + return out; + } + + if (n > len) { + break; + } + + len -= n; + in = in->next; + } + + if (in == NULL) { + return NULL; + } + + /* split in->buf by creating shadow bufs which reference it */ + + if (in->buf->shadow == NULL) { + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + return NGX_CHAIN_ERROR; + } + } + + *b = *in->buf; + b->shadow = in->buf; + b->last_shadow = 1; + in->buf = b; + } + + out = ngx_alloc_chain_link(c->pool); + if (out == NULL) { + return NGX_CHAIN_ERROR; + } + + if (qc->free_shadow_bufs) { + b = qc->free_shadow_bufs; + qc->free_shadow_bufs = b->shadow; + + } else { + b = ngx_alloc_buf(c->pool); + if (b == NULL) { + ngx_free_chain(c->pool, out); + return NGX_CHAIN_ERROR; + } + } + + out->buf = b; + out->next = in->next; + in->next = NULL; + + *b = *in->buf; + b->last_shadow = 0; + b->pos = b->pos + len; + + in->buf->shadow = b; + in->buf->last = in->buf->pos + len; + + return out; +} + + +ngx_chain_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->free_bufs) { + cl = qc->free_bufs; + qc->free_bufs = cl->next; + + b = cl->buf; + b->pos = b->start; + b->last = b->start; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse buffer n:%ui", qc->nbufs); +#endif + + return cl; + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); + if (b == NULL) { + return NULL; + } + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + + cl->buf = b; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ++qc->nbufs; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc buffer n:%ui", qc->nbufs); +#endif + + return cl; +} + + +ngx_chain_t * +ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + b = cl->buf; + n = ngx_min((size_t) (b->end - b->last), len); + + b->last = ngx_cpymem(b->last, data, n); + + data += n; + len -= n; + + *ll = cl; + ll = &cl->next; + } + + *ll = NULL; + + return out; +} + + +ngx_chain_t * +ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *out, **ll; + + out = NULL; + ll = &out; + + while (in) { + if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { + in = in->next; + continue; + } + + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + + while (in && b->last != b->end) { + + n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); + + if (limit > 0 && n > limit) { + n = limit; + } + + b->last = ngx_cpymem(b->last, in->buf->pos, n); + + in->buf->pos += n; + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + if (limit > 0) { + if (limit == n) { + goto done; + } + + limit -= n; + } + } + } + +done: + + *ll = NULL; + + return out; +} + + +ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +ngx_int_t +ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) +{ + size_t full_len; + ngx_int_t rc; + ngx_queue_t *q; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + if (f->offset > fs->received) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic out-of-order frame: expecting:%uL got:%uL", + fs->received, f->offset); + + return ngx_quic_buffer_frame(c, fs, frame); + } + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; + } + + /* intersecting data range, frame modified */ + } + + /* f->offset == fs->received */ + + rc = handler(c, frame, data); + if (rc == NGX_ERROR) { + return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; + } + + /* rc == NGX_OK */ + + fs->received += f->length; + + /* now check the queue if we can continue with buffered frames */ + + do { + q = ngx_queue_head(&fs->frames); + if (q == ngx_queue_sentinel(&fs->frames)) { + break; + } + + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &frame->u.ord; + + if (f->offset > fs->received) { + /* gap found, nothing more to do */ + break; + } + + full_len = f->length; + + if (f->offset < fs->received) { + + if (ngx_quic_adjust_frame_offset(c, frame, fs->received) + == NGX_DONE) + { + /* old/duplicate data range */ + ngx_queue_remove(q); + fs->total -= f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic skipped buffered frame, total:%ui", + fs->total); + ngx_quic_free_frame(c, frame); + continue; + } + + /* frame was adjusted, proceed to input */ + } + + /* f->offset == fs->received */ + + rc = handler(c, frame, data); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + + } else if (rc == NGX_DONE) { + /* handler destroyed stream, queue no longer exists */ + return NGX_OK; + } + + fs->received += f->length; + fs->total -= full_len; + + ngx_queue_remove(q); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic consumed buffered frame, total:%ui", fs->total); + + ngx_quic_free_frame(c, frame); + + } while (1); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, + uint64_t offset_in) +{ + size_t tail, n; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_quic_ordered_frame_t *f; + + f = &frame->u.ord; + + tail = offset_in - f->offset; + + if (tail >= f->length) { + /* range preceeding already received data or duplicate, ignore */ + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic old or duplicate data in ordered frame, ignored"); + return NGX_DONE; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic adjusted ordered frame data start to expected offset"); + + /* intersecting range: adjust data size */ + + f->offset += tail; + f->length -= tail; + + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + n = ngx_buf_size(b); + + if (n >= tail) { + b->pos += tail; + break; + } + + cl->buf->pos = cl->buf->last; + tail -= n; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, + ngx_quic_frame_t *frame) +{ + ngx_queue_t *q; + ngx_quic_frame_t *dst, *item; + ngx_quic_ordered_frame_t *f, *df; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_buffer_frame"); + + f = &frame->u.ord; + + /* frame start offset is in the future, buffer it */ + + dst = ngx_quic_alloc_frame(c); + if (dst == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); + + dst->data = ngx_quic_copy_chain(c, frame->data, 0); + if (dst->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + df = &dst->u.ord; + + fs->total += f->length; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ordered frame with unexpected offset:" + " buffered total:%ui", fs->total); + + if (ngx_queue_empty(&fs->frames)) { + ngx_queue_insert_after(&fs->frames, &dst->queue); + return NGX_OK; + } + + for (q = ngx_queue_last(&fs->frames); + q != ngx_queue_sentinel(&fs->frames); + q = ngx_queue_prev(q)) + { + item = ngx_queue_data(q, ngx_quic_frame_t, queue); + f = &item->u.ord; + + if (f->offset < df->offset) { + ngx_queue_insert_after(q, &dst->queue); + return NGX_OK; + } + } + + ngx_queue_insert_after(&fs->frames, &dst->queue); + + return NGX_OK; +} + + +#if (NGX_DEBUG) + +void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range, largest, smallest; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", + f->u.ack.range_count, f->u.ack.delay); + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", + f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), + p - buf, buf); +} + +#endif diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h new file mode 100644 index 000000000..2066d9516 --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.h @@ -0,0 +1,42 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ +#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ + + +#include +#include + + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); +void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); + +ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); +ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, + size_t len); +ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, + size_t limit); + +ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, + ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, + ngx_quic_frame_handler_pt handler, void *data); + +#if (NGX_DEBUG) +void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); +#else +#define ngx_quic_log_frame(log, f, tx) +#endif + +#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */ -- cgit v1.2.3 From 9326156b8b2599a56632a3afd5f66a0923d595fe Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:40:00 +0300 Subject: QUIC: separate files for stream related processing. --- auto/modules | 6 +- src/event/quic/ngx_event_quic.c | 2319 +++++++--------------------- src/event/quic/ngx_event_quic_connection.h | 4 + src/event/quic/ngx_event_quic_streams.c | 1268 +++++++++++++++ src/event/quic/ngx_event_quic_streams.h | 43 + 5 files changed, 1837 insertions(+), 1803 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_streams.c create mode 100644 src/event/quic/ngx_event_quic_streams.h diff --git a/auto/modules b/auto/modules index a35185741..ba0131da6 100644 --- a/auto/modules +++ b/auto/modules @@ -1345,13 +1345,15 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connection.h \ src/event/quic/ngx_event_quic_frames.h \ src/event/quic/ngx_event_quic_connid.h \ - src/event/quic/ngx_event_quic_migration.h" + src/event/quic/ngx_event_quic_migration.h \ + src/event/quic/ngx_event_quic_streams.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ src/event/quic/ngx_event_quic_frames.c \ src/event/quic/ngx_event_quic_connid.c \ - src/event/quic/ngx_event_quic_migration.c" + src/event/quic/ngx_event_quic_migration.c \ + src/event/quic/ngx_event_quic_streams.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cc4425044..703425085 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -21,8 +21,6 @@ */ #define NGX_QUIC_MAX_BUFFERED 65535 -#define NGX_QUIC_STREAM_GONE (void *) -1 - /* * Endpoints MUST discard packets that are too small to be valid QUIC * packets. With the set of AEAD functions defined in [QUIC-TLS], @@ -80,8 +78,6 @@ static void ngx_quic_input_handler(ngx_event_t *rev); static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); static void ngx_quic_close_timer_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, - ngx_quic_connection_t *qc); static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf); @@ -115,34 +111,12 @@ static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_msec_t *send_time); static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, enum ssl_encryption_level_t level, ngx_msec_t send_time); -static void ngx_quic_handle_stream_ack(ngx_connection_t *c, - ngx_quic_frame_t *f); static 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_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); -static ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -static ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); -static ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, - ngx_quic_max_data_frame_t *f); -static ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); -static ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); -static ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); -static ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); -static ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); -static 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); - -static ngx_int_t ngx_quic_output(ngx_connection_t *c); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); @@ -160,24 +134,6 @@ static void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static void ngx_quic_push_handler(ngx_event_t *ev); -static void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -static ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, - uint64_t id); -static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, - uint64_t id); -static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, - uint64_t id, size_t rcvbuf_size); -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 ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, - ngx_chain_t *in, off_t limit); -static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); -static void ngx_quic_stream_cleanup_handler(void *data); -static void ngx_quic_shutdown_quic(ngx_connection_t *c); - static void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_congestion_lost(ngx_connection_t *c, @@ -1607,94 +1563,6 @@ ngx_quic_close_timer_handler(ngx_event_t *ev) } -static ngx_int_t -ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) -{ - ngx_event_t *rev, *wev; - ngx_rbtree_t *tree; - ngx_rbtree_node_t *node; - ngx_quic_stream_t *qs; - -#if (NGX_DEBUG) - ngx_uint_t ns; -#endif - - tree = &qc->streams.tree; - - if (tree->root == tree->sentinel) { - return NGX_OK; - } - -#if (NGX_DEBUG) - ns = 0; -#endif - - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - - rev = qs->c->read; - rev->error = 1; - rev->ready = 1; - - wev = qs->c->write; - wev->error = 1; - wev->ready = 1; - - ngx_post_event(rev, &ngx_posted_events); - - if (rev->timer_set) { - ngx_del_timer(rev); - } - -#if (NGX_DEBUG) - ns++; -#endif - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic connection has %ui active streams", ns); - - return NGX_AGAIN; -} - - -ngx_int_t -ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) -{ - ngx_event_t *wev; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_RESET_STREAM; - frame->u.reset_stream.id = qs->id; - frame->u.reset_stream.error_code = err; - frame->u.reset_stream.final_size = c->sent; - - ngx_quic_queue_frame(qc, frame); - - wev = c->write; - wev->error = 1; - wev->ready = 1; - - return NGX_OK; -} - - static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { @@ -3152,38 +3020,6 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) } -static void -ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - uint64_t sent, unacked; - ngx_event_t *wev; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (sn == NULL) { - return; - } - - wev = sn->c->write; - sent = sn->c->sent; - unacked = sent - sn->acked; - - if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } - - sn->acked += f->u.stream.length; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, - "quic stream ack len:%uL acked:%uL unacked:%uL", - f->u.stream.length, sn->acked, sent - sn->acked); -} - - static ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) @@ -3334,1904 +3170,785 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) } -static ngx_int_t -ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) +ngx_int_t +ngx_quic_output(ngx_connection_t *c) { - size_t window; - uint64_t last; - ngx_buf_t *b; - ngx_pool_t *pool; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; - ngx_quic_frames_stream_t *fs; - - qc = ngx_quic_get_connection(c); - f = &frame->u.stream; - - if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - /* no overflow since both values are 62-bit */ - last = f->offset + f->length; - - sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + off_t max; + size_t len, min, in_flight; + ssize_t n; + u_char *p; + ngx_uint_t i, pad; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->stream_id); + c->log->action = "sending frames"; - if (sn == NULL) { - return NGX_ERROR; - } + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } + in_flight = cg->in_flight; - sc = sn->c; - fs = &sn->fs; - b = sn->b; - window = b->end - b->last; + for ( ;; ) { + p = dst; - if (last > window) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; - goto cleanup; - } + len = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn) - != NGX_OK) - { - goto cleanup; + if (!qc->validated) { + max = qc->received * 3; + max = (c->sent >= max) ? 0 : max - c->sent; + len = ngx_min(len, (size_t) max); } - sc->listening->handler(sc); - - return NGX_OK; - } - - fs = &sn->fs; - b = sn->b; - window = (b->pos - b->start) + (b->end - b->last); - - if (last > fs->received && last - fs->received > window) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; - return NGX_ERROR; - } - - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn); - -cleanup: - - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - uint64_t id; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_chain_t *cl; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; + pad = ngx_quic_get_padding_level(c); - qc = ngx_quic_get_connection(c); - sn = data; + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - f = &frame->u.stream; - id = f->stream_id; + ctx = &qc->send_ctx[i]; - b = sn->b; + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } - if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no space in stream buffer"); - return NGX_ERROR; - } + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - if ((size_t) (b->end - b->last) < f->length) { - b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); - b->pos = b->start; - } + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } - for (cl = frame->data; cl; cl = cl->next) { - b->last = ngx_cpymem(b->last, cl->buf->pos, - cl->buf->last - cl->buf->pos); - } + p += n; + len -= n; + } - rev = sn->c->read; - rev->ready = 1; + len = p - dst; + if (len == 0) { + break; + } - if (f->fin) { - rev->pending_eof = 1; + n = ngx_quic_send(c, dst, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } } - if (rev->active) { - rev->handler(rev); + if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - /* check if stream was destroyed by handler */ - if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { - return NGX_DONE; - } + ngx_quic_set_lost_timer(c); return NGX_OK; } -static ngx_int_t -ngx_quic_handle_max_data_frame(ngx_connection_t *c, - ngx_quic_max_data_frame_t *f) +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) { - ngx_event_t *wev; - ngx_rbtree_t *tree; - ngx_rbtree_node_t *node; - ngx_quic_stream_t *qs; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qc = ngx_quic_get_connection(c); - tree = &qc->streams.tree; + /* + * 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes + */ - if (f->max_data <= qc->streams.send_max_data) { - return NGX_OK; - } + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - if (qc->streams.sent >= qc->streams.send_max_data) { + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - wev = qs->c->write; + if (f->need_ack) { + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); + if (ngx_queue_empty(&ctx->frames)) { + return 0; } + + return 1; } } - qc->streams.send_max_data = f->max_data; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) -{ - return NGX_OK; + return NGX_QUIC_SEND_CTX_LAST; } static ngx_int_t -ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - size_t n; - ngx_buf_t *b; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *sn; + ngx_msec_t delay; ngx_quic_connection_t *qc; - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; + if (!ctx->send_ack) { + return NGX_OK; } - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + if (ctx->level == ssl_encryption_application) { - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); - if (sn == NULL) { - return NGX_ERROR; - } + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } - if (sn == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - - b = sn->b; - n = b->end - b->last; - - sn->c->listening->handler(sn->c); - - } else { - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); } - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { return NGX_ERROR; } - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = f->id; - frame->u.max_stream_data.limit = n; - - ngx_quic_queue_frame(qc, frame); + ctx->send_ack = 0; return NGX_OK; } -static ngx_int_t -ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) { - uint64_t sent; - ngx_event_t *wev; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - if (f->limit > sn->send_max_data) { - sn->send_max_data = f->limit; - } - - sn->c->listening->handler(sn->c); - - return NGX_OK; - } - - if (f->limit <= sn->send_max_data) { - return NGX_OK; - } - - sent = sn->c->sent; - - if (sent >= sn->send_max_data) { - wev = sn->c->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } - } - - sn->send_max_data = f->limit; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) -{ - ngx_event_t *rev; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = sn->c; - - rev = sc->read; - rev->error = 1; - rev->ready = 1; - - sc->listening->handler(sc); - - return NGX_OK; - } - - rev = sn->c->read; - rev->error = 1; - rev->ready = 1; - - if (rev->active) { - rev->handler(rev); - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) -{ - ngx_event_t *wev; - ngx_connection_t *sc; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NGX_ERROR; - } - - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); - - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); - - if (sn == NULL) { - return NGX_ERROR; - } - - if (sn == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = sn->c; - - wev = sc->write; - wev->error = 1; - wev->ready = 1; - - sc->listening->handler(sc); - - return NGX_OK; - } - - wev = sn->c->write; - wev->error = 1; - wev->ready = 1; - - if (wev->active) { - wev->handler(wev); - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_max_streams_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) -{ - ngx_quic_connection_t *qc; - - 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; - - 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; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_uni:%uL", f->limit); - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_output(ngx_connection_t *c) -{ - off_t max; - size_t len, min, in_flight; - ssize_t n; - u_char *p; - ngx_uint_t i, pad; - ngx_quic_send_ctx_t *ctx; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "sending frames"; - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - in_flight = cg->in_flight; - - for ( ;; ) { - p = dst; - - len = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - if (!qc->validated) { - max = qc->received * 3; - max = (c->sent >= max) ? 0 : max - c->sent; - len = ngx_min(len, (size_t) max); - } - - pad = ngx_quic_get_padding_level(c); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) - ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - - n = ngx_quic_output_packet(c, ctx, p, len, min); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - - p += n; - len -= n; - } - - len = p - dst; - if (len == 0) { - break; - } - - n = ngx_quic_send(c, dst, len); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - } - - if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - } - - ngx_quic_set_lost_timer(c); - - return NGX_OK; -} - - -static ngx_uint_t -ngx_quic_get_padding_level(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - /* - * 14.1. Initial Datagram Size - * - * Similarly, a server MUST expand the payload of all UDP datagrams - * carrying ack-eliciting Initial packets to at least the smallest - * allowed maximum datagram size of 1200 bytes - */ - - qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->need_ack) { - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - return 1; - } - } - - return NGX_QUIC_SEND_CTX_LAST; -} - - -static ngx_int_t -ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - ngx_msec_t delay; - ngx_quic_connection_t *qc; - - if (!ctx->send_ack) { - return NGX_OK; - } - - if (ctx->level == ssl_encryption_application) { - - delay = ngx_current_msec - ctx->ack_delay_start; - qc = ngx_quic_get_connection(c); - - if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP - && delay < qc->tp.max_ack_delay) - { - if (!qc->push.timer_set && !qc->closing) { - ngx_add_timer(&qc->push, - qc->tp.max_ack_delay - delay); - } - - return NGX_OK; - } - } - - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - ctx->send_ack = 0; - - return NGX_OK; -} - - -static ssize_t -ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min) -{ - size_t len, hlen, pad_len; - u_char *p; - ssize_t flen; - ngx_str_t out, res; - ngx_int_t rc; - ngx_uint_t nframes; - ngx_msec_t now; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_header_t pkt; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output %s packet max:%uz min:%uz", - ngx_quic_level_name(ctx->level), max, min); - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - hlen = (ctx->level == ssl_encryption_application) - ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; - - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - now = ngx_current_msec; - nframes = 0; - p = src; - len = 0; - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (!pkt.need_ack && f->need_ack && max > cg->window) { - max = cg->window; - } - - if (hlen + len >= max) { - break; - } - - if (hlen + len + f->len > max) { - rc = ngx_quic_split_frame(c, f, max - hlen - len); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - break; - } - } - - if (f->need_ack) { - pkt.need_ack = 1; - } - - ngx_quic_log_frame(c->log, f, 1); - - flen = ngx_quic_create_frame(p, f); - if (flen == -1) { - return NGX_ERROR; - } - - len += flen; - p += flen; - - f->pnum = ctx->pnum; - f->first = now; - f->last = now; - f->plen = 0; - - nframes++; - - if (f->flush) { - break; - } - } - - if (nframes == 0) { - return 0; - } - - out.data = src; - out.len = len; - - pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - - if (ctx->level == ssl_encryption_initial) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - - } else if (ctx->level == ssl_encryption_handshake) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; - - } else { - if (qc->key_phase) { - pkt.flags |= NGX_QUIC_PKT_KPHASE; - } - } - - ngx_quic_set_packet_number(&pkt, ctx); - - pkt.version = qc->version; - pkt.log = c->log; - pkt.level = ctx->level; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; - - pad_len = 4; - - if (min) { - hlen = EVP_GCM_TLS_TAG_LEN - + ngx_quic_create_header(&pkt, NULL, out.len, NULL); - - if (min > hlen + pad_len) { - pad_len = min - hlen; - } - } - - if (out.len < pad_len) { - ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); - out.len = pad_len; - } - - pkt.payload = out; - - res.data = data; - - ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet tx %s bytes:%ui" - " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, - pkt.number, pkt.num_len, pkt.trunc); - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - - ctx->pnum++; - - if (pkt.need_ack) { - /* move frames into the sent queue to wait for ack */ - - if (!qc->closing) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - f->plen = res.len; - - do { - q = ngx_queue_head(&ctx->frames); - ngx_queue_remove(q); - ngx_queue_insert_tail(&ctx->sent, q); - } while (--nframes); - } - - cg->in_flight += res.len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", cg->in_flight); - } - - while (nframes--) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - return res.len; -} - - -static ssize_t -ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) -{ - ngx_buf_t b; - ngx_chain_t cl, *res; - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - b.pos = b.start = buf; - b.last = b.end = buf + len; - b.last_buf = 1; - b.temporary = 1; - - cl.buf = &b; - cl.next= NULL; - - res = c->send_chain(c, &cl, 0); - if (res == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - return len; -} - - -static void -ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) -{ - uint64_t delta; - - delta = ctx->pnum - ctx->largest_ack; - pkt->number = ctx->pnum; - - if (delta <= 0x7F) { - pkt->num_len = 1; - pkt->trunc = ctx->pnum & 0xff; - - } else if (delta <= 0x7FFF) { - pkt->num_len = 2; - pkt->flags |= 0x1; - pkt->trunc = ctx->pnum & 0xffff; - - } else if (delta <= 0x7FFFFF) { - pkt->num_len = 3; - pkt->flags |= 0x2; - pkt->trunc = ctx->pnum & 0xffffff; - - } else { - pkt->num_len = 4; - pkt->flags |= 0x3; - pkt->trunc = ctx->pnum & 0xffffffff; - } -} - - -static void -ngx_quic_pto_handler(ngx_event_t *ev) -{ - ngx_uint_t i; - ngx_msec_t now; - ngx_queue_t *q, *next; - ngx_connection_t *c; - ngx_quic_frame_t *f; - 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; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - q = ngx_queue_head(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->pnum <= ctx->largest_ack - && ctx->largest_ack != NGX_QUIC_UNSET_PN) - { - continue; - } - - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { - continue; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pto %s pto_count:%ui", - ngx_quic_level_name(ctx->level), qc->pto_count); - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - /* void */) - { - next = ngx_queue_next(q); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->type == NGX_QUIC_FT_PING) { - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - q = next; - } - - for (q = ngx_queue_head(&ctx->sent); - q != ngx_queue_sentinel(&ctx->sent); - /* void */) - { - next = ngx_queue_next(q); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->type == NGX_QUIC_FT_PING) { - ngx_quic_congestion_lost(c, f); - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - q = next; - } - - /* enforce 2 udp datagrams */ - - f = ngx_quic_alloc_frame(c); - if (f == NULL) { - break; - } - - f->level = ctx->level; - f->type = NGX_QUIC_FT_PING; - f->flush = 1; - - ngx_quic_queue_frame(qc, f); - - f = ngx_quic_alloc_frame(c); - if (f == NULL) { - break; - } - - f->level = ctx->level; - f->type = NGX_QUIC_FT_PING; - - ngx_quic_queue_frame(qc, f); - } - - qc->pto_count++; - - ngx_quic_connstate_dbg(c); -} - - -static void -ngx_quic_push_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); - - c = ev->data; - - if (ngx_quic_output(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - return; - } - - ngx_quic_connstate_dbg(c); -} - - -static -void ngx_quic_lost_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); - - c = ev->data; - - if (ngx_quic_detect_lost(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - } - - ngx_quic_connstate_dbg(c); -} - - -static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c) -{ - ngx_uint_t i; - ngx_msec_t now, wait, thr; - ngx_queue_t *q; - ngx_quic_frame_t *start; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - now = ngx_current_msec; - thr = ngx_quic_lost_threshold(qc); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { - continue; - } - - while (!ngx_queue_empty(&ctx->sent)) { - - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (start->pnum > ctx->largest_ack) { - break; - } - - wait = start->last + thr - now; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", - start->pnum, thr, (ngx_int_t) wait, start->level); - - if ((ngx_msec_int_t) wait > 0 - && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) - { - break; - } - - ngx_quic_resend_frames(c, ctx); - } - } - - ngx_quic_set_lost_timer(c); - - return NGX_OK; -} - - -static void -ngx_quic_set_lost_timer(ngx_connection_t *c) -{ - ngx_uint_t i; + size_t len, hlen, pad_len; + u_char *p; + ssize_t flen; + ngx_str_t out, res; + ngx_int_t rc; + ngx_uint_t nframes; ngx_msec_t now; ngx_queue_t *q; - ngx_msec_int_t lost, pto, w; ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; + ngx_quic_header_t pkt; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - qc = ngx_quic_get_connection(c); - now = ngx_current_msec; - - lost = -1; - pto = -1; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ctx = &qc->send_ctx[i]; - - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { - q = ngx_queue_head(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); - - if (f->pnum <= ctx->largest_ack) { - if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { - w = 0; - } - - if (lost == -1 || w < lost) { - lost = w; - } - } - } - - q = ngx_queue_last(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); - - if (w < 0) { - w = 0; - } - - if (pto == -1 || w < pto) { - pto = w; - } - } - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - if (lost != -1) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic lost timer lost:%M", lost); - - qc->pto.handler = ngx_quic_lost_handler; - ngx_add_timer(&qc->pto, lost); - return; - } - - if (pto != -1) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic lost timer pto:%M", pto); - - qc->pto.handler = ngx_quic_pto_handler; - ngx_add_timer(&qc->pto, pto); - return; + if (ngx_queue_empty(&ctx->frames)) { + return 0; } - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); -} + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; -static void -ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t n; - ngx_buf_t *b; - ngx_queue_t *q; - ngx_quic_frame_t *f, *start; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; + hlen = (ctx->level == ssl_encryption_application) + ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; - qc = ngx_quic_get_connection(c); - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); + hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic resend packet pnum:%uL", start->pnum); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - ngx_quic_congestion_lost(c, start); + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; - do { + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (f->pnum != start->pnum) { - break; + if (!pkt.need_ack && f->need_ack && max > cg->window) { + max = cg->window; } - q = ngx_queue_next(q); - - ngx_queue_remove(&f->queue); - - switch (f->type) { - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - if (ctx->level == ssl_encryption_application) { - /* force generation of most recent acknowledgment */ - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } - - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_PING: - case NGX_QUIC_FT_PATH_RESPONSE: - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_MAX_DATA: - f->u.max_data.max_data = qc->streams.recv_max_data; - ngx_quic_queue_frame(qc, f); - break; - - 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; - ngx_quic_queue_frame(qc, f); + if (hlen + len >= max) { break; + } - case NGX_QUIC_FT_MAX_STREAM_DATA: - sn = ngx_quic_find_stream(&qc->streams.tree, - f->u.max_stream_data.id); - if (sn == NULL) { - ngx_quic_free_frame(c, f); - break; - } - - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + if (hlen + len + f->len > max) { + rc = ngx_quic_split_frame(c, f, max - hlen - len); - if (f->u.max_stream_data.limit < n) { - f->u.max_stream_data.limit = n; + if (rc == NGX_ERROR) { + return NGX_ERROR; } - ngx_quic_queue_frame(qc, f); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - - if (sn && sn->c->write->error) { - /* RESET_STREAM was sent */ - ngx_quic_free_frame(c, f); + if (rc == NGX_DECLINED) { break; } - - /* fall through */ - - default: - ngx_queue_insert_tail(&ctx->frames, &f->queue); } - } while (q != ngx_queue_sentinel(&ctx->sent)); - - if (qc->closing) { - return; - } - - ngx_post_event(&qc->push, &ngx_posted_events); -} - - -ngx_connection_t * -ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) -{ - size_t rcvbuf_size; - uint64_t id; - ngx_quic_stream_t *qs, *sn; - ngx_quic_connection_t *qc; - - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); - - if (bidi) { - if (qc->streams.server_streams_bidi - >= qc->streams.server_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); - return NULL; + if (f->need_ack) { + pkt.need_ack = 1; } - id = (qc->streams.server_streams_bidi << 2) - | NGX_QUIC_STREAM_SERVER_INITIATED; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_bidi, - qc->streams.server_max_streams_bidi, id); - - qc->streams.server_streams_bidi++; - rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; + ngx_quic_log_frame(c->log, f, 1); - } else { - if (qc->streams.server_streams_uni - >= qc->streams.server_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); - return NULL; + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; } - id = (qc->streams.server_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" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_uni, - qc->streams.server_max_streams_uni, id); - - qc->streams.server_streams_uni++; - rcvbuf_size = 0; - } - - sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); - if (sn == NULL) { - return NULL; - } - - return sn->c; -} - - -static void -ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) -{ - ngx_rbtree_node_t **p; - ngx_quic_stream_t *qn, *qnt; + len += flen; + p += flen; - for ( ;; ) { - qn = (ngx_quic_stream_t *) node; - qnt = (ngx_quic_stream_t *) temp; + f->pnum = ctx->pnum; + f->first = now; + f->last = now; + f->plen = 0; - p = (qn->id < qnt->id) ? &temp->left : &temp->right; + nframes++; - if (*p == sentinel) { + if (f->flush) { break; } - - temp = *p; } - *p = node; - node->parent = temp; - node->left = sentinel; - node->right = sentinel; - ngx_rbt_red(node); -} + if (nframes == 0) { + return 0; + } + out.data = src; + out.len = len; -static ngx_quic_stream_t * -ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) -{ - ngx_rbtree_node_t *node, *sentinel; - ngx_quic_stream_t *qn; + pkt.keys = qc->keys; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - node = rbtree->root; - sentinel = rbtree->sentinel; + if (ctx->level == ssl_encryption_initial) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - while (node != sentinel) { - qn = (ngx_quic_stream_t *) node; + } else if (ctx->level == ssl_encryption_handshake) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; - if (id == qn->id) { - return qn; + } else { + if (qc->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; } - - node = (id < qn->id) ? node->left : node->right; } - return NULL; -} - + ngx_quic_set_packet_number(&pkt, ctx); -static ngx_quic_stream_t * -ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) -{ - size_t n; - uint64_t min_id; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; + pkt.version = qc->version; + pkt.log = c->log; + pkt.level = ctx->level; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL is new", id); + pad_len = 4; - qc = ngx_quic_get_connection(c); + if (min) { + hlen = EVP_GCM_TLS_TAG_LEN + + ngx_quic_create_header(&pkt, NULL, out.len, NULL); - if (qc->shutdown) { - return NGX_QUIC_STREAM_GONE; + if (min > hlen + pad_len) { + pad_len = min - hlen; + } } - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_uni) { - return NGX_QUIC_STREAM_GONE; - } - - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } + if (out.len < pad_len) { + ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); + out.len = pad_len; + } - if ((id >> 2) < qc->streams.client_streams_uni) { - return NGX_QUIC_STREAM_GONE; - } + pkt.payload = out; - if ((id >> 2) >= qc->streams.client_max_streams_uni) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } + res.data = data; - min_id = (qc->streams.client_streams_uni << 2) - | NGX_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client_streams_uni = (id >> 2) + 1; - n = qc->tp.initial_max_stream_data_uni; + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx %s bytes:%ui" + " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", + ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, + pkt.number, pkt.num_len, pkt.trunc); - } else { + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_bidi) { - return NGX_QUIC_STREAM_GONE; - } + ctx->pnum++; - qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } + if (pkt.need_ack) { + /* move frames into the sent queue to wait for ack */ - if ((id >> 2) < qc->streams.client_streams_bidi) { - return NGX_QUIC_STREAM_GONE; - } + if (!qc->closing) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + f->plen = res.len; - if ((id >> 2) >= qc->streams.client_max_streams_bidi) { - qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; + do { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sent, q); + } while (--nframes); } - min_id = (qc->streams.client_streams_bidi << 2); - qc->streams.client_streams_bidi = (id >> 2) + 1; - n = qc->tp.initial_max_stream_data_bidi_remote; - } + cg->in_flight += res.len; - if (n < NGX_QUIC_STREAM_BUFSIZE) { - n = NGX_QUIC_STREAM_BUFSIZE; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); } - /* - * 2.1. Stream Types and Identifiers - * - * Within each type, streams are created with numerically increasing - * stream IDs. A stream ID that is used out of order results in all - * streams of that type with lower-numbered stream IDs also being - * opened. - */ - - for ( /* void */ ; min_id < id; min_id += 0x04) { - - sn = ngx_quic_create_stream(c, min_id, n); - if (sn == NULL) { - return NULL; - } - - sn->c->listening->handler(sn->c); + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (qc->shutdown) { - return NGX_QUIC_STREAM_GONE; - } + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); } - return ngx_quic_create_stream(c, id, n); + return res.len; } -static ngx_quic_stream_t * -ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) { - ngx_log_t *log; - ngx_pool_t *pool; - ngx_quic_stream_t *sn; - ngx_pool_cleanup_t *cln; - ngx_quic_connection_t *qc; + ngx_buf_t b; + ngx_chain_t cl, *res; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL create", id); + ngx_memzero(&b, sizeof(ngx_buf_t)); - qc = ngx_quic_get_connection(c); + b.pos = b.start = buf; + b.last = b.end = buf + len; + b.last_buf = 1; + b.temporary = 1; - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - return NULL; - } + cl.buf = &b; + cl.next= NULL; - sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); - if (sn == NULL) { - ngx_destroy_pool(pool); - return NULL; + res = c->send_chain(c, &cl, 0); + if (res == NGX_CHAIN_ERROR) { + return NGX_ERROR; } - sn->node.key = id; - sn->parent = c; - sn->id = id; + return len; +} - sn->b = ngx_create_temp_buf(pool, rcvbuf_size); - if (sn->b == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - ngx_queue_init(&sn->fs.frames); +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; - log = ngx_palloc(pool, sizeof(ngx_log_t)); - if (log == NULL) { - ngx_destroy_pool(pool); - return NULL; - } + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; - *log = *c->log; - pool->log = log; + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; - sn->c = ngx_get_connection(-1, log); - if (sn->c == NULL) { - ngx_destroy_pool(pool); - return NULL; + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; } +} - sn->c->quic = sn; - sn->c->type = SOCK_STREAM; - sn->c->pool = pool; - sn->c->ssl = c->ssl; - sn->c->sockaddr = c->sockaddr; - sn->c->listening = c->listening; - sn->c->addr_text = c->addr_text; - sn->c->local_sockaddr = c->local_sockaddr; - sn->c->local_socklen = c->local_socklen; - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; +static void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q, *next; + ngx_connection_t *c; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; - sn->c->read->log = log; - sn->c->write->log = log; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); - log->connection = sn->c->number; + c = ev->data; + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; - if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - sn->c->write->ready = 1; - } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_uni; - } + ctx = &qc->send_ctx[i]; - } else { - if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; - } else { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + if (ngx_queue_empty(&ctx->sent)) { + continue; } - } - - cln = ngx_pool_cleanup_add(pool, 0); - if (cln == NULL) { - ngx_close_connection(sn->c); - ngx_destroy_pool(pool); - return NULL; - } - cln->handler = ngx_quic_stream_cleanup_handler; - cln->data = sn->c; + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_rbtree_insert(&qc->streams.tree, &sn->node); + if (f->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } - return sn; -} + if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { + continue; + } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); -static ssize_t -ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) -{ - ssize_t len; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - qs = c->quic; - b = qs->b; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - rev = c->read; + if (f->type == NGX_QUIC_FT_PING) { + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } - if (rev->error) { - return NGX_ERROR; - } + q = next; + } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream recv id:0x%xL eof:%d avail:%z", - qs->id, rev->pending_eof, b->last - b->pos); + for (q = ngx_queue_head(&ctx->sent); + q != ngx_queue_sentinel(&ctx->sent); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (b->pos == b->last) { - rev->ready = 0; + if (f->type == NGX_QUIC_FT_PING) { + ngx_quic_congestion_lost(c, f); + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } - if (rev->pending_eof) { - rev->eof = 1; - return 0; + q = next; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv() not ready", qs->id); - return NGX_AGAIN; - } - - len = ngx_min(b->last - b->pos, (ssize_t) size); - - ngx_memcpy(buf, b->pos, len); + /* enforce 2 udp datagrams */ - b->pos += len; - qc->streams.received += len; + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } - if (b->pos == b->last) { - b->pos = b->start; - b->last = b->start; - rev->ready = rev->pending_eof; - } + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->flush = 1; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv len:%z of size:%uz", - qs->id, len, size); + ngx_quic_queue_frame(qc, f); - if (!rev->pending_eof) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_ERROR; + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) - + (b->end - b->last); + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; - ngx_quic_queue_frame(qc, frame); + ngx_quic_queue_frame(qc, f); } - if ((qc->streams.recv_max_data / 2) < qc->streams.received) { - - frame = ngx_quic_alloc_frame(pc); + qc->pto_count++; - if (frame == NULL) { - return NGX_ERROR; - } + ngx_quic_connstate_dbg(c); +} - qc->streams.recv_max_data *= 2; - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_DATA; - frame->u.max_data.max_data = qc->streams.recv_max_data; +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; - ngx_quic_queue_frame(qc, frame); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv: increased max_data:%uL", - qs->id, qc->streams.recv_max_data); + c = ev->data; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; } - return len; + ngx_quic_connstate_dbg(c); } -static ssize_t -ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +static +void ngx_quic_lost_handler(ngx_event_t *ev) { - ngx_buf_t b; - ngx_chain_t cl; - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - b.memory = 1; - b.pos = buf; - b.last = buf + size; + ngx_connection_t *c; - cl.buf = &b; - cl.next = NULL; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); - if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } + c = ev->data; - if (b.pos == buf) { - return NGX_AGAIN; + if (ngx_quic_detect_lost(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); } - return b.pos - buf; + ngx_quic_connstate_dbg(c); } -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_detect_lost(ngx_connection_t *c) { - size_t n, flow; - ngx_event_t *wev; - ngx_chain_t *cl; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; + ngx_uint_t i; + ngx_msec_t now, wait, thr; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - wev = c->write; - - if (wev->error) { - return NGX_CHAIN_ERROR; - } - - flow = ngx_quic_max_stream_flow(c); - if (flow == 0) { - wev->ready = 0; - return in; - } + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + thr = ngx_quic_lost_threshold(qc); - n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_CHAIN_ERROR; - } + ctx = &qc->send_ctx[i]; - frame->data = ngx_quic_copy_chain(pc, in, n); - if (frame->data == NGX_CHAIN_ERROR) { - return NGX_CHAIN_ERROR; - } + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } - for (n = 0, cl = frame->data; cl; cl = cl->next) { - n += ngx_buf_size(cl->buf); - } + while (!ngx_queue_empty(&ctx->sent)) { - while (in && ngx_buf_size(in->buf) == 0) { - in = in->next; - } + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; + if (start->pnum > ctx->largest_ack) { + break; + } - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = n; + wait = start->last + thr - now; - c->sent += n; - qc->streams.sent += n; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); - ngx_quic_queue_frame(qc, frame); + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + break; + } - wev->ready = (n < flow) ? 1 : 0; + ngx_quic_resend_frames(c, ctx); + } + } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz", n); + ngx_quic_set_lost_timer(c); - return in; + return NGX_OK; } -static size_t -ngx_quic_max_stream_flow(ngx_connection_t *c) +static void +ngx_quic_set_lost_timer(ngx_connection_t *c) { - size_t size; - uint64_t sent, unacked; - ngx_quic_stream_t *qs; + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; - size = NGX_QUIC_STREAM_BUFSIZE; - sent = c->sent; - unacked = sent - qs->acked; + lost = -1; + pto = -1; - if (qc->streams.send_max_data == 0) { - qc->streams.send_max_data = qc->ctp.initial_max_data; - } + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; - if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit buffer size"); - return 0; - } + if (ngx_queue_empty(&ctx->sent)) { + continue; + } - if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { - size = NGX_QUIC_STREAM_BUFSIZE - unacked; - } + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); - if (qc->streams.sent >= qc->streams.send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_DATA"); - return 0; - } + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); + + if (w < 0) { + w = 0; + } - if (qc->streams.sent + size > qc->streams.send_max_data) { - size = qc->streams.send_max_data - qc->streams.sent; + if (pto == -1 || w < pto) { + pto = w; + } } - if (sent >= qs->send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_STREAM_DATA"); - return 0; + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); } - if (sent + size > qs->send_max_data) { - size = qs->send_max_data - sent; + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); + + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow:%uz", size); + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } - return size; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); } static void -ngx_quic_stream_cleanup_handler(void *data) +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - ngx_connection_t *c = data; - - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; + size_t n; + ngx_buf_t *b; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *sn; ngx_quic_connection_t *qc; - qs = c->quic; - pc = qs->parent; - qc = ngx_quic_get_connection(pc); + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL cleanup", qs->id); + "quic resend packet pnum:%uL", start->pnum); - ngx_rbtree_delete(&qc->streams.tree, &qs->node); - ngx_quic_free_frames(pc, &qs->fs.frames); + ngx_quic_congestion_lost(c, start); - if (qc->closing) { - /* schedule handler call to continue ngx_quic_close_connection() */ - ngx_post_event(pc->read, &ngx_posted_events); - return; - } + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 - || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) - { - if (!c->read->pending_eof && !c->read->error) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - goto done; - } + if (f->pnum != start->pnum) { + break; + } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STOP_SENDING; - frame->u.stop_sending.id = qs->id; - frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ + q = ngx_queue_next(q); - ngx_quic_queue_frame(qc, frame); - } - } + ngx_queue_remove(&f->queue); - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - goto done; - } + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAMS; + ngx_quic_free_frame(c, f); + break; - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; - frame->u.max_streams.bidi = 0; + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; - } else { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; - frame->u.max_streams.bidi = 1; - } + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; - ngx_quic_queue_frame(qc, frame); + 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; + ngx_quic_queue_frame(qc, f); + break; - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - /* do not send fin for client unidirectional streams */ - goto done; - } - } + case NGX_QUIC_FT_MAX_STREAM_DATA: + sn = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (sn == NULL) { + ngx_quic_free_frame(c, f); + break; + } - if (c->write->error) { - goto done; - } + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL send fin", qs->id); + if (f->u.max_stream_data.limit < n) { + f->u.max_stream_data.limit = n; + } - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - goto done; - } + ngx_quic_queue_frame(qc, f); + break; - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 1; + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - frame->u.stream.type = frame->type; - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = 0; + if (sn && sn->c->write->error) { + /* RESET_STREAM was sent */ + ngx_quic_free_frame(c, f); + break; + } - ngx_quic_queue_frame(qc, frame); + /* fall through */ -done: + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } - (void) ngx_quic_output(pc); + } while (q != ngx_queue_sentinel(&ctx->sent)); - if (qc->shutdown) { - ngx_quic_shutdown_quic(pc); + if (qc->closing) { + return; } + + ngx_post_event(&qc->push, &ngx_posted_events); } -static void +void ngx_quic_shutdown_quic(ngx_connection_t *c) { ngx_rbtree_t *tree; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index a14bd65b4..17bc96435 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -19,6 +19,7 @@ typedef struct ngx_quic_connection_s ngx_quic_connection_t; #include #include #include +#include #define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ @@ -225,6 +226,9 @@ ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token); +ngx_int_t ngx_quic_output(ngx_connection_t *c); +void ngx_quic_shutdown_quic(ngx_connection_t *c); + /********************************* DEBUG *************************************/ /* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c new file mode 100644 index 000000000..90d6bcea3 --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.c @@ -0,0 +1,1268 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include +#include + + +#define NGX_QUIC_STREAM_GONE (void *) -1 + + +static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, + uint64_t id); +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, + uint64_t id, size_t rcvbuf_size); +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 ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); +static void ngx_quic_stream_cleanup_handler(void *data); + + +ngx_connection_t * +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) +{ + size_t rcvbuf_size; + uint64_t id; + ngx_quic_stream_t *qs, *sn; + ngx_quic_connection_t *qc; + + qs = c->quic; + qc = ngx_quic_get_connection(qs->parent); + + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_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); + return NULL; + } + + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_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); + return NULL; + } + + id = (qc->streams.server_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" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + rcvbuf_size = 0; + } + + sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); + if (sn == NULL) { + return NULL; + } + + return sn->c; +} + + +void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; + + for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; + + p = (qn->id < qnt->id) ? &temp->left : &temp->right; + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +ngx_quic_stream_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) +{ + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; + + if (id == qn->id) { + return qn; + } + + node = (id < qn->id) ? node->left : node->right; + } + + return NULL; +} + + +ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_event_t *rev, *wev; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + +#if (NGX_DEBUG) + ngx_uint_t ns; +#endif + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + +#if (NGX_DEBUG) + ns = 0; +#endif + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + rev = qs->c->read; + rev->error = 1; + rev->ready = 1; + + wev = qs->c->write; + wev->error = 1; + wev->ready = 1; + + ngx_post_event(rev, &ngx_posted_events); + + if (rev->timer_set) { + ngx_del_timer(rev); + } + +#if (NGX_DEBUG) + ns++; +#endif + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has %ui active streams", ns); + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = qs->id; + frame->u.reset_stream.error_code = err; + frame->u.reset_stream.final_size = c->sent; + + ngx_quic_queue_frame(qc, frame); + + wev = c->write; + wev->error = 1; + wev->ready = 1; + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) +{ + size_t n; + uint64_t min_id; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is new", id); + + qc = ngx_quic_get_connection(c); + + if (qc->shutdown) { + return NGX_QUIC_STREAM_GONE; + } + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + n = qc->tp.initial_max_stream_data_uni; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_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; + n = qc->tp.initial_max_stream_data_bidi_remote; + } + + if (n < NGX_QUIC_STREAM_BUFSIZE) { + n = NGX_QUIC_STREAM_BUFSIZE; + } + + /* + * 2.1. Stream Types and Identifiers + * + * Within each type, streams are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being + * opened. + */ + + for ( /* void */ ; min_id < id; min_id += 0x04) { + + sn = ngx_quic_create_stream(c, min_id, n); + if (sn == NULL) { + return NULL; + } + + sn->c->listening->handler(sn->c); + + if (qc->shutdown) { + return NGX_QUIC_STREAM_GONE; + } + } + + return ngx_quic_create_stream(c, id, n); +} + + +static ngx_quic_stream_t * +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) +{ + ngx_log_t *log; + ngx_pool_t *pool; + ngx_quic_stream_t *sn; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL create", id); + + qc = ngx_quic_get_connection(c); + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + return NULL; + } + + sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); + if (sn == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + sn->node.key = id; + sn->parent = c; + sn->id = id; + + sn->b = ngx_create_temp_buf(pool, rcvbuf_size); + if (sn->b == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + ngx_queue_init(&sn->fs.frames); + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + *log = *c->log; + pool->log = log; + + sn->c = ngx_get_connection(-1, log); + if (sn->c == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + sn->c->quic = sn; + sn->c->type = SOCK_STREAM; + sn->c->pool = pool; + sn->c->ssl = c->ssl; + sn->c->sockaddr = c->sockaddr; + sn->c->listening = c->listening; + sn->c->addr_text = c->addr_text; + sn->c->local_sockaddr = c->local_sockaddr; + sn->c->local_socklen = c->local_socklen; + sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + sn->c->recv = ngx_quic_stream_recv; + sn->c->send = ngx_quic_stream_send; + sn->c->send_chain = ngx_quic_stream_send_chain; + + sn->c->read->log = log; + sn->c->write->log = log; + + log->connection = sn->c->number; + + if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + sn->c->write->ready = 1; + } + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_uni; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + } else { + sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + } + } + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sn->c); + ngx_destroy_pool(pool); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sn->c; + + ngx_rbtree_insert(&qc->streams.tree, &sn->node); + + return sn; +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + b = qs->b; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + rev = c->read; + + if (rev->error) { + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream recv id:0x%xL eof:%d avail:%z", + qs->id, rev->pending_eof, b->last - b->pos); + + if (b->pos == b->last) { + rev->ready = 0; + + if (rev->pending_eof) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; + } + + len = ngx_min(b->last - b->pos, (ssize_t) size); + + ngx_memcpy(buf, b->pos, len); + + b->pos += len; + qc->streams.received += len; + + if (b->pos == b->last) { + b->pos = b->start; + b->last = b->start; + rev->ready = rev->pending_eof; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z of size:%uz", + qs->id, len, size); + + if (!rev->pending_eof) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + + (b->end - b->last); + + ngx_quic_queue_frame(qc, frame); + } + + if ((qc->streams.recv_max_data / 2) < qc->streams.received) { + + frame = ngx_quic_alloc_frame(pc); + + if (frame == NULL) { + return NGX_ERROR; + } + + qc->streams.recv_max_data *= 2; + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv: increased max_data:%uL", + qs->id, qc->streams.recv_max_data); + } + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + size_t n, flow; + ngx_event_t *wev; + ngx_chain_t *cl; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + wev = c->write; + + if (wev->error) { + return NGX_CHAIN_ERROR; + } + + flow = ngx_quic_max_stream_flow(c); + if (flow == 0) { + wev->ready = 0; + return in; + } + + n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_CHAIN_ERROR; + } + + frame->data = ngx_quic_copy_chain(pc, in, n); + if (frame->data == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + for (n = 0, cl = frame->data; cl; cl = cl->next) { + n += ngx_buf_size(cl->buf); + } + + while (in && ngx_buf_size(in->buf) == 0) { + in = in->next; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 0; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = n; + + c->sent += n; + qc->streams.sent += n; + + ngx_quic_queue_frame(qc, frame); + + wev->ready = (n < flow) ? 1 : 0; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uz", n); + + return in; +} + + +static size_t +ngx_quic_max_stream_flow(ngx_connection_t *c) +{ + size_t size; + uint64_t sent, unacked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + qc = ngx_quic_get_connection(qs->parent); + + size = NGX_QUIC_STREAM_BUFSIZE; + sent = c->sent; + unacked = sent - qs->acked; + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit buffer size"); + return 0; + } + + if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { + size = NGX_QUIC_STREAM_BUFSIZE - unacked; + } + + if (qc->streams.sent >= qc->streams.send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_DATA"); + return 0; + } + + if (qc->streams.sent + size > qc->streams.send_max_data) { + size = qc->streams.send_max_data - qc->streams.sent; + } + + if (sent >= qs->send_max_data) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow hit MAX_STREAM_DATA"); + return 0; + } + + if (sent + size > qs->send_max_data) { + size = qs->send_max_data - sent; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send flow:%uz", size); + + return size; +} + + +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_quic_free_frames(pc, &qs->fs.frames); + + if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ + ngx_post_event(pc->read, &ngx_posted_events); + return; + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 + || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + if (!c->read->pending_eof && !c->read->error) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + goto done; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ + + ngx_quic_queue_frame(qc, frame); + } + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + goto done; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_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.bidi = 1; + } + + ngx_quic_queue_frame(qc, frame); + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + /* do not send fin for client unidirectional streams */ + goto done; + } + } + + if (c->write->error) { + goto done; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL send fin", qs->id); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + goto done; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 1; + + frame->u.stream.type = frame->type; + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = 0; + + ngx_quic_queue_frame(qc, frame); + +done: + + (void) ngx_quic_output(pc); + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + } +} + + +ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + size_t window; + uint64_t last; + ngx_buf_t *b; + ngx_pool_t *pool; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + ngx_quic_frames_stream_t *fs; + + qc = ngx_quic_get_connection(c); + f = &frame->u.stream; + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->stream_id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + fs = &sn->fs; + b = sn->b; + window = b->end - b->last; + + if (last > window) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + goto cleanup; + } + + if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn) + != NGX_OK) + { + goto cleanup; + } + + sc->listening->handler(sc); + + return NGX_OK; + } + + fs = &sn->fs; + b = sn->b; + window = (b->pos - b->start) + (b->end - b->last); + + if (last > fs->received && last - fs->received > window) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, + sn); + +cleanup: + + pool = sc->pool; + + ngx_close_connection(sc); + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +{ + uint64_t id; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_chain_t *cl; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = ngx_quic_get_connection(c); + sn = data; + + f = &frame->u.stream; + id = f->stream_id; + + b = sn->b; + + if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no space in stream buffer"); + return NGX_ERROR; + } + + if ((size_t) (b->end - b->last) < f->length) { + b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); + b->pos = b->start; + } + + for (cl = frame->data; cl; cl = cl->next) { + b->last = ngx_cpymem(b->last, cl->buf->pos, + cl->buf->last - cl->buf->pos); + } + + rev = sn->c->read; + rev->ready = 1; + + if (f->fin) { + rev->pending_eof = 1; + } + + if (rev->active) { + rev->handler(rev); + } + + /* check if stream was destroyed by handler */ + if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { + return NGX_DONE; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_event_t *wev; + 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 (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (qc->streams.sent >= qc->streams.send_max_data) { + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + wev = qs->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + } + + qc->streams.send_max_data = f->max_data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + size_t n; + ngx_buf_t *b; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + b = sn->b; + n = b->end - b->last; + + sn->c->listening->handler(sn->c); + + } else { + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = pkt->level; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = f->id; + frame->u.max_stream_data.limit = n; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + uint64_t sent; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit > sn->send_max_data) { + sn->send_max_data = f->limit; + } + + sn->c->listening->handler(sn->c); + + return NGX_OK; + } + + if (f->limit <= sn->send_max_data) { + return NGX_OK; + } + + sent = sn->c->sent; + + if (sent >= sn->send_max_data) { + wev = sn->c->write; + + if (wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + } + + sn->send_max_data = f->limit; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_event_t *rev; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + rev = sc->read; + rev->error = 1; + rev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + rev = sn->c->read; + rev->error = 1; + rev->ready = 1; + + if (rev->active) { + rev->handler(rev); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_event_t *wev; + ngx_connection_t *sc; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + + if (sn == NULL) { + sn = ngx_quic_create_client_stream(c, f->id); + + if (sn == NULL) { + return NGX_ERROR; + } + + if (sn == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + sc = sn->c; + + wev = sc->write; + wev->error = 1; + wev->ready = 1; + + sc->listening->handler(sc); + + return NGX_OK; + } + + wev = sn->c->write; + wev->error = 1; + wev->ready = 1; + + if (wev->active) { + wev->handler(wev); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + 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; + + 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; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + +void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t sent, unacked; + ngx_event_t *wev; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (sn == NULL) { + return; + } + + wev = sn->c->write; + sent = sn->c->sent; + unacked = sent - sn->acked; + + if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { + wev->ready = 1; + ngx_post_event(wev, &ngx_posted_events); + } + + sn->acked += f->u.stream.length; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, + "quic stream ack len:%uL acked:%uL unacked:%uL", + f->u.stream.length, sn->acked, sent - sn->acked); +} diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h new file mode 100644 index 000000000..1a755e91e --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); +void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); +ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); +ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +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); + +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); + +#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ -- cgit v1.2.3 From 8df0b6bb2c3477cd0666b796b4e1a7373c4a600c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:41:20 +0300 Subject: QUIC: separate files for output and ack related processing. --- auto/modules | 8 +- src/event/quic/ngx_event_quic.c | 2140 ++-------------------------- src/event/quic/ngx_event_quic_ack.c | 1081 ++++++++++++++ src/event/quic/ngx_event_quic_ack.h | 31 + src/event/quic/ngx_event_quic_connection.h | 41 +- src/event/quic/ngx_event_quic_output.c | 851 +++++++++++ src/event/quic/ngx_event_quic_output.h | 40 + 7 files changed, 2132 insertions(+), 2060 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_ack.c create mode 100644 src/event/quic/ngx_event_quic_ack.h create mode 100644 src/event/quic/ngx_event_quic_output.c create mode 100644 src/event/quic/ngx_event_quic_output.h diff --git a/auto/modules b/auto/modules index ba0131da6..925741537 100644 --- a/auto/modules +++ b/auto/modules @@ -1346,14 +1346,18 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_frames.h \ src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h \ - src/event/quic/ngx_event_quic_streams.h" + src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_ack.h \ + src/event/quic/ngx_event_quic_output.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ src/event/quic/ngx_event_quic_frames.c \ src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c \ - src/event/quic/ngx_event_quic_streams.c" + src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_ack.c \ + src/event/quic/ngx_event_quic_output.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 703425085..365488701 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -11,28 +11,12 @@ #include -#define ngx_quic_lost_threshold(qc) \ - ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ - NGX_QUIC_TIME_GRANULARITY) - /* * 7.4. Cryptographic Message Buffering * Implementations MUST support buffering at least 4096 bytes of data */ #define NGX_QUIC_MAX_BUFFERED 65535 -/* - * Endpoints MUST discard packets that are too small to be valid QUIC - * packets. With the set of AEAD functions defined in [QUIC-TLS], - * packets that are smaller than 21 bytes are never valid. - */ -#define NGX_QUIC_MIN_PKT_LEN 21 - -#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */ -#define NGX_QUIC_MAX_SR_PACKET 1200 - -#define NGX_QUIC_MAX_ACK_GAP 2 - #if BORINGSSL_API_VERSION >= 10 static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, @@ -50,30 +34,19 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); -static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, uint8_t alert); static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp); static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, - ngx_quic_header_t *inpkt); -static ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, - ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, - ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]); static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, u_char *key, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); -static ngx_inline size_t ngx_quic_max_udp_payload(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); @@ -85,60 +58,21 @@ static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, - ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, - ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); -static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t pn); -static ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); -static ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); - -static ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_frame_t *f); -static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, - ngx_msec_t *send_time); -static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, - enum ssl_encryption_level_t level, ngx_msec_t send_time); + static 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_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data); -static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); -static ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); -static ssize_t ngx_quic_output_packet(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); -static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); - -static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, - ngx_quic_send_ctx_t *ctx); -static void ngx_quic_pto_handler(ngx_event_t *ev); -static void ngx_quic_lost_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); -static void ngx_quic_set_lost_timer(ngx_connection_t *c); -static void ngx_quic_resend_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); static void ngx_quic_push_handler(ngx_event_t *ev); -static void ngx_quic_congestion_ack(ngx_connection_t *c, - ngx_quic_frame_t *frame); -static void ngx_quic_congestion_lost(ngx_connection_t *c, - ngx_quic_frame_t *frame); - static ngx_core_module_t ngx_quic_module_ctx = { ngx_string("quic"), @@ -178,7 +112,7 @@ static SSL_QUIC_METHOD quic_method = { #if (NGX_DEBUG) -static void +void ngx_quic_connstate_dbg(ngx_connection_t *c) { u_char *p, *last; @@ -241,10 +175,6 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) "quic %*s", p - buf, buf); } -#else - -#define ngx_quic_connstate_dbg(c) - #endif @@ -468,38 +398,6 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) } -static int -ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, - uint8_t alert) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_alert() lvl:%d alert:%d", - (int) level, (int) alert); - - qc = ngx_quic_get_connection(c); - if (qc == NULL) { - return 1; - } - - qc->error_level = level; - qc->error = NGX_QUIC_ERR_CRYPTO(alert); - qc->error_reason = "TLS alert"; - qc->error_app = 0; - qc->error_ftype = 0; - - if (ngx_quic_send_cc(c) != NGX_OK) { - return 0; - } - - return 1; -} - - static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) { @@ -722,57 +620,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } -static ngx_int_t -ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *pkt) -{ - u_char *token; - size_t len, max; - uint16_t rndbytes; - u_char buf[NGX_QUIC_MAX_SR_PACKET]; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handle stateless reset output"); - - if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { - return NGX_DECLINED; - } - - if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { - len = pkt->len - 1; - - } else { - max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); - - if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { - return NGX_ERROR; - } - - len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) - + NGX_QUIC_MIN_SR_PACKET; - } - - if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { - return NGX_ERROR; - } - - buf[0] &= ~NGX_QUIC_PKT_LONG; - buf[0] |= NGX_QUIC_PKT_FIXED_BIT; - - token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; - - if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) - != NGX_OK) - { - return NGX_ERROR; - } - - (void) ngx_quic_send(c, buf, len); - - return NGX_DECLINED; -} - - ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token) @@ -843,102 +690,7 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static ngx_int_t -ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) -{ - size_t len; - ngx_quic_header_t pkt; - static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sending version negotiation packet"); - - pkt.log = c->log; - pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; - pkt.dcid = inpkt->scid; - pkt.scid = inpkt->dcid; - - len = ngx_quic_create_version_negotiation(&pkt, buf); - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic vnego packet to send len:%uz %*xs", len, len, buf); -#endif - - (void) ngx_quic_send(c, buf, len); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, - ngx_quic_header_t *inpkt) -{ - time_t expires; - ssize_t len; - ngx_str_t res, token; - ngx_quic_header_t pkt; - - u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; - u_char dcid[NGX_QUIC_SERVER_CID_LEN]; - - expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; - - if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, - expires, 1) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; - pkt.version = inpkt->version; - pkt.log = c->log; - - pkt.odcid = inpkt->dcid; - pkt.dcid = inpkt->scid; - - /* TODO: generate routable dcid */ - if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { - return NGX_ERROR; - } - - pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; - pkt.scid.data = dcid; - - pkt.token = token; - - res.data = buf; - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet to send len:%uz %xV", res.len, &res); -#endif - - len = ngx_quic_send(c, res.data, res.len); - if (len == NGX_ERROR) { - return NGX_ERROR; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic retry packet sent to %xV", &pkt.dcid); - - /* - * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry - * packet in response to a single UDP datagram. - * NGX_DONE will stop quic_input() from processing further - */ - return NGX_DONE; -} - - -static ngx_int_t +ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) { @@ -1267,21 +1019,6 @@ ngx_quic_init_connection(ngx_connection_t *c) } -static ngx_inline size_t -ngx_quic_max_udp_payload(ngx_connection_t *c) -{ - /* TODO: path MTU discovery */ - -#if (NGX_HAVE_INET6) - if (c->sockaddr->sa_family == AF_INET6) { - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; - } -#endif - - return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; -} - - static void ngx_quic_input_handler(ngx_event_t *rev) { @@ -1902,83 +1639,6 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static ngx_int_t -ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, - ngx_uint_t err, const char *reason) -{ - ssize_t len; - ngx_str_t res; - ngx_quic_frame_t frame; - ngx_quic_header_t pkt; - - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - frame.level = inpkt->level; - frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; - frame.u.close.error_code = err; - - frame.u.close.reason.data = (u_char *) reason; - frame.u.close.reason.len = ngx_strlen(reason); - - len = ngx_quic_create_frame(NULL, &frame); - if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { - return NGX_ERROR; - } - - ngx_quic_log_frame(c->log, &frame, 1); - - len = ngx_quic_create_frame(src, &frame); - if (len == -1) { - return NGX_ERROR; - } - - pkt.keys = ngx_quic_keys_new(c->pool); - if (pkt.keys == NULL) { - return NGX_ERROR; - } - - if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid, - inpkt->version) - != NGX_OK) - { - return NGX_ERROR; - } - - pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG - | NGX_QUIC_PKT_INITIAL; - - pkt.num_len = 1; - /* - * pkt.num = 0; - * pkt.trunc = 0; - */ - - pkt.version = inpkt->version; - pkt.log = c->log; - pkt.level = inpkt->level; - pkt.dcid = inpkt->scid; - pkt.scid = inpkt->dcid; - pkt.payload.data = src; - pkt.payload.len = len; - - res.data = dst; - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) { - return NGX_ERROR; - } - - return NGX_OK; -} - - static void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) { @@ -2291,1383 +1951,152 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) { - uint64_t base, largest, smallest, gs, ge, gap, range, pn; - uint64_t prev_pending; - ngx_uint_t i, nr; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_range_t *r; - ngx_quic_connection_t *qc; - - c->log->action = "preparing ack"; + uint64_t last; + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + ngx_quic_frames_stream_t *fs; qc = ngx_quic_get_connection(c); + fs = &qc->crypto[pkt->level]; + f = &frame->u.crypto; - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" - " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, - ctx->first_range, ctx->nranges); + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; - prev_pending = ctx->pending_ack; + if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } - if (pkt->need_ack) { + rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, + NULL); + if (rc != NGX_DECLINED) { + return rc; + } - ngx_post_event(&qc->push, &ngx_posted_events); + /* speeding up handshake completion */ - if (ctx->send_ack == 0) { - ctx->ack_delay_start = ngx_current_msec; - } + if (pkt->level == ssl_encryption_initial) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); - ctx->send_ack++; + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); - if (ctx->pending_ack == NGX_QUIC_UNSET_PN - || ctx->pending_ack < pkt->pn) - { - ctx->pending_ack = pkt->pn; + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } } } - base = ctx->largest_range; - pn = pkt->pn; - - if (base == NGX_QUIC_UNSET_PN) { - ctx->largest_range = pn; - ctx->largest_received = pkt->received; - return NGX_OK; - } - - if (base == pn) { - return NGX_OK; - } + return NGX_OK; +} - largest = base; - smallest = largest - ctx->first_range; - if (pn > base) { +ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; - if (pn - base == 1) { - ctx->first_range++; - ctx->largest_range = pn; - ctx->largest_received = pkt->received; + qc = ngx_quic_get_connection(c); - return NGX_OK; + ssl_conn = c->ssl->connection; - } else { - /* new gap in front of current largest */ + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); - /* no place for new range, send current range as is */ - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; - if (prev_pending != NGX_QUIC_UNSET_PN) { - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - } + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + b->pos, b->last - b->pos)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } - if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - } + n = SSL_do_handshake(ssl_conn); - gap = pn - base - 2; - range = ctx->first_range; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); - ctx->first_range = 0; - ctx->largest_range = pn; - ctx->largest_received = pkt->received; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - /* packet is out of order, force send */ - if (pkt->need_ack) { - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); - i = 0; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); - goto insert; - } - } - - /* pn < base, perform lookup in existing ranges */ - - /* packet is out of order */ - if (pkt->need_ack) { - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } - - if (pn >= smallest && pn <= largest) { - return NGX_OK; - } - -#if (NGX_SUPPRESS_WARN) - r = NULL; -#endif - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - - ge = smallest - 1; - gs = ge - r->gap; - - if (pn >= gs && pn <= ge) { - - if (gs == ge) { - /* gap size is exactly one packet, now filled */ - - /* data moves to previous range, current is removed */ - - if (i == 0) { - ctx->first_range += r->range + 2; - - } else { - ctx->ranges[i - 1].range += r->range + 2; - } - - nr = ctx->nranges - i - 1; - if (nr) { - ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], - sizeof(ngx_quic_ack_range_t) * nr); - } - - ctx->nranges--; - - } else if (pn == gs) { - /* current gap shrinks from tail (current range grows) */ - r->gap--; - r->range++; - - } else if (pn == ge) { - /* current gap shrinks from head (previous range grows) */ - r->gap--; - - if (i == 0) { - ctx->first_range++; - - } else { - ctx->ranges[i - 1].range++; - } - - } else { - /* current gap is split into two parts */ - - gap = ge - pn - 1; - range = 0; - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - if (prev_pending != NGX_QUIC_UNSET_PN) { - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - } - - if (prev_pending == ctx->pending_ack || !pkt->need_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - } - - r->gap = pn - gs - 1; - goto insert; - } - - return NGX_OK; - } - - largest = smallest - r->gap - 2; - smallest = largest - r->range; - - if (pn >= smallest && pn <= largest) { - /* this packet number is already known */ - return NGX_OK; - } - - } - - if (pn == smallest - 1) { - /* extend first or last range */ - - if (i == 0) { - ctx->first_range++; - - } else { - r->range++; - } - - return NGX_OK; - } - - /* nothing found, add new range at the tail */ - - if (ctx->nranges == NGX_QUIC_MAX_RANGES) { - /* packet is too old to keep it */ - - if (pkt->need_ack) { - return ngx_quic_send_ack_range(c, ctx, pn, pn); - } - - return NGX_OK; - } - - gap = smallest - 2 - pn; - range = 0; - -insert: - - if (ctx->nranges < NGX_QUIC_MAX_RANGES) { - ctx->nranges++; - } - - ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], - sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); - - ctx->ranges[i].gap = gap; - ctx->ranges[i].range = range; - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t smallest, uint64_t largest) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ctx->level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = largest; - frame->u.ack.delay = 0; - frame->u.ack.range_count = 0; - frame->u.ack.first_range = largest - smallest; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static void -ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t pn) -{ - uint64_t base; - ngx_uint_t i, smallest, largest; - ngx_quic_ack_range_t *r; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" - " fr:%uL nranges:%ui", pn, ctx->largest_range, - ctx->first_range, ctx->nranges); - - base = ctx->largest_range; - - if (base == NGX_QUIC_UNSET_PN) { - return; - } - - if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { - ctx->pending_ack = NGX_QUIC_UNSET_PN; - } - - largest = base; - smallest = largest - ctx->first_range; - - if (pn >= largest) { - ctx->largest_range = NGX_QUIC_UNSET_PN; - ctx->first_range = 0; - ctx->nranges = 0; - return; - } - - if (pn >= smallest) { - ctx->first_range = largest - pn - 1; - ctx->nranges = 0; - return; - } - - for (i = 0; i < ctx->nranges; i++) { - r = &ctx->ranges[i]; - - largest = smallest - r->gap - 2; - smallest = largest - r->range; - - if (pn >= largest) { - ctx->nranges = i; - return; - } - if (pn >= smallest) { - r->range = largest - pn - 1; - ctx->nranges = i + 1; - return; - } - } -} - - -static ngx_int_t -ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t len, left; - uint64_t ack_delay; - ngx_buf_t *b; - ngx_uint_t i; - ngx_chain_t *cl, **ll; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ack_delay = ngx_current_msec - ctx->largest_received; - ack_delay *= 1000; - ack_delay >>= qc->tp.ack_delay_exponent; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - ll = &frame->data; - b = NULL; - - for (i = 0; i < ctx->nranges; i++) { - len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, - ctx->ranges[i].range); - - left = b ? b->end - b->last : 0; - - if (left < len) { - cl = ngx_quic_alloc_buf(c); - if (cl == NULL) { - return NGX_ERROR; - } - - *ll = cl; - ll = &cl->next; - - b = cl->buf; - left = b->end - b->last; - - if (left < len) { - return NGX_ERROR; - } - } - - b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, - ctx->ranges[i].range); - - frame->u.ack.ranges_length += len; - } - - *ll = NULL; - - frame->level = ctx->level; - frame->type = NGX_QUIC_FT_ACK; - frame->u.ack.largest = ctx->largest_range; - frame->u.ack.delay = ack_delay; - frame->u.ack.range_count = ctx->nranges; - frame->u.ack.first_range = ctx->first_range; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_send_cc(ngx_connection_t *c) -{ - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->draining) { - return NGX_OK; - } - - if (qc->closing - && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) - { - /* dot not send CC too often */ - return NGX_OK; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = qc->error_level; - frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP - : NGX_QUIC_FT_CONNECTION_CLOSE; - frame->u.close.error_code = qc->error; - frame->u.close.frame_type = qc->error_ftype; - - if (qc->error_reason) { - frame->u.close.reason.len = ngx_strlen(qc->error_reason); - frame->u.close.reason.data = (u_char *) qc->error_reason; - } - - ngx_quic_queue_frame(qc, frame); - - qc->last_cc = ngx_current_msec; - - return ngx_quic_output(c); -} - - -static ngx_int_t -ngx_quic_send_new_token(ngx_connection_t *c) -{ - time_t expires; - ngx_str_t token; - ngx_quic_frame_t *frame; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (!qc->conf->retry) { - return NGX_OK; - } - - expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - - if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) - != NGX_OK) - { - return NGX_ERROR; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_NEW_TOKEN; - frame->u.token.length = token.len; - frame->u.token.data = token.data; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *f) -{ - ssize_t n; - u_char *pos, *end; - uint64_t min, max, gap, range; - ngx_msec_t send_time; - ngx_uint_t i; - ngx_quic_send_ctx_t *ctx; - ngx_quic_ack_frame_t *ack; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_handle_ack_frame level:%d", pkt->level); - - ack = &f->u.ack; - - /* - * If any computed packet number is negative, an endpoint MUST - * generate a connection error of type FRAME_ENCODING_ERROR. - * (19.3.1) - */ - - if (ack->first_range > ack->largest) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid first range in ack frame"); - return NGX_ERROR; - } - - min = ack->largest - ack->first_range; - max = ack->largest; - - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) - != NGX_OK) - { - return NGX_ERROR; - } - - /* 13.2.3. Receiver Tracking of ACK Frames */ - if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { - ctx->largest_ack = max; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic updated largest received ack:%uL", max); - - /* - * An endpoint generates an RTT sample on receiving an - * ACK frame that meets the following two conditions: - * - * - the largest acknowledged packet number is newly acknowledged - * - at least one of the newly acknowledged packets was ack-eliciting. - */ - - if (send_time != NGX_TIMER_INFINITE) { - ngx_quic_rtt_sample(c, ack, pkt->level, send_time); - } - } - - if (f->data) { - pos = f->data->buf->pos; - end = f->data->buf->last; - - } else { - pos = NULL; - end = NULL; - } - - for (i = 0; i < ack->range_count; i++) { - - n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - pos += n; - - if (gap + 2 > min) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); - return NGX_ERROR; - } - - max = min - gap - 2; - - if (range > max) { - qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic invalid range:%ui in ack frame", i); - return NGX_ERROR; - } - - min = max - range; - - if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) - != NGX_OK) - { - return NGX_ERROR; - } - } - - return ngx_quic_detect_lost(c); -} - - -static ngx_int_t -ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t min, uint64_t max, ngx_msec_t *send_time) -{ - ngx_uint_t found; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - *send_time = NGX_TIMER_INFINITE; - found = 0; - - q = ngx_queue_last(&ctx->sent); - - while (q != ngx_queue_sentinel(&ctx->sent)) { - - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - q = ngx_queue_prev(q); - - if (f->pnum >= min && f->pnum <= max) { - ngx_quic_congestion_ack(c, f); - - switch (f->type) { - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - ngx_quic_handle_stream_ack(c, f); - break; - } - - if (f->pnum == max) { - *send_time = f->last; - } - - ngx_queue_remove(&f->queue); - ngx_quic_free_frame(c, f); - found = 1; - } - } - - if (!found) { - - if (max < ctx->pnum) { - /* duplicate ACK or ACK for non-ack-eliciting frame */ - return NGX_OK; - } - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic ACK for the packet not sent"); - - qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; - qc->error_ftype = NGX_QUIC_FT_ACK; - qc->error_reason = "unknown packet number"; - - return NGX_ERROR; - } - - if (!qc->push.timer_set) { - ngx_post_event(&qc->push, &ngx_posted_events); - } - - qc->pto_count = 0; - - return NGX_OK; -} - - -static void -ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, - enum ssl_encryption_level_t level, ngx_msec_t send_time) -{ - ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - latest_rtt = ngx_current_msec - send_time; - qc->latest_rtt = latest_rtt; - - if (qc->min_rtt == NGX_TIMER_INFINITE) { - qc->min_rtt = latest_rtt; - qc->avg_rtt = latest_rtt; - qc->rttvar = latest_rtt / 2; - - } else { - qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); - - ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; - - if (c->ssl->handshaked) { - ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); - } - - adjusted_rtt = latest_rtt; - - if (qc->min_rtt + ack_delay < latest_rtt) { - adjusted_rtt -= ack_delay; - } - - qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; - rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); - qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; - } - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic rtt sample latest:%M min:%M avg:%M var:%M", - latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); -} - - -ngx_msec_t -ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - ngx_msec_t duration; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - /* PTO calculation: quic-recovery, Appendix 8 */ - duration = qc->avg_rtt; - - duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); - duration <<= qc->pto_count; - - if (qc->congestion.in_flight == 0) { /* no in-flight packets */ - return duration; - } - - if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { - duration += qc->ctp.max_ack_delay << qc->pto_count; - } - - return duration; -} - - -static ngx_int_t -ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) -{ - uint64_t last; - ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - ngx_quic_crypto_frame_t *f; - ngx_quic_frames_stream_t *fs; - - qc = ngx_quic_get_connection(c); - fs = &qc->crypto[pkt->level]; - f = &frame->u.crypto; - - /* no overflow since both values are 62-bit */ - last = f->offset + f->length; - - if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { - qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; - return NGX_ERROR; - } - - rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, - NULL); - if (rc != NGX_DECLINED) { - return rc; - } - - /* speeding up handshake completion */ - - if (pkt->level == ssl_encryption_initial) { - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - if (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - while (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - } - } - } - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - int n, sslerr; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ssl_conn = c->ssl->connection; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - b->pos, b->last - b->pos)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; - } - } - - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n <= 0) { - sslerr = SSL_get_error(ssl_conn, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - - if (sslerr != SSL_ERROR_WANT_READ) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - return NGX_ERROR; - } - - return NGX_OK; - } - - if (SSL_in_init(ssl_conn)) { - return NGX_OK; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake completed successfully"); - - c->ssl->handshaked = 1; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - /* 12.4 Frames and frame types, figure 8 */ - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(qc, frame); - - if (ngx_quic_send_new_token(c) != NGX_OK) { - return NGX_ERROR; - } - - /* - * Generating next keys before a key update is received. - * See quic-tls 9.4 Header Protection Timing Side-Channels. - */ - - if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { - return NGX_ERROR; - } - - /* - * 4.10.2 An endpoint MUST discard its handshake keys - * when the TLS handshake is confirmed - */ - ngx_quic_discard_ctx(c, ssl_encryption_handshake); - - if (ngx_quic_issue_server_ids(c) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_output(ngx_connection_t *c) -{ - off_t max; - size_t len, min, in_flight; - ssize_t n; - u_char *p; - ngx_uint_t i, pad; - ngx_quic_send_ctx_t *ctx; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - c->log->action = "sending frames"; - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - in_flight = cg->in_flight; - - for ( ;; ) { - p = dst; - - len = ngx_min(qc->ctp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - if (!qc->validated) { - max = qc->received * 3; - max = (c->sent >= max) ? 0 : max - c->sent; - len = ngx_min(len, (size_t) max); - } - - pad = ngx_quic_get_padding_level(c); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) - ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - - n = ngx_quic_output_packet(c, ctx, p, len, min); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - - p += n; - len -= n; - } - - len = p - dst; - if (len == 0) { - break; - } - - n = ngx_quic_send(c, dst, len); - if (n == NGX_ERROR) { - return NGX_ERROR; - } - } - - if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); - } - - ngx_quic_set_lost_timer(c); - - return NGX_OK; -} - - -static ngx_uint_t -ngx_quic_get_padding_level(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - /* - * 14.1. Initial Datagram Size - * - * Similarly, a server MUST expand the payload of all UDP datagrams - * carrying ack-eliciting Initial packets to at least the smallest - * allowed maximum datagram size of 1200 bytes - */ - - qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->need_ack) { - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - return 1; - } - } - - return NGX_QUIC_SEND_CTX_LAST; -} - - -static ngx_int_t -ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - ngx_msec_t delay; - ngx_quic_connection_t *qc; - - if (!ctx->send_ack) { - return NGX_OK; - } - - if (ctx->level == ssl_encryption_application) { - - delay = ngx_current_msec - ctx->ack_delay_start; - qc = ngx_quic_get_connection(c); - - if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP - && delay < qc->tp.max_ack_delay) - { - if (!qc->push.timer_set && !qc->closing) { - ngx_add_timer(&qc->push, - qc->tp.max_ack_delay - delay); - } - - return NGX_OK; - } - } - - if (ngx_quic_send_ack(c, ctx) != NGX_OK) { - return NGX_ERROR; - } - - ctx->send_ack = 0; - - return NGX_OK; -} - - -static ssize_t -ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min) -{ - size_t len, hlen, pad_len; - u_char *p; - ssize_t flen; - ngx_str_t out, res; - ngx_int_t rc; - ngx_uint_t nframes; - ngx_msec_t now; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_header_t pkt; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - - if (ngx_queue_empty(&ctx->frames)) { - return 0; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output %s packet max:%uz min:%uz", - ngx_quic_level_name(ctx->level), max, min); - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - hlen = (ctx->level == ssl_encryption_application) - ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; - - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; - - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - - now = ngx_current_msec; - nframes = 0; - p = src; - len = 0; - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - q = ngx_queue_next(q)) - { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (!pkt.need_ack && f->need_ack && max > cg->window) { - max = cg->window; - } - - if (hlen + len >= max) { - break; - } - - if (hlen + len + f->len > max) { - rc = ngx_quic_split_frame(c, f, max - hlen - len); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_DECLINED) { - break; - } - } - - if (f->need_ack) { - pkt.need_ack = 1; - } - - ngx_quic_log_frame(c->log, f, 1); - - flen = ngx_quic_create_frame(p, f); - if (flen == -1) { + if (sslerr != SSL_ERROR_WANT_READ) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); return NGX_ERROR; } - len += flen; - p += flen; - - f->pnum = ctx->pnum; - f->first = now; - f->last = now; - f->plen = 0; - - nframes++; - - if (f->flush) { - break; - } - } - - if (nframes == 0) { - return 0; - } - - out.data = src; - out.len = len; - - pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - - if (ctx->level == ssl_encryption_initial) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - - } else if (ctx->level == ssl_encryption_handshake) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; - - } else { - if (qc->key_phase) { - pkt.flags |= NGX_QUIC_PKT_KPHASE; - } - } - - ngx_quic_set_packet_number(&pkt, ctx); - - pkt.version = qc->version; - pkt.log = c->log; - pkt.level = ctx->level; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; - - pad_len = 4; - - if (min) { - hlen = EVP_GCM_TLS_TAG_LEN - + ngx_quic_create_header(&pkt, NULL, out.len, NULL); - - if (min > hlen + pad_len) { - pad_len = min - hlen; - } - } - - if (out.len < pad_len) { - ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); - out.len = pad_len; - } - - pkt.payload = out; - - res.data = data; - - ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet tx %s bytes:%ui" - " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, - pkt.number, pkt.num_len, pkt.trunc); - - if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return NGX_ERROR; - } - - ctx->pnum++; - - if (pkt.need_ack) { - /* move frames into the sent queue to wait for ack */ - - if (!qc->closing) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - f->plen = res.len; - - do { - q = ngx_queue_head(&ctx->frames); - ngx_queue_remove(q); - ngx_queue_insert_tail(&ctx->sent, q); - } while (--nframes); - } - - cg->in_flight += res.len; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", cg->in_flight); - } - - while (nframes--) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - return res.len; -} - - -static ssize_t -ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) -{ - ngx_buf_t b; - ngx_chain_t cl, *res; - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - b.pos = b.start = buf; - b.last = b.end = buf + len; - b.last_buf = 1; - b.temporary = 1; - - cl.buf = &b; - cl.next= NULL; - - res = c->send_chain(c, &cl, 0); - if (res == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - return len; -} - - -static void -ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) -{ - uint64_t delta; - - delta = ctx->pnum - ctx->largest_ack; - pkt->number = ctx->pnum; - - if (delta <= 0x7F) { - pkt->num_len = 1; - pkt->trunc = ctx->pnum & 0xff; - - } else if (delta <= 0x7FFF) { - pkt->num_len = 2; - pkt->flags |= 0x1; - pkt->trunc = ctx->pnum & 0xffff; - - } else if (delta <= 0x7FFFFF) { - pkt->num_len = 3; - pkt->flags |= 0x2; - pkt->trunc = ctx->pnum & 0xffffff; - - } else { - pkt->num_len = 4; - pkt->flags |= 0x3; - pkt->trunc = ctx->pnum & 0xffffffff; - } -} - - -static void -ngx_quic_pto_handler(ngx_event_t *ev) -{ - ngx_uint_t i; - ngx_msec_t now; - ngx_queue_t *q, *next; - ngx_connection_t *c; - ngx_quic_frame_t *f; - 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; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - q = ngx_queue_head(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->pnum <= ctx->largest_ack - && ctx->largest_ack != NGX_QUIC_UNSET_PN) - { - continue; - } - - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { - continue; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic pto %s pto_count:%ui", - ngx_quic_level_name(ctx->level), qc->pto_count); - - for (q = ngx_queue_head(&ctx->frames); - q != ngx_queue_sentinel(&ctx->frames); - /* void */) - { - next = ngx_queue_next(q); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->type == NGX_QUIC_FT_PING) { - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } - - q = next; - } - - for (q = ngx_queue_head(&ctx->sent); - q != ngx_queue_sentinel(&ctx->sent); - /* void */) - { - next = ngx_queue_next(q); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); + return NGX_OK; + } - if (f->type == NGX_QUIC_FT_PING) { - ngx_quic_congestion_lost(c, f); - ngx_queue_remove(q); - ngx_quic_free_frame(c, f); - } + if (SSL_in_init(ssl_conn)) { + return NGX_OK; + } - q = next; - } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - /* enforce 2 udp datagrams */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handshake completed successfully"); - f = ngx_quic_alloc_frame(c); - if (f == NULL) { - break; - } + c->ssl->handshaked = 1; - f->level = ctx->level; - f->type = NGX_QUIC_FT_PING; - f->flush = 1; + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } - ngx_quic_queue_frame(qc, f); + /* 12.4 Frames and frame types, figure 8 */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); - f = ngx_quic_alloc_frame(c); - if (f == NULL) { - break; - } + if (ngx_quic_send_new_token(c) != NGX_OK) { + return NGX_ERROR; + } - f->level = ctx->level; - f->type = NGX_QUIC_FT_PING; + /* + * Generating next keys before a key update is received. + * See quic-tls 9.4 Header Protection Timing Side-Channels. + */ - ngx_quic_queue_frame(qc, f); + if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { + return NGX_ERROR; } - qc->pto_count++; + /* + * 4.10.2 An endpoint MUST discard its handshake keys + * when the TLS handshake is confirmed + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); - ngx_quic_connstate_dbg(c); + if (ngx_quic_issue_server_ids(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; } @@ -3689,265 +2118,6 @@ ngx_quic_push_handler(ngx_event_t *ev) } -static -void ngx_quic_lost_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); - - c = ev->data; - - if (ngx_quic_detect_lost(c) != NGX_OK) { - ngx_quic_close_connection(c, NGX_ERROR); - } - - ngx_quic_connstate_dbg(c); -} - - -static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c) -{ - ngx_uint_t i; - ngx_msec_t now, wait, thr; - ngx_queue_t *q; - ngx_quic_frame_t *start; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - now = ngx_current_msec; - thr = ngx_quic_lost_threshold(qc); - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - - ctx = &qc->send_ctx[i]; - - if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { - continue; - } - - while (!ngx_queue_empty(&ctx->sent)) { - - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (start->pnum > ctx->largest_ack) { - break; - } - - wait = start->last + thr - now; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", - start->pnum, thr, (ngx_int_t) wait, start->level); - - if ((ngx_msec_int_t) wait > 0 - && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) - { - break; - } - - ngx_quic_resend_frames(c, ctx); - } - } - - ngx_quic_set_lost_timer(c); - - return NGX_OK; -} - - -static void -ngx_quic_set_lost_timer(ngx_connection_t *c) -{ - ngx_uint_t i; - ngx_msec_t now; - ngx_queue_t *q; - ngx_msec_int_t lost, pto, w; - ngx_quic_frame_t *f; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - now = ngx_current_msec; - - lost = -1; - pto = -1; - - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ctx = &qc->send_ctx[i]; - - if (ngx_queue_empty(&ctx->sent)) { - continue; - } - - if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { - q = ngx_queue_head(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); - - if (f->pnum <= ctx->largest_ack) { - if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { - w = 0; - } - - if (lost == -1 || w < lost) { - lost = w; - } - } - } - - q = ngx_queue_last(&ctx->sent); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); - - if (w < 0) { - w = 0; - } - - if (pto == -1 || w < pto) { - pto = w; - } - } - - if (qc->pto.timer_set) { - ngx_del_timer(&qc->pto); - } - - if (lost != -1) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic lost timer lost:%M", lost); - - qc->pto.handler = ngx_quic_lost_handler; - ngx_add_timer(&qc->pto, lost); - return; - } - - if (pto != -1) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic lost timer pto:%M", pto); - - qc->pto.handler = ngx_quic_pto_handler; - ngx_add_timer(&qc->pto, pto); - return; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); -} - - -static void -ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) -{ - size_t n; - ngx_buf_t *b; - ngx_queue_t *q; - ngx_quic_frame_t *f, *start; - ngx_quic_stream_t *sn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - q = ngx_queue_head(&ctx->sent); - start = ngx_queue_data(q, ngx_quic_frame_t, queue); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic resend packet pnum:%uL", start->pnum); - - ngx_quic_congestion_lost(c, start); - - do { - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - - if (f->pnum != start->pnum) { - break; - } - - q = ngx_queue_next(q); - - ngx_queue_remove(&f->queue); - - switch (f->type) { - case NGX_QUIC_FT_ACK: - case NGX_QUIC_FT_ACK_ECN: - if (ctx->level == ssl_encryption_application) { - /* force generation of most recent acknowledgment */ - ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; - } - - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_PING: - case NGX_QUIC_FT_PATH_RESPONSE: - case NGX_QUIC_FT_CONNECTION_CLOSE: - ngx_quic_free_frame(c, f); - break; - - case NGX_QUIC_FT_MAX_DATA: - f->u.max_data.max_data = qc->streams.recv_max_data; - ngx_quic_queue_frame(qc, f); - break; - - 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; - ngx_quic_queue_frame(qc, f); - break; - - case NGX_QUIC_FT_MAX_STREAM_DATA: - sn = ngx_quic_find_stream(&qc->streams.tree, - f->u.max_stream_data.id); - if (sn == NULL) { - ngx_quic_free_frame(c, f); - break; - } - - b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); - - if (f->u.max_stream_data.limit < n) { - f->u.max_stream_data.limit = n; - } - - ngx_quic_queue_frame(qc, f); - break; - - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - - if (sn && sn->c->write->error) { - /* RESET_STREAM was sent */ - ngx_quic_free_frame(c, f); - break; - } - - /* fall through */ - - default: - ngx_queue_insert_tail(&ctx->frames, &f->queue); - } - - } while (q != ngx_queue_sentinel(&ctx->sent)); - - if (qc->closing) { - return; - } - - ngx_post_event(&qc->push, &ngx_posted_events); -} - - void ngx_quic_shutdown_quic(ngx_connection_t *c) { @@ -3981,100 +2151,6 @@ ngx_quic_shutdown_quic(ngx_connection_t *c) } - -static void -ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - ngx_msec_t timer; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - - if (f->plen == 0) { - return; - } - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - cg->in_flight -= f->plen; - - timer = f->last - cg->recovery_start; - - if ((ngx_msec_int_t) timer <= 0) { - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion ack recovery win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - return; - } - - if (cg->window < cg->ssthresh) { - cg->window += f->plen; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion slow start win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - } else { - cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion avoidance win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - } - - /* prevent recovery_start from wrapping */ - - timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; - - if ((ngx_msec_int_t) timer < 0) { - cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; - } -} - - -static void -ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) -{ - ngx_msec_t timer; - ngx_quic_congestion_t *cg; - ngx_quic_connection_t *qc; - - if (f->plen == 0) { - return; - } - - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - cg->in_flight -= f->plen; - f->plen = 0; - - timer = f->last - cg->recovery_start; - - if ((ngx_msec_int_t) timer <= 0) { - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost recovery win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); - - return; - } - - cg->recovery_start = ngx_current_msec; - cg->window /= 2; - - if (cg->window < qc->tp.max_udp_payload_size * 2) { - cg->window = qc->tp.max_udp_payload_size * 2; - } - - cg->ssthresh = cg->window; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion lost win:%uz ss:%z if:%uz", - cg->window, cg->ssthresh, cg->in_flight); -} - - uint32_t ngx_quic_version(ngx_connection_t *c) { diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c new file mode 100644 index 000000000..8f78ac51e --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.c @@ -0,0 +1,1081 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_ACK_GAP 2 + +/* quic-recovery, section 6.1.1, Packet Threshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* quic-recovery, section 6.1.2, Time Threshold */ +#define NGX_QUIC_TIME_THR 1.125 +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +#define ngx_quic_lost_threshold(qc) \ + ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ + NGX_QUIC_TIME_GRANULARITY) + + +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_msec_t *send_time); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_lost_handler(ngx_event_t *ev); + + +ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *f) +{ + ssize_t n; + u_char *pos, *end; + uint64_t min, max, gap, range; + ngx_msec_t send_time; + ngx_uint_t i; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + + ack = &f->u.ack; + + /* + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + * (19.3.1) + */ + + if (ack->first_range > ack->largest) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + max = ack->largest; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + + /* 13.2.3. Receiver Tracking of ACK Frames */ + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { + ctx->largest_ack = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic updated largest received ack:%uL", max); + + /* + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time); + } + } + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap + 2 > min) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + max = min - gap - 2; + + if (range > max) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_quic_detect_lost(c); +} + + +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; + + if (c->ssl->handshaked) { + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample latest:%M min:%M avg:%M var:%M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t min, uint64_t max, ngx_msec_t *send_time) +{ + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + *send_time = NGX_TIMER_INFINITE; + found = 0; + + q = ngx_queue_last(&ctx->sent); + + while (q != ngx_queue_sentinel(&ctx->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_prev(q); + + if (f->pnum >= min && f->pnum <= max) { + ngx_quic_congestion_ack(c, f); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + ngx_quic_handle_stream_ack(c, f); + break; + } + + if (f->pnum == max) { + *send_time = f->last; + } + + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + } + } + + if (!found) { + + if (max < ctx->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ACK for the packet not sent"); + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; + + return NGX_ERROR; + } + + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + + qc->pto_count = 0; + + return NGX_OK; +} + + +void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->in_flight -= f->plen; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + return; + } + + if (cg->window < cg->ssthresh) { + cg->window += f->plen; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } +} + + +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + + base = ctx->largest_range; + + if (base == NGX_QUIC_UNSET_PN) { + return; + } + + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = NGX_QUIC_UNSET_PN; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + +static ngx_int_t +ngx_quic_detect_lost(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now, wait, thr; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + thr = ngx_quic_lost_threshold(qc); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } + + while (!ngx_queue_empty(&ctx->sent)) { + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum > ctx->largest_ack) { + break; + } + + wait = start->last + thr - now; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + break; + } + + ngx_quic_resend_frames(c, ctx); + } + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t n; + ngx_buf_t *b; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *sn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); + + ngx_quic_congestion_lost(c, start); + + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != start->pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + 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; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + sn = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (sn == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + b = sn->b; + n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + + if (f->u.max_stream_data.limit < n) { + f->u.max_stream_data.limit = n; + } + + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (sn && sn->c->write->error) { + /* RESET_STREAM was sent */ + ngx_quic_free_frame(c, f); + break; + } + + /* fall through */ + + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->in_flight -= f->plen; + f->plen = 0; + + timer = f->last - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + return; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); +} + + +void +ngx_quic_set_lost_timer(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + lost = -1; + pto = -1; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_lost_threshold(qc) - now); + + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); + + if (w < 0) { + w = 0; + } + + if (pto == -1 || w < pto) { + pto = w; + } + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); + + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; + } + + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); +} + + +ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* PTO calculation: quic-recovery, Appendix 8 */ + duration = qc->avg_rtt; + + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration <<= qc->pto_count; + + if (qc->congestion.in_flight == 0) { /* no in-flight packets */ + return duration; + } + + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay << qc->pto_count; + } + + return duration; +} + + +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q, *next; + ngx_connection_t *c; + ngx_quic_frame_t *f; + 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; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } + + if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + q = next; + } + + for (q = ngx_queue_head(&ctx->sent); + q != ngx_queue_sentinel(&ctx->sent); + /* void */) + { + next = ngx_queue_next(q); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->type == NGX_QUIC_FT_PING) { + ngx_quic_congestion_lost(c, f); + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + q = next; + } + + /* enforce 2 udp datagrams */ + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->flush = 1; + + ngx_quic_queue_frame(qc, f); + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + break; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, f); + } + + qc->pto_count++; + + ngx_quic_connstate_dbg(c); +} + + +ngx_int_t +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; + + c->log->action = "preparing ack"; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, + ctx->first_range, ctx->nranges); + + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&qc->push, &ngx_posted_events); + + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; + + if (ctx->pending_ack == NGX_QUIC_UNSET_PN + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == NGX_QUIC_UNSET_PN) { + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; + + if (!ctx->send_ack) { + return NGX_OK; + } + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); + + if (ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } + + return NGX_OK; + } + } + + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ctx->send_ack = 0; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ack.h b/src/event/quic/ngx_event_quic_ack.h new file mode 100644 index 000000000..8e53446d7 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.h @@ -0,0 +1,31 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_ +#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); + +void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +void ngx_quic_resend_frames(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +void ngx_quic_set_lost_timer(ngx_connection_t *c); +void ngx_quic_pto_handler(ngx_event_t *ev); +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); + +ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); + +#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 17bc96435..6c89ab820 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -15,39 +15,22 @@ #include typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; #include #include #include #include +#include +#include -#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -#define NGX_QUIC_MAX_LONG_HEADER 56 - /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ - -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 -#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 - -#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ -#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ -#define NGX_QUIC_RETRY_BUFFER_SIZE 256 - /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ #define NGX_QUIC_MAX_TOKEN_SIZE 64 /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ /* quic-recovery, section 6.2.2, kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ -/* quic-recovery, section 6.1.1, Packet Threshold */ -#define NGX_QUIC_PKT_THR 3 /* packets */ -/* quic-recovery, section 6.1.2, Time Threshold */ -#define NGX_QUIC_TIME_THR 1.125 -#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ - -#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ - - #define NGX_QUIC_UNSET_PN (uint64_t) -1 #define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) @@ -124,7 +107,7 @@ typedef struct { * with Initial packet protection keys and acknowledged in packets which * are also Initial packets. */ -typedef struct { +struct ngx_quic_send_ctx_s { enum ssl_encryption_level_t level; uint64_t pnum; /* to be sent */ @@ -142,7 +125,7 @@ typedef struct { ngx_uint_t nranges; ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; ngx_uint_t send_ack; -} ngx_quic_send_ctx_t; +}; struct ngx_quic_connection_s { @@ -221,16 +204,22 @@ struct ngx_quic_connection_s { void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); -ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +void ngx_quic_shutdown_quic(ngx_connection_t *c); ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token); - -ngx_int_t ngx_quic_output(ngx_connection_t *c); -void ngx_quic_shutdown_quic(ngx_connection_t *c); +ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, + ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); /********************************* DEBUG *************************************/ +#if (NGX_DEBUG) +void ngx_quic_connstate_dbg(ngx_connection_t *c); +#else +#define ngx_quic_connstate_dbg(c) +#endif + + /* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ /* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ /* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c new file mode 100644 index 000000000..c8e483afb --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.c @@ -0,0 +1,851 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ +#define NGX_QUIC_MAX_LONG_HEADER 56 + /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ + +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 +#define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 + +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ + +/* + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + + +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_send_ctx_t *ctx); + + +size_t +ngx_quic_max_udp_payload(ngx_connection_t *c) +{ + /* TODO: path MTU discovery */ + +#if (NGX_HAVE_INET6) + if (c->sockaddr->sa_family == AF_INET6) { + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT6; + } +#endif + + return NGX_QUIC_MAX_UDP_PAYLOAD_OUT; +} + + +ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + off_t max; + size_t len, min, in_flight; + ssize_t n; + u_char *p; + ngx_uint_t i, pad; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; + + for ( ;; ) { + p = dst; + + len = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + if (!qc->validated) { + max = qc->received * 3; + max = (c->sent >= max) ? 0 : max - c->sent; + len = ngx_min(len, (size_t) max); + } + + pad = ngx_quic_get_padding_level(c); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + + len = p - dst; + if (len == 0) { + break; + } + + n = ngx_quic_send(c, dst, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + } + + if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* + * 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->need_ack) { + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + return 1; + } + } + + return NGX_QUIC_SEND_CTX_LAST; +} + + +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) +{ + size_t len, hlen, pad_len; + u_char *p; + ssize_t flen; + ngx_str_t out, res; + ngx_int_t rc; + ngx_uint_t nframes; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + hlen = (ctx->level == ssl_encryption_application) + ? NGX_QUIC_MAX_SHORT_HEADER + : NGX_QUIC_MAX_LONG_HEADER; + + hlen += EVP_GCM_TLS_TAG_LEN; + hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (!pkt.need_ack && f->need_ack && max > cg->window) { + max = cg->window; + } + + if (hlen + len >= max) { + break; + } + + if (hlen + len + f->len > max) { + rc = ngx_quic_split_frame(c, f, max - hlen - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } + } + + if (f->need_ack) { + pkt.need_ack = 1; + } + + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + + f->pnum = ctx->pnum; + f->first = now; + f->last = now; + f->plen = 0; + + nframes++; + + if (f->flush) { + break; + } + } + + if (nframes == 0) { + return 0; + } + + out.data = src; + out.len = len; + + pkt.keys = qc->keys; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; + } + } + + ngx_quic_set_packet_number(&pkt, ctx); + + pkt.version = qc->version; + pkt.log = c->log; + pkt.level = ctx->level; + pkt.dcid = qc->scid; + pkt.scid = qc->dcid; + + pad_len = 4; + + if (min) { + hlen = EVP_GCM_TLS_TAG_LEN + + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + + if (min > hlen + pad_len) { + pad_len = min - hlen; + } + } + + if (out.len < pad_len) { + ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); + out.len = pad_len; + } + + pkt.payload = out; + + res.data = data; + + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet tx %s bytes:%ui" + " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", + ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, + pkt.number, pkt.num_len, pkt.trunc); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + if (pkt.need_ack) { + /* move frames into the sent queue to wait for ack */ + + if (!qc->closing) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + f->plen = res.len; + + do { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sent, q); + } while (--nframes); + } + + cg->in_flight += res.len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + } + + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + ngx_quic_free_frame(c, f); + } + + return res.len; +} + + +ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) +{ + ngx_buf_t b; + ngx_chain_t cl, *res; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.pos = b.start = buf; + b.last = b.end = buf + len; + b.last_buf = 1; + b.temporary = 1; + + cl.buf = &b; + cl.next= NULL; + + res = c->send_chain(c, &cl, 0); + if (res == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + return len; +} + + +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; + } +} + + +ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); +#endif + + (void) ngx_quic_send(c, buf, len); + + return NGX_ERROR; +} + + +int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() lvl:%d alert:%d", + (int) level, (int) alert); + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error_level = level; + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "TLS alert"; + qc->error_app = 0; + qc->error_ftype = 0; + + if (ngx_quic_send_cc(c) != NGX_OK) { + return 0; + } + + return 1; +} + + +ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) ngx_quic_send(c, buf, len); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->draining) { + return NGX_OK; + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = qc->error_level; + frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; + + if (qc->error_reason) { + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; + } + + ngx_quic_queue_frame(qc, frame); + + qc->last_cc = ngx_current_msec; + + return ngx_quic_output(c); +} + + +ngx_int_t +ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, + ngx_uint_t err, const char *reason) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_frame_t frame; + ngx_quic_header_t pkt; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + frame.level = inpkt->level; + frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = err; + + frame.u.close.reason.data = (u_char *) reason; + frame.u.close.reason.len = ngx_strlen(reason); + + len = ngx_quic_create_frame(NULL, &frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 1); + + len = ngx_quic_create_frame(src, &frame); + if (len == -1) { + return NGX_ERROR; + } + + pkt.keys = ngx_quic_keys_new(c->pool); + if (pkt.keys == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid, + inpkt->version) + != NGX_OK) + { + return NGX_ERROR; + } + + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG + | NGX_QUIC_PKT_INITIAL; + + pkt.num_len = 1; + /* + * pkt.num = 0; + * pkt.trunc = 0; + */ + + pkt.version = inpkt->version; + pkt.log = c->log; + pkt.level = inpkt->level; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *inpkt) +{ + time_t expires; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + u_char dcid[NGX_QUIC_SERVER_CID_LEN]; + + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; + + if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, + expires, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = inpkt->version; + pkt.log = c->log; + + pkt.odcid = inpkt->dcid; + pkt.dcid = inpkt->scid; + + /* TODO: generate routable dcid */ + if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; + pkt.scid.data = dcid; + + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); +#endif + + len = ngx_quic_send(c, res.data, res.len); + if (len == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); + + /* + * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry + * packet in response to a single UDP datagram. + * NGX_DONE will stop quic_input() from processing further + */ + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c) +{ + time_t expires; + ngx_str_t token; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->conf->retry) { + return NGX_OK; + } + + expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; + + if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->u.token.length = token.len; + frame->u.token.data = token.data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t len, left; + uint64_t ack_delay; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, **ll; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->tp.ack_delay_exponent; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + ll = &frame->data; + b = NULL; + + for (i = 0; i < ctx->nranges; i++) { + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; + } + + *ll = NULL; + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ack_delay; + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h new file mode 100644 index 000000000..3277122cb --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.h @@ -0,0 +1,40 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ + + +#include +#include + + +size_t ngx_quic_max_udp_payload(ngx_connection_t *c); + +ngx_int_t ngx_quic_output(ngx_connection_t *c); + +ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, + ngx_quic_header_t *inpkt); + +int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); + +ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); +ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, + ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); + +ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); + +ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); + +#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ -- cgit v1.2.3 From e0b73cf6a2eba148e09fdbe2e16b9a53d1b546cc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:41:52 +0300 Subject: QUIC: separate files for tokens related processing. --- auto/modules | 2 + src/event/quic/ngx_event_quic.c | 278 --------------------------- src/event/quic/ngx_event_quic_connection.h | 8 +- src/event/quic/ngx_event_quic_tokens.c | 292 +++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_tokens.h | 22 +++ 5 files changed, 317 insertions(+), 285 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_tokens.c create mode 100644 src/event/quic/ngx_event_quic_tokens.h diff --git a/auto/modules b/auto/modules index 925741537..fcaed67b4 100644 --- a/auto/modules +++ b/auto/modules @@ -1347,6 +1347,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h \ src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_tokens.h \ src/event/quic/ngx_event_quic_ack.h \ src/event/quic/ngx_event_quic_output.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ @@ -1356,6 +1357,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c \ src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_tokens.c \ src/event/quic/ngx_event_quic_ack.c \ src/event/quic/ngx_event_quic_output.c" diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 365488701..cf9e64628 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -7,7 +7,6 @@ #include #include #include -#include #include @@ -42,10 +41,6 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); -static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, - u_char buf[20]); -static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, - u_char *key, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -620,32 +615,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } -ngx_int_t -ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, - u_char *token) -{ - ngx_str_t tmp; - - tmp.data = secret; - tmp.len = NGX_QUIC_SR_KEY_LEN; - - if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, - NGX_QUIC_SR_TOKEN_LEN) - != NGX_OK) - { - return NGX_ERROR; - } - -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif - - return NGX_OK; -} - - static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -690,253 +659,6 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) } -ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, - ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) -{ - int len, iv_len; - u_char *p, *iv; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - - ngx_quic_address_hash(c, !is_retry, in); - - p = in + 20; - - p = ngx_cpymem(p, &exp, sizeof(time_t)); - - *p++ = is_retry ? 1 : 0; - - if (odcid) { - *p++ = odcid->len; - p = ngx_cpymem(p, odcid->data, odcid->len); - - } else { - *p++ = 0; - } - - len = p - in; - - cipher = EVP_aes_256_cbc(); - iv_len = EVP_CIPHER_iv_length(cipher); - - token->len = iv_len + len + EVP_CIPHER_block_size(cipher); - token->data = ngx_pnalloc(c->pool, token->len); - if (token->data == NULL) { - return NGX_ERROR; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - iv = token->data; - - if (RAND_bytes(iv, iv_len) <= 0 - || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) - { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len = iv_len; - - if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - EVP_CIPHER_CTX_free(ctx); - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic new token len:%uz %xV", token->len, token); -#endif - - return NGX_OK; -} - - -static void -ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) -{ - size_t len; - u_char *data; - ngx_sha1_t sha1; - struct sockaddr_in *sin; -#if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; -#endif - - len = (size_t) c->socklen; - data = (u_char *) c->sockaddr; - - if (no_port) { - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; - - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; - - break; -#endif - - case AF_INET: - sin = (struct sockaddr_in *) c->sockaddr; - - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; - - break; - } - } - - ngx_sha1_init(&sha1); - ngx_sha1_update(&sha1, data, len); - ngx_sha1_final(buf, &sha1); -} - - -static ngx_int_t -ngx_quic_validate_token(ngx_connection_t *c, u_char *key, - ngx_quic_header_t *pkt) -{ - int len, tlen, iv_len; - u_char *iv, *p; - time_t now, exp; - size_t total; - ngx_str_t odcid; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - u_char addr_hash[20]; - u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - - /* Retry token or NEW_TOKEN in a previous connection */ - - cipher = EVP_aes_256_cbc(); - iv = pkt->token.data; - iv_len = EVP_CIPHER_iv_length(cipher); - - /* sanity checks */ - - if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { - goto garbage; - } - - if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { - goto garbage; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - p = pkt->token.data + iv_len; - len = pkt->token.len - iv_len; - - if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - goto garbage; - } - total = len; - - if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { - EVP_CIPHER_CTX_free(ctx); - goto garbage; - } - total += tlen; - - EVP_CIPHER_CTX_free(ctx); - - if (total < (20 + sizeof(time_t) + 2)) { - goto garbage; - } - - p = tdec + 20; - - ngx_memcpy(&exp, p, sizeof(time_t)); - p += sizeof(time_t); - - pkt->retried = (*p++ == 1); - - ngx_quic_address_hash(c, !pkt->retried, addr_hash); - - if (ngx_memcmp(tdec, addr_hash, 20) != 0) { - goto bad_token; - } - - odcid.len = *p++; - if (odcid.len) { - if (odcid.len > NGX_QUIC_MAX_CID_LEN) { - goto bad_token; - } - - if ((size_t)(tdec + total - p) < odcid.len) { - goto bad_token; - } - - odcid.data = p; - p += odcid.len; - } - - now = ngx_time(); - - if (now > exp) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); - return NGX_DECLINED; - } - - if (odcid.len) { - pkt->odcid.len = odcid.len; - pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); - if (pkt->odcid.data == NULL) { - return NGX_ERROR; - } - - } else { - pkt->odcid = pkt->dcid; - } - - pkt->validated = 1; - - return NGX_OK; - -garbage: - - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); - - return NGX_ABORT; - -bad_token: - - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - - return NGX_DECLINED; -} - - static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 6c89ab820..9e86394f7 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -21,13 +21,11 @@ typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; #include #include #include +#include #include #include -#define NGX_QUIC_MAX_TOKEN_SIZE 64 - /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ - /* quic-recovery, section 6.2.2, kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ @@ -206,10 +204,6 @@ struct ngx_quic_connection_s { void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); void ngx_quic_shutdown_quic(ngx_connection_t *c); -ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - u_char *secret, u_char *token); -ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, - ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); /********************************* DEBUG *************************************/ diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c new file mode 100644 index 000000000..cbfc356d7 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -0,0 +1,292 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + + +static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, + u_char buf[20]); + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, + ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) +{ + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + ngx_quic_address_hash(c, !is_retry, in); + + p = in + 20; + + p = ngx_cpymem(p, &exp, sizeof(time_t)); + + *p++ = is_retry ? 1 : 0; + + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); + + } else { + *p++ = 0; + } + + len = p - in; + + cipher = EVP_aes_256_cbc(); + iv_len = EVP_CIPHER_iv_length(cipher); + + token->len = iv_len + len + EVP_CIPHER_block_size(cipher); + token->data = ngx_pnalloc(c->pool, token->len); + if (token->data == NULL) { + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static void +ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) +{ + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + len = (size_t) c->socklen; + data = (u_char *) c->sockaddr; + + if (no_port) { + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + } + + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + + /* Retry token or NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_cbc(); + iv = pkt->token.data; + iv_len = EVP_CIPHER_iv_length(cipher); + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + goto garbage; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { + goto garbage; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len; + + if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total = len; + + if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total += tlen; + + EVP_CIPHER_CTX_free(ctx); + + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } + + p = tdec + 20; + + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); + + pkt->retried = (*p++ == 1); + + ngx_quic_address_hash(c, !pkt->retried, addr_hash); + + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } + + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } + + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } + + odcid.data = p; + p += odcid.len; + } + + now = ngx_time(); + + if (now > exp) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); + if (pkt->odcid.data == NULL) { + return NGX_ERROR; + } + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + + return NGX_OK; + +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + return NGX_DECLINED; +} diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h new file mode 100644 index 000000000..f3185db22 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); +ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, + ngx_str_t *token, ngx_str_t *odcid, 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); + +#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ -- cgit v1.2.3 From 2fd1aac46d654905242ee2a0d0b5dc6997fd8569 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 14 Apr 2021 14:47:04 +0300 Subject: QUIC: separate files for SSL library interfaces. --- auto/modules | 2 + src/event/quic/ngx_event_quic.c | 505 +---------------------------- src/event/quic/ngx_event_quic_connection.h | 5 + src/event/quic/ngx_event_quic_frames.c | 4 - src/event/quic/ngx_event_quic_ssl.c | 500 ++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_ssl.h | 22 ++ 6 files changed, 531 insertions(+), 507 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_ssl.c create mode 100644 src/event/quic/ngx_event_quic_ssl.h diff --git a/auto/modules b/auto/modules index fcaed67b4..6d5a61e22 100644 --- a/auto/modules +++ b/auto/modules @@ -1347,6 +1347,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h \ src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_ssl.h \ src/event/quic/ngx_event_quic_tokens.h \ src/event/quic/ngx_event_quic_ack.h \ src/event/quic/ngx_event_quic_output.h" @@ -1357,6 +1358,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c \ src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_ssl.c \ src/event/quic/ngx_event_quic_tokens.c \ src/event/quic/ngx_event_quic_ack.c \ src/event/quic/ngx_event_quic_output.c" diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cf9e64628..2dd839fc3 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -10,38 +10,10 @@ #include -/* - * 7.4. Cryptographic Message Buffering - * Implementations MUST support buffering at least 4096 bytes of data - */ -#define NGX_QUIC_MAX_BUFFERED 65535 - - -#if BORINGSSL_API_VERSION >= 10 -static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len); -static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *secret, size_t secret_len); -#else -static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *read_secret, - const uint8_t *write_secret, size_t secret_len); -#endif - -static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len); -static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); - - -static ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, - ngx_quic_tp_t *ctp); static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); @@ -53,19 +25,11 @@ static ngx_int_t ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt); -static void ngx_quic_discard_ctx(ngx_connection_t *c, - enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt); - -static 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_crypto_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - static void ngx_quic_push_handler(ngx_event_t *ev); @@ -92,19 +56,6 @@ ngx_module_t ngx_quic_module = { }; -static SSL_QUIC_METHOD quic_method = { -#if BORINGSSL_API_VERSION >= 10 - ngx_quic_set_read_secret, - ngx_quic_set_write_secret, -#else - ngx_quic_set_encryption_secrets, -#endif - ngx_quic_add_handshake_data, - ngx_quic_flush_flight, - ngx_quic_send_alert, -}; - - #if (NGX_DEBUG) void @@ -173,227 +124,7 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) #endif -#if BORINGSSL_API_VERSION >= 10 - -static int -ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *rsecret, size_t secret_len) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_read_secret() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic read secret len:%uz %*xs", secret_len, - secret_len, rsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, - cipher, rsecret, secret_len); -} - - -static int -ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_write_secret() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic write secret len:%uz %*xs", secret_len, - secret_len, wsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); -} - -#else - -static int -ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *rsecret, - const uint8_t *wsecret, size_t secret_len) -{ - ngx_connection_t *c; - const SSL_CIPHER *cipher; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_set_encryption_secrets() level:%d", level); -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic read secret len:%uz %*xs", secret_len, - secret_len, rsecret); -#endif - - cipher = SSL_get_current_cipher(ssl_conn); - - if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, - cipher, rsecret, secret_len) - != 1) - { - return 0; - } - - if (level == ssl_encryption_early_data) { - return 1; - } - -#ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic write secret len:%uz %*xs", secret_len, - secret_len, wsecret); -#endif - - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); -} - -#endif - - -static int -ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, const uint8_t *data, size_t len) -{ - u_char *p, *end; - size_t client_params_len; - const uint8_t *client_params; - ngx_quic_tp_t ctp; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; - ngx_quic_frames_stream_t *fs; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - qc = ngx_quic_get_connection(c); - - 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 defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - if (qc->conf->require_alpn) { - unsigned int len; - const unsigned char *data; - - SSL_get0_alpn_selected(ssl_conn, &data, &len); - - if (len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; - qc->error_reason = "unsupported protocol in ALPN extension"; - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; - } - } -#endif - - 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); - - if (client_params_len == 0) { - /* quic-tls 8.2 */ - 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"); - return 0; - } - - 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)); - - 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 0; - } - - if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { - return 0; - } - - qc->client_tp_done = 1; - } - - fs = &qc->crypto[level]; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return 0; - } - - frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); - if (frame->data == NGX_CHAIN_ERROR) { - return 0; - } - - frame->level = level; - frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset = fs->sent; - frame->u.crypto.length = len; - - fs->sent += len; - - ngx_quic_queue_frame(qc, frame); - - return 1; -} - - -static int -ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) -{ -#if (NGX_DEBUG) - ngx_connection_t *c; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_flush_flight()"); -#endif - return 1; -} - - -static ngx_int_t +ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) { ngx_quic_connection_t *qc; @@ -659,88 +390,6 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static ngx_int_t -ngx_quic_init_connection(ngx_connection_t *c) -{ - u_char *p; - size_t clen; - ssize_t len; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { - return NGX_ERROR; - } - - c->ssl->no_wait_shutdown = 1; - - ssl_conn = c->ssl->connection; - - if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_method() failed"); - return NGX_ERROR; - } - -#ifdef SSL_READ_EARLY_DATA_SUCCESS - if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { - SSL_set_quic_early_data_enabled(ssl_conn, 1); - } -#endif - -#if BORINGSSL_API_VERSION >= 13 - SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); -#endif - - if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, - qc->tp.sr_token) - != NGX_OK) - { - return NGX_ERROR; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); - - len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); - /* always succeeds */ - - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - return NGX_ERROR; - } - - len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); - if (len < 0) { - return NGX_ERROR; - } - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic transport parameters len:%uz %*xs", len, len, p); -#endif - - if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_transport_params() failed"); - return NGX_ERROR; - } - -#if NGX_OPENSSL_QUIC_ZRTT_CTX - if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic SSL_set_quic_early_data_context() failed"); - return NGX_ERROR; - } -#endif - - return NGX_OK; -} - - static void ngx_quic_input_handler(ngx_event_t *rev) { @@ -1361,7 +1010,7 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static void +void ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) { ngx_queue_t *q; @@ -1672,156 +1321,6 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) } -static ngx_int_t -ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, - ngx_quic_frame_t *frame) -{ - uint64_t last; - ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - ngx_quic_crypto_frame_t *f; - ngx_quic_frames_stream_t *fs; - - qc = ngx_quic_get_connection(c); - fs = &qc->crypto[pkt->level]; - f = &frame->u.crypto; - - /* no overflow since both values are 62-bit */ - last = f->offset + f->length; - - if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { - qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; - return NGX_ERROR; - } - - rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, - NULL); - if (rc != NGX_DECLINED) { - return rc; - } - - /* speeding up handshake completion */ - - if (pkt->level == ssl_encryption_initial) { - ctx = ngx_quic_get_send_ctx(qc, pkt->level); - - if (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - while (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - } - } - } - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - int n, sslerr; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - ssl_conn = c->ssl->connection; - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - - if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), - b->pos, b->last - b->pos)) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "SSL_provide_quic_data() failed"); - return NGX_ERROR; - } - } - - n = SSL_do_handshake(ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", - (int) SSL_quic_read_level(ssl_conn), - (int) SSL_quic_write_level(ssl_conn)); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - - if (n <= 0) { - sslerr = SSL_get_error(ssl_conn, n); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", - sslerr); - - if (sslerr != SSL_ERROR_WANT_READ) { - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - return NGX_ERROR; - } - - return NGX_OK; - } - - if (SSL_in_init(ssl_conn)) { - return NGX_OK; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake completed successfully"); - - c->ssl->handshaked = 1; - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } - - /* 12.4 Frames and frame types, figure 8 */ - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; - ngx_quic_queue_frame(qc, frame); - - if (ngx_quic_send_new_token(c) != NGX_OK) { - return NGX_ERROR; - } - - /* - * Generating next keys before a key update is received. - * See quic-tls 9.4 Header Protection Timing Side-Channels. - */ - - if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { - return NGX_ERROR; - } - - /* - * 4.10.2 An endpoint MUST discard its handshake keys - * when the TLS handshake is confirmed - */ - ngx_quic_discard_ctx(c, ssl_encryption_handshake); - - if (ngx_quic_issue_server_ids(c) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - static void ngx_quic_push_handler(ngx_event_t *ev) { diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 9e86394f7..eb8507050 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -21,6 +21,7 @@ typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; #include #include #include +#include #include #include #include @@ -201,6 +202,10 @@ 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, + enum ssl_encryption_level_t level); void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); void ngx_quic_shutdown_quic(ngx_connection_t *c); diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 33af5ddd3..e0914b92f 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -469,10 +469,6 @@ done: } -ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - - ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c new file mode 100644 index 000000000..e68952e26 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -0,0 +1,500 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +/* + * 7.4. Cryptographic Message Buffering + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + + +#if BORINGSSL_API_VERSION >= 10 +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +#endif + +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); + + +static SSL_QUIC_METHOD quic_method = { +#if BORINGSSL_API_VERSION >= 10 + ngx_quic_set_read_secret, + ngx_quic_set_write_secret, +#else + ngx_quic_set_encryption_secrets, +#endif + ngx_quic_add_handshake_data, + ngx_quic_flush_flight, + ngx_quic_send_alert, +}; + + +#if BORINGSSL_API_VERSION >= 10 + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + cipher, rsecret, secret_len); +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len); +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + cipher = SSL_get_current_cipher(ssl_conn); + + if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + cipher, rsecret, secret_len) + != 1) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + return 1; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len); +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *end; + size_t client_params_len; + const uint8_t *client_params; + ngx_quic_tp_t ctp; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + ngx_quic_frames_stream_t *fs; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + 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 defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + if (qc->conf->require_alpn) { + unsigned int len; + const unsigned char *data; + + SSL_get0_alpn_selected(ssl_conn, &data, &len); + + if (len == 0) { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + } +#endif + + 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); + + if (client_params_len == 0) { + /* quic-tls 8.2 */ + 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"); + return 0; + } + + 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)); + + 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 0; + } + + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { + return 0; + } + + qc->client_tp_done = 1; + } + + fs = &qc->crypto[level]; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } + + frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); + if (frame->data == NGX_CHAIN_ERROR) { + return 0; + } + + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = fs->sent; + frame->u.crypto.length = len; + + fs->sent += len; + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ +#if (NGX_DEBUG) + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); +#endif + return 1; +} + + +ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + ngx_quic_frames_stream_t *fs; + + qc = ngx_quic_get_connection(c); + fs = &qc->crypto[pkt->level]; + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } + + rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, + NULL); + if (rc != NGX_DECLINED) { + return rc; + } + + /* speeding up handshake completion */ + + if (pkt->level == ssl_encryption_initial) { + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ssl_conn = c->ssl->connection; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + for (cl = frame->data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), + b->pos, b->last - b->pos)) + { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_quic_read_level:%d SSL_quic_write_level:%d", + (int) SSL_quic_read_level(ssl_conn), + (int) SSL_quic_write_level(ssl_conn)); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + + return NGX_OK; + } + + if (SSL_in_init(ssl_conn)) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handshake completed successfully"); + + c->ssl->handshaked = 1; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + /* 12.4 Frames and frame types, figure 8 */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (ngx_quic_send_new_token(c) != NGX_OK) { + return NGX_ERROR; + } + + /* + * Generating next keys before a key update is received. + * See quic-tls 9.4 Header Protection Timing Side-Channels. + */ + + if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { + return NGX_ERROR; + } + + /* + * 4.10.2 An endpoint MUST discard its handshake keys + * when the TLS handshake is confirmed + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); + + if (ngx_quic_issue_server_ids(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + u_char *p; + size_t clen; + ssize_t len; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + return NGX_ERROR; + } + + c->ssl->no_wait_shutdown = 1; + + ssl_conn = c->ssl->connection; + + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_method() failed"); + return NGX_ERROR; + } + +#ifdef SSL_READ_EARLY_DATA_SUCCESS + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + +#if BORINGSSL_API_VERSION >= 13 + SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); +#endif + + if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, + qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); + + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + if (len < 0) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); +#endif + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + +#if NGX_OPENSSL_QUIC_ZRTT_CTX + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h new file mode 100644 index 000000000..68656b85c --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_ + + +#include +#include + +ngx_int_t ngx_quic_init_connection(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_crypto_input(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + +#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ -- cgit v1.2.3 From 792117312d4fafba5af68b5c2fff51c58dcd3bc0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 13 Apr 2021 11:49:52 +0300 Subject: QUIC: ngx_quic_frames_stream_t made opaque. --- src/event/quic/ngx_event_quic.h | 10 ++-------- src/event/quic/ngx_event_quic_ack.c | 2 +- src/event/quic/ngx_event_quic_connection.h | 8 ++++++++ src/event/quic/ngx_event_quic_streams.c | 18 ++++++++++++------ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 0066c96e6..66395dbcf 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -70,13 +70,7 @@ typedef struct { } ngx_quic_conf_t; -typedef struct { - uint64_t sent; - uint64_t received; - ngx_queue_t frames; /* reorder queue */ - size_t total; /* size of buffered data */ -} ngx_quic_frames_stream_t; - +typedef struct ngx_quic_frames_stream_s ngx_quic_frames_stream_t; struct ngx_quic_stream_s { ngx_rbtree_node_t node; @@ -86,7 +80,7 @@ struct ngx_quic_stream_s { uint64_t acked; uint64_t send_max_data; ngx_buf_t *b; - ngx_quic_frames_stream_t fs; + ngx_quic_frames_stream_t *fs; ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 8f78ac51e..fc72ae13d 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -498,7 +498,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) } b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + n = sn->fs->received + (b->pos - b->start) + (b->end - b->last); if (f->u.max_stream_data.limit < n) { f->u.max_stream_data.limit = n; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index eb8507050..992397a66 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -98,6 +98,14 @@ typedef struct { } ngx_quic_congestion_t; +struct ngx_quic_frames_stream_s { + uint64_t sent; + uint64_t received; + ngx_queue_t frames; /* reorder queue */ + size_t total; /* size of buffered data */ +}; + + /* * 12.3. Packet Numbers * diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 90d6bcea3..1174ea11e 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -367,7 +367,13 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) return NULL; } - ngx_queue_init(&sn->fs.frames); + sn->fs = ngx_pcalloc(pool, sizeof(ngx_quic_frames_stream_t)); + if (sn->fs == NULL) { + ngx_destroy_pool(pool); + return NULL; + } + + ngx_queue_init(&sn->fs->frames); log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { @@ -503,7 +509,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->fs.received + (b->pos - b->start) + frame->u.max_stream_data.limit = qs->fs->received + (b->pos - b->start) + (b->end - b->last); ngx_quic_queue_frame(qc, frame); @@ -706,7 +712,7 @@ ngx_quic_stream_cleanup_handler(void *data) "quic stream id:0x%xL cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); - ngx_quic_free_frames(pc, &qs->fs.frames); + ngx_quic_free_frames(pc, &qs->fs->frames); if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ @@ -834,7 +840,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } sc = sn->c; - fs = &sn->fs; + fs = sn->fs; b = sn->b; window = b->end - b->last; @@ -855,7 +861,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - fs = &sn->fs; + fs = sn->fs; b = sn->b; window = (b->pos - b->start) + (b->end - b->last); @@ -1019,7 +1025,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, } else { b = sn->b; - n = sn->fs.received + (b->pos - b->start) + (b->end - b->last); + n = sn->fs->received + (b->pos - b->start) + (b->end - b->last); } frame = ngx_quic_alloc_frame(c); -- cgit v1.2.3 From 2f5bcafdde60c722fe5cd28965613abafb451acb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 13 Apr 2021 12:38:34 +0300 Subject: QUIC: normalize header inclusion. Stop including QUIC headers with no user-serviceable parts inside. This allows to provide a much cleaner QUIC interface. To cope with that, ngx_quic_derive_key() is now explicitly exported for v3 and quic modules. Additionally, this completely hides the ngx_quic_keys_t internal type. --- src/event/quic/ngx_event_quic.h | 5 ++--- src/event/quic/ngx_event_quic_connection.h | 6 +++--- src/event/quic/ngx_event_quic_protection.c | 3 +-- src/event/quic/ngx_event_quic_protection.h | 2 -- src/event/quic/ngx_event_quic_streams.c | 2 -- src/event/quic/ngx_event_quic_tokens.c | 2 +- src/event/quic/ngx_event_quic_transport.c | 2 +- src/http/modules/ngx_http_quic_module.c | 2 -- src/stream/ngx_stream_quic_module.c | 2 -- 9 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 66395dbcf..8a442b4b3 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -85,9 +85,6 @@ struct ngx_quic_stream_s { }; -typedef struct ngx_quic_keys_s ngx_quic_keys_t; - - void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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, @@ -98,5 +95,7 @@ ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); uint32_t ngx_quic_version(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, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); #endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 992397a66..03495a684 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -11,12 +11,12 @@ #include #include -#include -#include - typedef struct ngx_quic_connection_s ngx_quic_connection_t; typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; +typedef struct ngx_quic_keys_s ngx_quic_keys_t; +#include +#include #include #include #include diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 12dd233b8..d1801d05f 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -7,8 +7,7 @@ #include #include #include -#include -#include +#include #define NGX_QUIC_IV_LEN 12 diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index a351c9b1b..7a1604c6c 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -29,8 +29,6 @@ void ngx_quic_keys_discard(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level); void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); -ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, - ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 1174ea11e..716550b3d 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include #define NGX_QUIC_STREAM_GONE (void *) -1 diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index cbfc356d7..b896611e3 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #define NGX_QUIC_MAX_TOKEN_SIZE 64 diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 27dbf92af..2c952177c 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 5282bf4bb..b7661b42c 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -9,8 +9,6 @@ #include #include -#include - static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 6a67680b2..6567b16cf 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -9,8 +9,6 @@ #include #include -#include - static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); -- cgit v1.2.3 From 17eebfea99bb554741a5732ed3a4472399591c5a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 15 Apr 2021 12:17:19 +0300 Subject: QUIC: avoid sending extra frames in case of error. --- src/event/quic/ngx_event_quic_streams.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 716550b3d..6739d3d93 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -718,6 +718,10 @@ ngx_quic_stream_cleanup_handler(void *data) return; } + if (qc->error) { + goto done; + } + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { -- cgit v1.2.3 From db4c8fe45f8c609a85b52547891d6ba992c30b4e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 19 Apr 2021 11:36:41 +0300 Subject: QUIC: fixed parsing of unknown frame types. The ngx_quic_frame_allowed() function only expects known frame types. --- src/event/quic/ngx_event_quic_transport.c | 7 +++++++ src/event/quic/ngx_event_quic_transport.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 2c952177c..ad4758c60 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -742,6 +742,13 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, return NGX_ERROR; } + if (varint > NGX_QUIC_FT_LAST) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xL", varint); + return NGX_ERROR; + } + f->type = varint; if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 2cda8088f..9fb621721 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -83,6 +83,8 @@ #define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D #define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E +#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE + /* 22.4. QUIC Transport Error Codes Registry */ /* Keep in sync with ngx_quic_errors[] */ #define NGX_QUIC_ERR_NO_ERROR 0x00 -- cgit v1.2.3 From f184bc0a0af74ec160399451a655eac9fb71c490 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 19 Apr 2021 09:46:37 +0300 Subject: QUIC: added missing checks for limits in stream frames parsing. --- src/event/quic/ngx_event_quic_transport.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index ad4758c60..0d84546eb 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1003,6 +1003,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } + if (f->u.streams_blocked.limit > 0x1000000000000000) { + goto error; + } + f->u.streams_blocked.bidi = (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; break; @@ -1015,6 +1019,10 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } + if (f->u.max_streams.limit > 0x1000000000000000) { + goto error; + } + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; break; -- cgit v1.2.3 From 256db862fd54e51e2334187da383943c81a9f406 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 16 Apr 2021 23:03:59 +0300 Subject: QUIC: fixed permitted packet types for PATH_RESPONSE. PATH_RESPONSE was explicitly forbidden in 0-RTT since at least draft-22, but the Frame Types table was not updated until recently while in IESG evaluation. --- src/event/quic/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 0d84546eb..2524223d6 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1137,7 +1137,7 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) /* NEW_CONNECTION_ID */ 0x3, /* RETIRE_CONNECTION_ID */ 0x3, /* PATH_CHALLENGE */ 0x3, - /* PATH_RESPONSE */ 0x3, + /* PATH_RESPONSE */ 0x1, /* CONNECTION_CLOSE */ 0xF, /* CONNECTION_CLOSE2 */ 0x3, /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ -- cgit v1.2.3 From e9a0123e67dea3a2b15deb5fb3c47ac6931ddf37 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 19 Apr 2021 17:21:07 +0300 Subject: QUIC: renamed stream field from c to connection. --- src/event/quic/ngx_event_quic.h | 2 +- src/event/quic/ngx_event_quic_ack.c | 2 +- src/event/quic/ngx_event_quic_streams.c | 81 +++++++++++++++++---------------- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 8a442b4b3..4492213f9 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -75,7 +75,7 @@ typedef struct ngx_quic_frames_stream_s ngx_quic_frames_stream_t; struct ngx_quic_stream_s { ngx_rbtree_node_t node; ngx_connection_t *parent; - ngx_connection_t *c; + ngx_connection_t *connection; uint64_t id; uint64_t acked; uint64_t send_max_data; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index fc72ae13d..4ae7e6ef5 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -517,7 +517,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) case NGX_QUIC_FT_STREAM7: sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (sn && sn->c->write->error) { + if (sn && sn->connection->write->error) { /* RESET_STREAM was sent */ ngx_quic_free_frame(c, f); break; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 6739d3d93..5469db7f3 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -89,7 +89,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) return NULL; } - return sn->c; + return sn->connection; } @@ -172,11 +172,11 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { qs = (ngx_quic_stream_t *) node; - rev = qs->c->read; + rev = qs->connection->read; rev->error = 1; rev->ready = 1; - wev = qs->c->write; + wev = qs->connection->write; wev->error = 1; wev->ready = 1; @@ -319,7 +319,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) return NULL; } - sn->c->listening->handler(sn->c); + sn->connection->listening->handler(sn->connection); if (qc->shutdown) { return NGX_QUIC_STREAM_GONE; @@ -335,6 +335,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) { ngx_log_t *log; ngx_pool_t *pool; + ngx_connection_t *sc; ngx_quic_stream_t *sn; ngx_pool_cleanup_t *cln; ngx_quic_connection_t *qc; @@ -382,36 +383,38 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) *log = *c->log; pool->log = log; - sn->c = ngx_get_connection(-1, log); - if (sn->c == NULL) { + sc = ngx_get_connection(-1, log); + if (sc == NULL) { ngx_destroy_pool(pool); return NULL; } - sn->c->quic = sn; - sn->c->type = SOCK_STREAM; - sn->c->pool = pool; - sn->c->ssl = c->ssl; - sn->c->sockaddr = c->sockaddr; - sn->c->listening = c->listening; - sn->c->addr_text = c->addr_text; - sn->c->local_sockaddr = c->local_sockaddr; - sn->c->local_socklen = c->local_socklen; - sn->c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sn->connection = sc; + + sc->quic = sn; + sc->type = SOCK_STREAM; + sc->pool = pool; + sc->ssl = c->ssl; + sc->sockaddr = c->sockaddr; + sc->listening = c->listening; + sc->addr_text = c->addr_text; + sc->local_sockaddr = c->local_sockaddr; + sc->local_socklen = c->local_socklen; + sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); - sn->c->recv = ngx_quic_stream_recv; - sn->c->send = ngx_quic_stream_send; - sn->c->send_chain = ngx_quic_stream_send_chain; + sc->recv = ngx_quic_stream_recv; + sc->send = ngx_quic_stream_send; + sc->send_chain = ngx_quic_stream_send_chain; - sn->c->read->log = log; - sn->c->write->log = log; + sc->read->log = log; + sc->write->log = log; - log->connection = sn->c->number; + log->connection = sc->number; if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) { - sn->c->write->ready = 1; + sc->write->ready = 1; } if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { @@ -429,13 +432,13 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { - ngx_close_connection(sn->c); + ngx_close_connection(sc); ngx_destroy_pool(pool); return NULL; } cln->handler = ngx_quic_stream_cleanup_handler; - cln->data = sn->c; + cln->data = sc; ngx_rbtree_insert(&qc->streams.tree, &sn->node); @@ -841,7 +844,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - sc = sn->c; + sc = sn->connection; fs = sn->fs; b = sn->b; window = b->end - b->last; @@ -921,7 +924,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) cl->buf->last - cl->buf->pos); } - rev = sn->c->read; + rev = sn->connection->read; rev->ready = 1; if (f->fin) { @@ -965,7 +968,7 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, node = ngx_rbtree_next(tree, node)) { qs = (ngx_quic_stream_t *) node; - wev = qs->c->write; + wev = qs->connection->write; if (wev->active) { wev->ready = 1; @@ -1023,7 +1026,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, b = sn->b; n = b->end - b->last; - sn->c->listening->handler(sn->c); + sn->connection->listening->handler(sn->connection); } else { b = sn->b; @@ -1081,7 +1084,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, sn->send_max_data = f->limit; } - sn->c->listening->handler(sn->c); + sn->connection->listening->handler(sn->connection); return NGX_OK; } @@ -1090,10 +1093,10 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, return NGX_OK; } - sent = sn->c->sent; + sent = sn->connection->sent; if (sent >= sn->send_max_data) { - wev = sn->c->write; + wev = sn->connection->write; if (wev->active) { wev->ready = 1; @@ -1138,7 +1141,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } - sc = sn->c; + sc = sn->connection; rev = sc->read; rev->error = 1; @@ -1149,7 +1152,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } - rev = sn->c->read; + rev = sn->connection->read; rev->error = 1; rev->ready = 1; @@ -1192,7 +1195,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_OK; } - sc = sn->c; + sc = sn->connection; wev = sc->write; wev->error = 1; @@ -1203,7 +1206,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_OK; } - wev = sn->c->write; + wev = sn->connection->write; wev->error = 1; wev->ready = 1; @@ -1259,8 +1262,8 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) return; } - wev = sn->c->write; - sent = sn->c->sent; + wev = sn->connection->write; + sent = sn->connection->sent; unacked = sent - sn->acked; if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { @@ -1270,7 +1273,7 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) sn->acked += f->u.stream.length; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->c->log, 0, + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->connection->log, 0, "quic stream ack len:%uL acked:%uL unacked:%uL", f->u.stream.length, sn->acked, sent - sn->acked); } -- cgit v1.2.3 From 013880bbdaa220aa245a327f16b5bf5a608bfd98 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 19 Apr 2021 17:25:56 +0300 Subject: QUIC: renamed stream variables from sn to qs. Currently both names are used which is confusing. Historically these were different objects, but now it's the same one. The name qs (quic stream) makes more sense than sn (stream node). --- src/event/quic/ngx_event_quic_ack.c | 14 +-- src/event/quic/ngx_event_quic_streams.c | 184 ++++++++++++++++---------------- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 4ae7e6ef5..fe9aee68d 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -436,7 +436,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_buf_t *b; ngx_queue_t *q; ngx_quic_frame_t *f, *start; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -490,15 +490,15 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) break; case NGX_QUIC_FT_MAX_STREAM_DATA: - sn = ngx_quic_find_stream(&qc->streams.tree, + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.max_stream_data.id); - if (sn == NULL) { + if (qs == NULL) { ngx_quic_free_frame(c, f); break; } - b = sn->b; - n = sn->fs->received + (b->pos - b->start) + (b->end - b->last); + b = qs->b; + n = qs->fs->received + (b->pos - b->start) + (b->end - b->last); if (f->u.max_stream_data.limit < n) { f->u.max_stream_data.limit = n; @@ -515,9 +515,9 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) case NGX_QUIC_FT_STREAM5: case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (sn && sn->connection->write->error) { + if (qs && qs->connection->write->error) { /* RESET_STREAM was sent */ ngx_quic_free_frame(c, f); break; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 5469db7f3..e8dd06657 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -32,7 +32,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { size_t rcvbuf_size; uint64_t id; - ngx_quic_stream_t *qs, *sn; + ngx_quic_stream_t *qs, *nqs; ngx_quic_connection_t *qc; qs = c->quic; @@ -84,12 +84,12 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) rcvbuf_size = 0; } - sn = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); - if (sn == NULL) { + nqs = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); + if (nqs == NULL) { return NULL; } - return sn->connection; + return nqs->connection; } @@ -237,7 +237,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) { size_t n; uint64_t min_id; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -314,12 +314,12 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) for ( /* void */ ; min_id < id; min_id += 0x04) { - sn = ngx_quic_create_stream(c, min_id, n); - if (sn == NULL) { + qs = ngx_quic_create_stream(c, min_id, n); + if (qs == NULL) { return NULL; } - sn->connection->listening->handler(sn->connection); + qs->connection->listening->handler(qs->connection); if (qc->shutdown) { return NGX_QUIC_STREAM_GONE; @@ -336,7 +336,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) ngx_log_t *log; ngx_pool_t *pool; ngx_connection_t *sc; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_pool_cleanup_t *cln; ngx_quic_connection_t *qc; @@ -350,29 +350,29 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) return NULL; } - sn = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); - if (sn == NULL) { + qs = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); + if (qs == NULL) { ngx_destroy_pool(pool); return NULL; } - sn->node.key = id; - sn->parent = c; - sn->id = id; + qs->node.key = id; + qs->parent = c; + qs->id = id; - sn->b = ngx_create_temp_buf(pool, rcvbuf_size); - if (sn->b == NULL) { + qs->b = ngx_create_temp_buf(pool, rcvbuf_size); + if (qs->b == NULL) { ngx_destroy_pool(pool); return NULL; } - sn->fs = ngx_pcalloc(pool, sizeof(ngx_quic_frames_stream_t)); - if (sn->fs == NULL) { + qs->fs = ngx_pcalloc(pool, sizeof(ngx_quic_frames_stream_t)); + if (qs->fs == NULL) { ngx_destroy_pool(pool); return NULL; } - ngx_queue_init(&sn->fs->frames); + ngx_queue_init(&qs->fs->frames); log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { @@ -389,9 +389,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) return NULL; } - sn->connection = sc; + qs->connection = sc; - sc->quic = sn; + sc->quic = qs; sc->type = SOCK_STREAM; sc->pool = pool; sc->ssl = c->ssl; @@ -419,14 +419,14 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_uni; + qs->send_max_data = qc->ctp.initial_max_stream_data_uni; } } else { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; } else { - sn->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; } } @@ -440,9 +440,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) cln->handler = ngx_quic_stream_cleanup_handler; cln->data = sc; - ngx_rbtree_insert(&qc->streams.tree, &sn->node); + ngx_rbtree_insert(&qc->streams.tree, &qs->node); - return sn; + return qs; } @@ -813,7 +813,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_buf_t *b; ngx_pool_t *pool; ngx_connection_t *sc; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; ngx_quic_frames_stream_t *fs; @@ -831,22 +831,22 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, /* no overflow since both values are 62-bit */ last = f->offset + f->length; - sn = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->stream_id); + if (qs == NULL) { + qs = ngx_quic_create_client_stream(c, f->stream_id); - if (sn == NULL) { + if (qs == NULL) { return NGX_ERROR; } - if (sn == NGX_QUIC_STREAM_GONE) { + if (qs == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - sc = sn->connection; - fs = sn->fs; - b = sn->b; + sc = qs->connection; + fs = qs->fs; + b = qs->b; window = b->end - b->last; if (last > window) { @@ -855,7 +855,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn) + qs) != NGX_OK) { goto cleanup; @@ -866,8 +866,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - fs = sn->fs; - b = sn->b; + fs = qs->fs; + b = qs->b; window = (b->pos - b->start) + (b->end - b->last); if (last > fs->received && last - fs->received > window) { @@ -876,7 +876,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - sn); + qs); cleanup: @@ -896,17 +896,17 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) ngx_buf_t *b; ngx_event_t *rev; ngx_chain_t *cl; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; qc = ngx_quic_get_connection(c); - sn = data; + qs = data; f = &frame->u.stream; id = f->stream_id; - b = sn->b; + b = qs->b; if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -924,7 +924,7 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) cl->buf->last - cl->buf->pos); } - rev = sn->connection->read; + rev = qs->connection->read; rev->ready = 1; if (f->fin) { @@ -998,7 +998,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, size_t n; ngx_buf_t *b; ngx_quic_frame_t *frame; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1010,27 +1010,27 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, return NGX_ERROR; } - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->id); - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); + if (qs == NULL) { + qs = ngx_quic_create_client_stream(c, f->id); - if (sn == NULL) { + if (qs == NULL) { return NGX_ERROR; } - if (sn == NGX_QUIC_STREAM_GONE) { + if (qs == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - b = sn->b; + b = qs->b; n = b->end - b->last; - sn->connection->listening->handler(sn->connection); + qs->connection->listening->handler(qs->connection); } else { - b = sn->b; - n = sn->fs->received + (b->pos - b->start) + (b->end - b->last); + b = qs->b; + n = qs->fs->received + (b->pos - b->start) + (b->end - b->last); } frame = ngx_quic_alloc_frame(c); @@ -1055,7 +1055,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, { uint64_t sent; ngx_event_t *wev; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1067,36 +1067,36 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, return NGX_ERROR; } - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->id); - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); + if (qs == NULL) { + qs = ngx_quic_create_client_stream(c, f->id); - if (sn == NULL) { + if (qs == NULL) { return NGX_ERROR; } - if (sn == NGX_QUIC_STREAM_GONE) { + if (qs == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - if (f->limit > sn->send_max_data) { - sn->send_max_data = f->limit; + if (f->limit > qs->send_max_data) { + qs->send_max_data = f->limit; } - sn->connection->listening->handler(sn->connection); + qs->connection->listening->handler(qs->connection); return NGX_OK; } - if (f->limit <= sn->send_max_data) { + if (f->limit <= qs->send_max_data) { return NGX_OK; } - sent = sn->connection->sent; + sent = qs->connection->sent; - if (sent >= sn->send_max_data) { - wev = sn->connection->write; + if (sent >= qs->send_max_data) { + wev = qs->connection->write; if (wev->active) { wev->ready = 1; @@ -1104,7 +1104,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, } } - sn->send_max_data = f->limit; + qs->send_max_data = f->limit; return NGX_OK; } @@ -1116,7 +1116,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, { ngx_event_t *rev; ngx_connection_t *sc; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1128,20 +1128,20 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->id); - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); + if (qs == NULL) { + qs = ngx_quic_create_client_stream(c, f->id); - if (sn == NULL) { + if (qs == NULL) { return NGX_ERROR; } - if (sn == NGX_QUIC_STREAM_GONE) { + if (qs == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - sc = sn->connection; + sc = qs->connection; rev = sc->read; rev->error = 1; @@ -1152,7 +1152,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } - rev = sn->connection->read; + rev = qs->connection->read; rev->error = 1; rev->ready = 1; @@ -1170,7 +1170,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, { ngx_event_t *wev; ngx_connection_t *sc; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1182,20 +1182,20 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_ERROR; } - sn = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_find_stream(&qc->streams.tree, f->id); - if (sn == NULL) { - sn = ngx_quic_create_client_stream(c, f->id); + if (qs == NULL) { + qs = ngx_quic_create_client_stream(c, f->id); - if (sn == NULL) { + if (qs == NULL) { return NGX_ERROR; } - if (sn == NGX_QUIC_STREAM_GONE) { + if (qs == NGX_QUIC_STREAM_GONE) { return NGX_OK; } - sc = sn->connection; + sc = qs->connection; wev = sc->write; wev->error = 1; @@ -1206,7 +1206,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_OK; } - wev = sn->connection->write; + wev = qs->connection->write; wev->error = 1; wev->ready = 1; @@ -1252,28 +1252,28 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { uint64_t sent, unacked; ngx_event_t *wev; - ngx_quic_stream_t *sn; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - sn = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (sn == NULL) { + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (qs == NULL) { return; } - wev = sn->connection->write; - sent = sn->connection->sent; - unacked = sent - sn->acked; + wev = qs->connection->write; + sent = qs->connection->sent; + unacked = sent - qs->acked; if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { wev->ready = 1; ngx_post_event(wev, &ngx_posted_events); } - sn->acked += f->u.stream.length; + qs->acked += f->u.stream.length; - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, sn->connection->log, 0, + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0, "quic stream ack len:%uL acked:%uL unacked:%uL", - f->u.stream.length, sn->acked, sent - sn->acked); + f->u.stream.length, qs->acked, sent - qs->acked); } -- cgit v1.2.3 From a8acca865bc63efd0ae99284af37ed6bb246923b Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 22 Apr 2021 13:49:18 +0300 Subject: HTTP/3: adjusted control stream parsing. 7.2.1: If a DATA frame is received on a control stream, the recipient MUST respond with a connection error of type H3_FRAME_UNEXPECTED; 7.2.2: If a HEADERS frame is received on a control stream, the recipient MUST respond with a connection error (Section 8) of type H3_FRAME_UNEXPECTED. --- src/http/v3/ngx_http_v3_parse.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index b7cf242ba..18255a677 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1069,6 +1069,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) st->state = sw_max_push_id; break; + case NGX_HTTP_V3_FRAME_DATA: + case NGX_HTTP_V3_FRAME_HEADERS: + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + default: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse skip unknown frame"); -- cgit v1.2.3 From c4f5b50c47a867dfb72f80a5c3dd0e87508e0502 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 29 Apr 2021 15:35:02 +0300 Subject: QUIC: connection migration. The patch adds proper transitions between multiple networking addresses that can be used by a single quic connection. New networking paths are validated using PATH_CHALLENGE/PATH_RESPONSE frames. --- README | 3 +- auto/modules | 6 +- src/event/quic/ngx_event_quic.c | 118 +++-- src/event/quic/ngx_event_quic_ack.c | 2 +- src/event/quic/ngx_event_quic_ack.h | 3 +- src/event/quic/ngx_event_quic_connection.h | 72 ++- src/event/quic/ngx_event_quic_connid.c | 467 ++++++++++--------- src/event/quic/ngx_event_quic_connid.h | 13 +- src/event/quic/ngx_event_quic_migration.c | 703 ++++++++++++++++++++++++++++- src/event/quic/ngx_event_quic_migration.h | 24 + src/event/quic/ngx_event_quic_output.c | 203 +++++++-- src/event/quic/ngx_event_quic_output.h | 5 +- src/event/quic/ngx_event_quic_socket.c | 355 +++++++++++++++ src/event/quic/ngx_event_quic_socket.h | 32 ++ src/event/quic/ngx_event_quic_ssl.c | 16 +- src/event/quic/ngx_event_quic_tokens.c | 26 +- src/event/quic/ngx_event_quic_tokens.h | 5 +- 17 files changed, 1680 insertions(+), 373 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_socket.c create mode 100644 src/event/quic/ngx_event_quic_socket.h diff --git a/README b/README index e82bb9a82..de9df4a44 100644 --- a/README +++ b/README @@ -51,14 +51,13 @@ Experimental QUIC support for nginx subsequently reference them from header blocks + Version Negotiation packet is sent to client with unknown version + Lost packets are detected and retransmitted properly + + Clients may migrate to new address Not (yet) supported features: - Explicit Congestion Notification (ECN) as specified in quic-recovery [5] - A connection with the spin bit succeeds and the bit is spinning - Structured Logging - - NAT Rebinding - - Address Mobility - HTTP/3 trailers Since the code is experimental and still under development, diff --git a/auto/modules b/auto/modules index 6d5a61e22..95b722d8c 100644 --- a/auto/modules +++ b/auto/modules @@ -1350,7 +1350,8 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_ssl.h \ src/event/quic/ngx_event_quic_tokens.h \ src/event/quic/ngx_event_quic_ack.h \ - src/event/quic/ngx_event_quic_output.h" + src/event/quic/ngx_event_quic_output.h \ + src/event/quic/ngx_event_quic_socket.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ @@ -1361,7 +1362,8 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_ssl.c \ src/event/quic/ngx_event_quic_tokens.c \ src/event/quic/ngx_event_quic_ack.c \ - src/event/quic/ngx_event_quic_output.c" + src/event/quic/ngx_event_quic_output.c \ + src/event/quic/ngx_event_quic_socket.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 2dd839fc3..4088960b7 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -87,7 +87,6 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) 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" : ""); - p = ngx_slprintf(p, last, "%s", qc->validated? " valid" : ""); } else { p = ngx_slprintf(p, last, " early"); @@ -127,12 +126,16 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) { + ngx_str_t scid; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - if (qc->scid.len != ctp->initial_scid.len - || ngx_memcmp(qc->scid.data, ctp->initial_scid.data, qc->scid.len) != 0) + scid.data = qc->socket->cid->id; + scid.len = qc->socket->cid->len; + + if (scid.len != ctp->initial_scid.len + || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic client initial_source_connection_id mismatch"); @@ -277,8 +280,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, * qc->latest_rtt = 0 */ - qc->received = pkt->raw->last - pkt->raw->start; - qc->pto.log = c->log; qc->pto.data = c; qc->pto.handler = ngx_quic_pto_handler; @@ -289,19 +290,14 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->push.handler = ngx_quic_push_handler; qc->push.cancelable = 1; + qc->path_validation.log = c->log; + qc->path_validation.data = c; + qc->path_validation.handler = ngx_quic_path_validation_handler; + qc->path_validation.cancelable = 1; + qc->conf = conf; qc->tp = conf->tp; - if (qc->tp.disable_active_migration) { - qc->sockaddr = ngx_palloc(c->pool, c->socklen); - if (qc->sockaddr == NULL) { - return NULL; - } - - ngx_memcpy(qc->sockaddr, c->sockaddr, c->socklen); - qc->socklen = c->socklen; - } - ctp = &qc->ctp; /* defaults to be used before actual client parameters are received */ @@ -338,10 +334,13 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->validated = pkt->validated; - if (ngx_quic_setup_connection_ids(c, qc, pkt) != NGX_OK) { + if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { return NULL; } + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection created"); + return qc; } @@ -425,20 +424,8 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - if (qc->tp.disable_active_migration) { - if (c->socklen != qc->socklen - || ngx_memcmp(c->sockaddr, qc->sockaddr, c->socklen) != 0) - { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic dropping packet from new address"); - return; - } - } - b = c->udp->dgram->buffer; - qc->received += (b->last - b->pos); - rc = ngx_quic_input(c, b, NULL); if (rc == NGX_ERROR) { @@ -506,9 +493,7 @@ static ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) { ngx_uint_t i; - ngx_queue_t *q; ngx_quic_send_ctx_t *ctx; - ngx_quic_server_id_t *sid; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -601,23 +586,20 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) ngx_del_timer(&qc->pto); } - if (qc->push.posted) { - ngx_delete_posted_event(&qc->push); + if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); } - while (!ngx_queue_empty(&qc->server_ids)) { - q = ngx_queue_head(&qc->server_ids); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); - - ngx_queue_remove(q); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); } if (qc->close.timer_set) { return NGX_AGAIN; } + ngx_quic_close_sockets(c); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic part of connection is terminated"); @@ -801,6 +783,11 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } + rc = ngx_quic_check_migration(c, pkt); + if (rc != NGX_OK) { + return rc; + } + if (pkt->level != ssl_encryption_application) { if (pkt->version != qc->version) { @@ -946,6 +933,10 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->decrypted = 1; + if (ngx_quic_update_paths(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + if (c->ssl == NULL) { if (ngx_quic_init_connection(c) != NGX_OK) { return NGX_ERROR; @@ -959,8 +950,8 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) */ ngx_quic_discard_ctx(c, ssl_encryption_initial); - if (qc->validated == 0) { - qc->validated = 1; + if (qc->socket->path->state != NGX_QUIC_PATH_VALIDATED) { + qc->socket->path->state = NGX_QUIC_PATH_VALIDATED; ngx_post_event(&qc->push, &ngx_posted_events); } } @@ -1015,6 +1006,7 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) { ngx_queue_t *q; ngx_quic_frame_t *f; + ngx_quic_socket_t *qsock; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -1049,7 +1041,11 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) } if (level == ssl_encryption_initial) { - ngx_quic_clear_temp_server_ids(c); + /* close temporary listener with odcid */ + qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN); + if (qsock) { + ngx_quic_close_socket(c, qsock); + } } ctx->send_ack = 0; @@ -1088,9 +1084,10 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) u_char *end, *p; ssize_t len; ngx_buf_t buf; - ngx_uint_t do_close; + ngx_uint_t do_close, nonprobing; ngx_chain_t chain; ngx_quic_frame_t frame; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1099,11 +1096,13 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) end = p + pkt->payload.len; do_close = 0; + nonprobing = 0; while (p < end) { c->log->action = "parsing frames"; + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); ngx_memzero(&buf, sizeof(ngx_buf_t)); buf.temporary = 1; @@ -1124,6 +1123,19 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) p += len; + switch (frame.type) { + /* probing frames */ + case NGX_QUIC_FT_PADDING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + break; + + /* non-probing frames */ + default: + nonprobing = 1; + break; + } + switch (frame.type) { case NGX_QUIC_FT_ACK: @@ -1313,6 +1325,26 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_close_connection(c, NGX_OK); } + qsock = ngx_quic_get_socket(c); + + if (qsock != qc->socket) { + + if (qsock->path != qc->socket->path && nonprobing) { + /* + * An endpoint can migrate a connection to a new local + * address by sending packets containing non-probing frames + * from that address. + */ + if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + } + /* + * else: packet arrived via non-default socket; + * no reason to change active path + */ + } + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index fe9aee68d..84e83917f 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -722,7 +722,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ctx = &qc->send_ctx[i]; + ctx = &qc->send_ctx[i]; if (ngx_queue_empty(&ctx->sent)) { continue; diff --git a/src/event/quic/ngx_event_quic_ack.h b/src/event/quic/ngx_event_quic_ack.h index 8e53446d7..56920c2a5 100644 --- a/src/event/quic/ngx_event_quic_ack.h +++ b/src/event/quic/ngx_event_quic_ack.h @@ -17,8 +17,7 @@ ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *frame); -void ngx_quic_resend_frames(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx); +void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); void ngx_quic_set_lost_timer(ngx_connection_t *c); void ngx_quic_pto_handler(ngx_event_t *ev); ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 03495a684..d5e32c66a 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -12,7 +12,11 @@ #include 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; +typedef struct ngx_quic_socket_s ngx_quic_socket_t; +typedef struct ngx_quic_path_s ngx_quic_path_t; typedef struct ngx_quic_keys_s ngx_quic_keys_t; #include @@ -25,6 +29,7 @@ typedef struct ngx_quic_keys_s ngx_quic_keys_t; #include #include #include +#include /* quic-recovery, section 6.2.2, kInitialRtt */ @@ -47,26 +52,57 @@ typedef struct ngx_quic_keys_s ngx_quic_keys_t; : &((qc)->send_ctx[2])) #define ngx_quic_get_connection(c) \ - (((c)->udp) ? (((ngx_quic_server_id_t *)((c)->udp))->quic) : NULL) + (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) +#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) -typedef struct { + +struct ngx_quic_client_id_s { ngx_queue_t queue; uint64_t seqnum; size_t len; u_char id[NGX_QUIC_CID_LEN_MAX]; u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; -} ngx_quic_client_id_t; + ngx_uint_t refcnt; +}; -typedef struct { - ngx_udp_connection_t udp; - ngx_quic_connection_t *quic; - ngx_queue_t queue; +struct ngx_quic_server_id_s { uint64_t seqnum; size_t len; u_char id[NGX_QUIC_CID_LEN_MAX]; -} ngx_quic_server_id_t; +}; + + +struct ngx_quic_path_s { + ngx_queue_t queue; + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_uint_t state; + ngx_msec_t expires; + ngx_uint_t tries; + off_t sent; + off_t received; + u_char challenge1[8]; + u_char challenge2[8]; + ngx_uint_t refcnt; + uint64_t seqnum; + time_t validated_at; + ngx_str_t addr_text; + u_char text[NGX_SOCKADDR_STRLEN]; +}; + + +struct ngx_quic_socket_s { + ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; + ngx_queue_t queue; + + ngx_quic_server_id_t sid; + + ngx_quic_path_t *path; + ngx_quic_client_id_t *cid; +}; typedef struct { @@ -138,22 +174,22 @@ struct ngx_quic_send_ctx_s { struct ngx_quic_connection_s { uint32_t version; - ngx_str_t scid; /* initial client ID */ - ngx_str_t dcid; /* server (our own) ID */ - ngx_str_t odcid; /* original server ID */ - - struct sockaddr *sockaddr; - socklen_t socklen; + ngx_quic_socket_t *socket; + ngx_quic_socket_t *backup; + ngx_queue_t sockets; + ngx_queue_t paths; ngx_queue_t client_ids; - ngx_queue_t server_ids; + ngx_queue_t free_sockets; + ngx_queue_t free_paths; ngx_queue_t free_client_ids; - ngx_queue_t free_server_ids; + + ngx_uint_t nsockets; ngx_uint_t nclient_ids; - ngx_uint_t nserver_ids; uint64_t max_retired_seqnum; uint64_t client_seqnum; uint64_t server_seqnum; + uint64_t path_seqnum; ngx_uint_t client_tp_done; ngx_quic_tp_t tp; @@ -170,6 +206,7 @@ struct ngx_quic_connection_s { ngx_event_t push; ngx_event_t pto; ngx_event_t close; + ngx_event_t path_validation; ngx_msec_t last_cc; ngx_msec_t latest_rtt; @@ -190,7 +227,6 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; - off_t received; ngx_uint_t error; enum ssl_encryption_level_t error_level; diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index faf5d9c83..a30e7ef35 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -9,103 +9,24 @@ #include #include - #define NGX_QUIC_MAX_SERVER_IDS 8 -static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); #if (NGX_QUIC_BPF) static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); #endif -static ngx_int_t ngx_quic_retire_connection_id(ngx_connection_t *c, +static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum); -static ngx_quic_server_id_t *ngx_quic_insert_server_id(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_str_t *id); + static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); -static ngx_quic_server_id_t *ngx_quic_alloc_server_id(ngx_connection_t *c, - ngx_quic_connection_t *qc); +static ngx_int_t ngx_quic_replace_retired_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *retired_cid); +static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, + ngx_quic_server_id_t *sid); ngx_int_t -ngx_quic_setup_connection_ids(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_quic_header_t *pkt) -{ - ngx_quic_server_id_t *sid, *osid; - ngx_quic_client_id_t *cid; - - /* - * qc->nclient_ids = 0 - * qc->nserver_ids = 0 - * qc->max_retired_seqnum = 0 - */ - - ngx_queue_init(&qc->client_ids); - ngx_queue_init(&qc->server_ids); - ngx_queue_init(&qc->free_client_ids); - ngx_queue_init(&qc->free_server_ids); - - qc->odcid.len = pkt->odcid.len; - qc->odcid.data = ngx_pstrdup(c->pool, &pkt->odcid); - if (qc->odcid.data == NULL) { - return NGX_ERROR; - } - - qc->tp.original_dcid = qc->odcid; - - qc->scid.len = pkt->scid.len; - qc->scid.data = ngx_pstrdup(c->pool, &pkt->scid); - if (qc->scid.data == NULL) { - return NGX_ERROR; - } - - qc->dcid.len = NGX_QUIC_SERVER_CID_LEN; - qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); - if (qc->dcid.data == NULL) { - return NGX_ERROR; - } - - if (ngx_quic_create_server_id(c, qc->dcid.data) != NGX_OK) { - return NGX_ERROR; - } - - qc->tp.initial_scid = qc->dcid; - - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NGX_ERROR; - } - - cid->seqnum = 0; - cid->len = pkt->scid.len; - ngx_memcpy(cid->id, pkt->scid.data, pkt->scid.len); - - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - qc->client_seqnum = 0; - - qc->server_seqnum = NGX_QUIC_UNSET_PN; - - osid = ngx_quic_insert_server_id(c, qc, &qc->odcid); - if (osid == NULL) { - return NGX_ERROR; - } - - qc->server_seqnum = 0; - - sid = ngx_quic_insert_server_id(c, qc, &qc->dcid); - if (sid == NULL) { - ngx_rbtree_delete(&c->listening->rbtree, &osid->udp.node); - return NGX_ERROR; - } - - c->udp = &sid->udp; - - return NGX_OK; -} - - -static ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) { if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { @@ -120,9 +41,6 @@ ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) } #endif - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic create server id %*xs", - (size_t) NGX_QUIC_SERVER_CID_LEN, id); return NGX_OK; } @@ -155,12 +73,11 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) #endif - - ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) { + ngx_str_t id; ngx_queue_t *q; ngx_quic_client_id_t *cid, *item; ngx_quic_connection_t *qc; @@ -177,7 +94,9 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, * done so for that sequence number. */ - if (ngx_quic_retire_connection_id(c, pkt->level, f->seqnum) != NGX_OK) { + if (ngx_quic_send_retire_connection_id(c, pkt->level, f->seqnum) + != NGX_OK) + { return NGX_ERROR; } @@ -220,25 +139,11 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, } else { - cid = ngx_quic_alloc_client_id(c, qc); - if (cid == NULL) { - return NGX_ERROR; - } - - cid->seqnum = f->seqnum; - cid->len = f->len; - ngx_memcpy(cid->id, f->cid, f->len); - - ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN); + id.data = f->cid; + id.len = f->len; - ngx_queue_insert_tail(&qc->client_ids, &cid->queue); - qc->nclient_ids++; - - /* always use latest available connection id */ - if (f->seqnum > qc->client_seqnum) { - qc->scid.len = cid->len; - qc->scid.data = cid->id; - qc->client_seqnum = f->seqnum; + if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) { + return NGX_ERROR; } } @@ -269,15 +174,20 @@ retire: /* this connection id must be retired */ - if (ngx_quic_retire_connection_id(c, pkt->level, cid->seqnum) + if (ngx_quic_send_retire_connection_id(c, pkt->level, cid->seqnum) != NGX_OK) { return NGX_ERROR; } - ngx_queue_remove(&cid->queue); - ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); - qc->nclient_ids--; + if (cid->refcnt) { + /* we are going to retire client id which is in use */ + if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_quic_unref_client_id(c, cid); } done: @@ -300,7 +210,7 @@ done: static ngx_int_t -ngx_quic_retire_connection_id(ngx_connection_t *c, +ngx_quic_send_retire_connection_id(ngx_connection_t *c, enum ssl_encryption_level_t level, uint64_t seqnum) { ngx_quic_frame_t *frame; @@ -319,214 +229,293 @@ ngx_quic_retire_connection_id(ngx_connection_t *c, ngx_quic_queue_frame(qc, frame); + /* we are no longer going to use this client id */ + return NGX_OK; } -ngx_int_t -ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, + uint64_t seqnum, u_char *token) +{ + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NULL; + } + + cid->seqnum = seqnum; + + cid->len = id->len; + ngx_memcpy(cid->id, id->data, id->len); + + if (token) { + ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN); + } + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + if (seqnum > qc->client_seqnum) { + qc->client_seqnum = seqnum; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic cid #%uL received id:%uz:%xV:%*xs", + cid->seqnum, id->len, id, + (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_next_client_id(ngx_connection_t *c) { ngx_queue_t *q; - ngx_quic_server_id_t *sid; + ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); q = ngx_queue_next(q)) { - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - if (sid->seqnum == f->sequence_number) { - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; - break; + if (cid->refcnt == 0) { + return cid; } } - return ngx_quic_issue_server_ids(c); + return NULL; } ngx_int_t -ngx_quic_issue_server_ids(ngx_connection_t *c) +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) { - ngx_str_t dcid; - ngx_uint_t n; - ngx_quic_frame_t *frame; - ngx_quic_server_id_t *sid; + ngx_quic_path_t *path; + ngx_quic_socket_t *qsock, **tmp; + ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; - u_char id[NGX_QUIC_SERVER_CID_LEN]; qc = ngx_quic_get_connection(c); - n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + qsock = ngx_quic_find_socket(c, f->sequence_number); + if (qsock == NULL) { + return NGX_OK; + } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic issue server ids has:%ui max:%ui", qc->nserver_ids, n); + if (qsock->sid.seqnum == qc->socket->sid.seqnum) { + tmp = &qc->socket; - while (qc->nserver_ids < n) { - if (ngx_quic_create_server_id(c, id) != NGX_OK) { - return NGX_ERROR; - } + } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) { + tmp = &qc->backup; - dcid.len = NGX_QUIC_SERVER_CID_LEN; - dcid.data = id; + } else { + tmp = NULL; + } - sid = ngx_quic_insert_server_id(c, qc, &dcid); - if (sid == NULL) { - return NGX_ERROR; - } + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } + if (tmp) { + /* replace socket in use (active or backup) */ - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; - frame->u.ncid.seqnum = sid->seqnum; - frame->u.ncid.retire = 0; - frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; - ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %s socket #%uL:%uL:%uL retired", + (*tmp) == qc->socket ? "active" : "backup", + (*tmp)->sid.seqnum, (*tmp)->cid->seqnum, + (*tmp)->path->seqnum); - if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, - frame->u.ncid.srt) - != NGX_OK) - { + qsock = ngx_quic_get_unconnected_socket(c); + if (qsock == NULL) { return NGX_ERROR; } - ngx_quic_queue_frame(qc, frame); + path = (*tmp)->path; + cid = (*tmp)->cid; + + ngx_quic_connect(c, qsock, path, cid); + + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %s socket is now #%uL:%uL:%uL (%s)", + (*tmp) == qc->socket ? "active" : "backup", + qsock->sid.seqnum, qsock->cid->seqnum, + qsock->path->seqnum, + ngx_quic_path_state_str(qsock->path)); + + ngx_quic_close_socket(c, *tmp); /* no longer used */ + + *tmp = qsock; } return NGX_OK; } -void -ngx_quic_clear_temp_server_ids(ngx_connection_t *c) +ngx_int_t +ngx_quic_create_sockets(ngx_connection_t *c) { - ngx_queue_t *q, *next; - ngx_quic_server_id_t *sid; + ngx_uint_t n; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic clear temp server ids"); + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); - for (q = ngx_queue_head(&qc->server_ids); - q != ngx_queue_sentinel(&qc->server_ids); - q = next) - { - next = ngx_queue_next(q); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create sockets has:%ui max:%ui", qc->nsockets, n); - if (sid->seqnum != NGX_QUIC_UNSET_PN) { - continue; + while (qc->nsockets < n) { + + qsock = ngx_quic_alloc_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; } - ngx_queue_remove(q); - ngx_queue_insert_tail(&qc->free_server_ids, &sid->queue); - ngx_rbtree_delete(&c->listening->rbtree, &sid->udp.node); - qc->nserver_ids--; + if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) { + return NGX_ERROR; + } } + + return NGX_OK; } -static ngx_quic_server_id_t * -ngx_quic_insert_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_str_t *id) +static ngx_int_t +ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) { - ngx_str_t dcid; - ngx_quic_server_id_t *sid; - - sid = ngx_quic_alloc_server_id(c, qc); - if (sid == NULL) { - return NULL; - } + ngx_str_t dcid; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; - sid->quic = qc; + qc = ngx_quic_get_connection(c); - sid->seqnum = qc->server_seqnum; + dcid.len = sid->len; + dcid.data = sid->id; - if (qc->server_seqnum != NGX_QUIC_UNSET_PN) { - qc->server_seqnum++; + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; } - sid->len = id->len; - ngx_memcpy(sid->id, id->data, id->len); + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN); - ngx_queue_insert_tail(&qc->server_ids, &sid->queue); - qc->nserver_ids++; - - dcid.data = sid->id; - dcid.len = sid->len; - - ngx_insert_udp_connection(c, &sid->udp, &dcid); + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic insert server id seqnum:%uL id len:%uz %xV", - sid->seqnum, id->len, id); + ngx_quic_queue_frame(qc, frame); - return sid; + return NGX_OK; } -static ngx_quic_client_id_t * -ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +static ngx_int_t +ngx_quic_replace_retired_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *retired_cid) { - ngx_queue_t *q; - ngx_quic_client_id_t *cid; + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; - if (!ngx_queue_empty(&qc->free_client_ids)) { + qc = ngx_quic_get_connection(c); - q = ngx_queue_head(&qc->free_client_ids); - cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); - ngx_queue_remove(&cid->queue); + if (qsock->cid == retired_cid) { - ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + return NGX_ERROR; + } - } else { + qsock->cid = cid; + cid->refcnt++; - cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); - if (cid == NULL) { - return NULL; + ngx_quic_unref_client_id(c, retired_cid); + + if (retired_cid->refcnt == 0) { + return NGX_OK; + } } } - return cid; + return NGX_OK; } -static ngx_quic_server_id_t * -ngx_quic_alloc_server_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +void +ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) { - ngx_queue_t *q; - ngx_quic_server_id_t *sid; - - if (!ngx_queue_empty(&qc->free_server_ids)) { - - q = ngx_queue_head(&qc->free_server_ids); - sid = ngx_queue_data(q, ngx_quic_server_id_t, queue); + ngx_quic_connection_t *qc; - ngx_queue_remove(&sid->queue); + cid->refcnt--; - ngx_memzero(sid, sizeof(ngx_quic_server_id_t)); + if (cid->refcnt) { + return; + } - } else { + qc = ngx_quic_get_connection(c); - sid = ngx_pcalloc(c->pool, sizeof(ngx_quic_server_id_t)); - if (sid == NULL) { - return NULL; - } - } + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); - return sid; + qc->nclient_ids--; } diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h index cec09829d..fc7850a9c 100644 --- a/src/event/quic/ngx_event_quic_connid.h +++ b/src/event/quic/ngx_event_quic_connid.h @@ -12,14 +12,17 @@ #include -ngx_int_t ngx_quic_setup_connection_ids(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); -void ngx_quic_clear_temp_server_ids(ngx_connection_t *c); -ngx_int_t ngx_quic_issue_server_ids(ngx_connection_t *c); - ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); +ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); +ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); + +ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, + ngx_str_t *id, uint64_t seqnum, u_char *token); +ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); +void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid); + #endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 2adf9d4e7..f1c923f83 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -10,25 +10,71 @@ #include +static void ngx_quic_set_connection_path(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, + ngx_quic_socket_t *qsock); +static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c); +static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c); + + ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { - ngx_quic_frame_t *frame; + off_t max, pad; + ssize_t sent; + ngx_quic_path_t *path; + ngx_quic_frame_t frame, *fp; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { + frame.level = pkt->level; + frame.type = NGX_QUIC_FT_PATH_RESPONSE; + frame.u.path_response = *f; + + /* + * A PATH_RESPONSE frame MUST be sent on the network path where the + * PATH_CHALLENGE was received. + */ + qsock = ngx_quic_get_socket(c); + path = qsock->path; + + /* + * An endpoint MUST NOT expand the datagram containing the PATH_RESPONSE + * if the resulting data exceeds the anti-amplification limit. + */ + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + pad = ngx_min(1200, max); + + sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); + if (sent == -1) { return NGX_ERROR; } - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_PATH_RESPONSE; - frame->u.path_response = *f; + path->sent += sent; - ngx_quic_queue_frame(qc, frame); + if (qsock == qc->socket) { + /* + * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD + * send a non-probing packet in response. + */ + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = pkt->level; + fp->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, fp); + } return NGX_OK; } @@ -38,7 +84,648 @@ ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { - /* TODO */ + ngx_queue_t *q; + ngx_quic_path_t *path, *prev; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* + * A PATH_RESPONSE frame received on any network path validates the path + * on which the PATH_CHALLENGE was sent. + */ + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state != NGX_QUIC_PATH_VALIDATING) { + continue; + } + + if (ngx_memcmp(path->challenge1, f->data, sizeof(f->data)) == 0 + || ngx_memcmp(path->challenge2, f->data, sizeof(f->data)) == 0) + { + goto valid; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stale PATH_RESPONSE ignored"); + + return NGX_OK; + +valid: + + /* + * On confirming a peer's ownership of its new address, + * an endpoint MUST immediately reset the congestion controller + * and round-trip time estimator for the new path + * to initial values + * ...unless the only change in the peer's address is its port number. + */ + + prev = qc->backup->path; + + if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, + path->sockaddr, path->socklen, 0) + != NGX_OK) + { + /* address has changed */ + ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + } + + /* + * After verifying a new client address, the server SHOULD + * send new address validation tokens (Section 8) to the client. + */ + + if (ngx_quic_send_new_token(c, path) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path #%uL successfully validated", path->seqnum); + + path->state = NGX_QUIC_PATH_VALIDATED; + path->validated_at = ngx_time(); + + return NGX_OK; +} + + +static ngx_quic_path_t * +ngx_quic_alloc_path(ngx_connection_t *c) +{ + ngx_queue_t *q; + struct sockaddr *sa; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_paths)) { + + q = ngx_queue_head(&qc->free_paths); + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + ngx_queue_remove(&path->queue); + + sa = path->sockaddr; + ngx_memzero(path, sizeof(ngx_quic_path_t)); + path->sockaddr = sa; + + } else { + + path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); + if (path == NULL) { + return NULL; + } + + path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); + if (path->sockaddr == NULL) { + return NULL; + } + } + + return path; +} + + +ngx_quic_path_t * +ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, + socklen_t socklen) +{ + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + path = ngx_quic_alloc_path(c); + if (path == NULL) { + return NULL; + } + + path->seqnum = qc->path_seqnum++; + + path->socklen = socklen; + ngx_memcpy(path->sockaddr, sockaddr, socklen); + + path->addr_text.data = path->text; + path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_queue_insert_tail(&qc->paths, &path->queue); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path #%uL created src:%V", + path->seqnum, &path->addr_text); + + return path; +} + + +ngx_quic_path_t * +ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, + socklen_t socklen) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (ngx_cmp_sockaddr(sockaddr, socklen, + path->sockaddr, path->socklen, 1) + == NGX_OK) + { + return path; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_check_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *path; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + qsock = ngx_quic_get_socket(c); + + if (c->udp->dgram == NULL) { + /* 2nd QUIC packet in first UDP datagram */ + return NGX_OK; + } + + path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, + c->udp->dgram->socklen); + if (path == NULL) { + /* packet comes from unknown path, possibly migration */ + + if (qc->tp.disable_active_migration) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic migration disabled, dropping packet " + "from unknown path"); + return NGX_DECLINED; + } + + if (pkt->level != ssl_encryption_application) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too early migration attempt"); + return NGX_DECLINED; + } + + return NGX_OK; + } + + /* packet from known path */ + + if (qsock->path == NULL) { + /* client switched to previously unused server id */ + return NGX_OK; + } + + if (path == qsock->path) { + /* regular packet to expected path */ + return NGX_OK; + } + + /* client is trying to use server id already used on other path */ + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic attempt to use socket #%uL:%uL:%uL with path #%uL", + qsock->sid.seqnum, qsock->cid->seqnum, + qsock->path->seqnum, path->seqnum); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + off_t len; + ngx_quic_path_t *path; + ngx_quic_socket_t *qsock; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qsock = ngx_quic_get_socket(c); + path = qsock->path; + + if (path) { + goto update; + } + + path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, + c->udp->dgram->socklen); + + if (path == NULL) { + path = ngx_quic_add_path(c, c->udp->dgram->sockaddr, + c->udp->dgram->socklen); + if (path == NULL) { + return NGX_ERROR; + } + } + + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + qc = ngx_quic_get_connection(c); + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "no available client ids for new path"; + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no available client ids for new path"); + + return NGX_ERROR; + } + + ngx_quic_connect(c, qsock, path, cid); + +update: + + if (pkt->raw->start == pkt->data) { + len = pkt->raw->last - pkt->raw->start; + + } else { + len = 0; + } + + /* TODO: this may be too late in some cases; + * for example, if error happens during decrypt(), we cannot + * send CC, if error happens in 1st packet, due to amplification + * limit, because path->received = 0 + * + * should we account garbage as received or only decrypting packets? + */ + path->received += len; + + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet via #%uL:%uL:%uL" + " size:%O path recvd:%O sent:%O", + qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum, + len, path->received, path->sent); + + return NGX_OK; +} + + +static void +ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + size_t len; + + ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); + c->socklen = path->socklen; + + if (c->addr_text.data) { + len = ngx_min(c->addr_text.len, path->addr_text.len); + + ngx_memcpy(c->addr_text.data, path->addr_text.data, len); + c->addr_text.len = len; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send path set to #%uL addr:%V", + path->seqnum, &path->addr_text); +} + + +ngx_int_t +ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *next; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* got non-probing packet via non-active socket with different path */ + + qc = ngx_quic_get_connection(c); + + /* current socket, different from active */ + qsock = ngx_quic_get_socket(c); + + next = qsock->path; /* going to migrate to this path... */ + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic migration from #%uL:%uL:%uL (%s)" + " to #%uL:%uL:%uL (%s)", + qc->socket->sid.seqnum, qc->socket->cid->seqnum, + qc->socket->path->seqnum, + ngx_quic_path_state_str(qc->socket->path), + qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum, + ngx_quic_path_state_str(next)); + + switch (next->state) { + case NGX_QUIC_PATH_NEW: + if (ngx_quic_validate_path(c, qsock) != NGX_OK) { + return NGX_ERROR; + } + break; + + /* migration to previously known path */ + + case NGX_QUIC_PATH_VALIDATING: + /* alredy validating, nothing to do */ + break; + + case NGX_QUIC_PATH_VALIDATED: + /* if path is old enough, revalidate */ + if (ngx_time() - next->validated_at > NGX_QUIC_PATH_VALID_TIME) { + + next->state = NGX_QUIC_PATH_NEW; + + if (ngx_quic_validate_path(c, qsock) != NGX_OK) { + return NGX_ERROR; + } + } + + break; + } + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * An endpoint only changes the address to which it sends packets in + * response to the highest-numbered non-probing packet. + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_OK; + } + + /* switching connection to new path */ + + ngx_quic_set_connection_path(c, next); + + /* + * An endpoint MUST NOT reuse a connection ID when sending to + * more than one destination address. + */ + + /* preserve valid path we are migrating from */ + if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) { + + if (qc->backup) { + ngx_quic_close_socket(c, qc->backup); + } + + qc->backup = qc->socket; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic backup socket is now #%uL:%uL:%uL (%s)", + qc->backup->sid.seqnum, qc->backup->cid->seqnum, + qc->backup->path->seqnum, + ngx_quic_path_state_str(qc->backup->path)); + } + + qc->socket = qsock; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active socket is now #%uL:%uL:%uL (%s)", + qsock->sid.seqnum, qsock->cid->seqnum, + qsock->path->seqnum, ngx_quic_path_state_str(qsock->path)); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + ngx_msec_t pto; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + path = qsock->path; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initiated validation of new path #%uL", + path->seqnum); + + path->state = NGX_QUIC_PATH_VALIDATING; + + if (RAND_bytes(path->challenge1, 8) != 1) { + return NGX_ERROR; + } + + if (RAND_bytes(path->challenge2, 8) != 1) { + return NGX_ERROR; + } + + if (ngx_quic_send_path_challenge(c, path) != NGX_OK) { + return NGX_ERROR; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pto = ngx_quic_pto(c, ctx); + + path->expires = ngx_current_msec + pto; + path->tries = NGX_QUIC_PATH_RETRIES; + + if (!qc->path_validation.timer_set) { + ngx_add_timer(&qc->path_validation, pto); + } + + return NGX_OK; } + +static ngx_int_t +ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) +{ + off_t max, pad; + ssize_t sent; + ngx_quic_frame_t frame; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path #%uL send path challenge tries:%ui", + path->seqnum, path->tries); + + frame.level = ssl_encryption_application; + frame.type = NGX_QUIC_FT_PATH_CHALLENGE; + + ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); + + /* + * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes, + * unless the anti-amplification limit for the path does not permit + * sending a datagram of this size. + */ + + /* same applies to PATH_RESPONSE frames */ + + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + pad = ngx_min(1200, max); + + sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); + if (sent == -1) { + return NGX_ERROR; + } + + path->sent += sent; + + ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); + + max = (path->sent >= max) ? 0 : max - path->sent; + pad = ngx_min(1200, max); + + sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); + if (sent == -1) { + return NGX_ERROR; + } + + path->sent += sent; + + return NGX_OK; +} + + +void +ngx_quic_path_validation_handler(ngx_event_t *ev) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next, pto; + ngx_quic_path_t *path; + ngx_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pto = ngx_quic_pto(c, ctx); + + next = -1; + now = ngx_current_msec; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state != NGX_QUIC_PATH_VALIDATING) { + continue; + } + + left = path->expires - now; + + if (left > 0) { + + if (next == -1 || left < next) { + next = path->expires; + } + + continue; + } + + if (--path->tries) { + path->expires = ngx_current_msec + pto; + + if (next == -1 || pto < next) { + next = pto; + } + + /* retransmit */ + (void) ngx_quic_send_path_challenge(c, path); + + continue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic path #%uL validation failed", path->seqnum); + + /* found expired path */ + + path->state = NGX_QUIC_PATH_NEW; + + /* + * If the timer fires before the PATH_RESPONSE is received, the + * endpoint might send a new PATH_CHALLENGE, and restart the timer for + * a longer period of time. This timer SHOULD be set as described in + * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive. + */ + + if (qc->socket->path != path) { + /* the path was not actually used */ + continue; + } + + if (ngx_quic_path_restore(c) != NGX_OK) { + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; + qc->error_reason = "no viable path"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + } +} + + +static ngx_int_t +ngx_quic_path_restore(ngx_connection_t *c) +{ + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* Failure to validate a path does not cause the connection to end */ + + /* + * To protect the connection from failing due to such a spurious + * migration, an endpoint MUST revert to using the last validated + * peer address when validation of a new peer address fails. + */ + + if (qc->backup == NULL) { + return NGX_ERROR; + } + + qc->socket = qc->backup; + qc->backup = NULL; + + qsock = qc->socket; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active socket is restored to #%uL:%uL:%uL" + " (%s), no backup", + qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, + ngx_quic_path_state_str(qsock->path)); + + ngx_quic_set_connection_path(c, qsock->path); + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 5f27c278d..3de1d2c51 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -11,10 +11,34 @@ #include #include +#define NGX_QUIC_PATH_RETRIES 3 + +#define NGX_QUIC_PATH_NEW 0 +#define NGX_QUIC_PATH_VALIDATING 1 +#define NGX_QUIC_PATH_VALIDATED 2 + +#define NGX_QUIC_PATH_VALID_TIME 600 /* seconds */ + + +#define ngx_quic_path_state_str(p) \ + ((p)->state == NGX_QUIC_PATH_NEW) ? "new" : \ + (((p)->state == NGX_QUIC_PATH_VALIDATED) ? "validated" : "validating") + ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen); + +ngx_int_t ngx_quic_check_migration(ngx_connection_t *c, + ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +void ngx_quic_path_validation_handler(ngx_event_t *ev); + #endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index c8e483afb..d6085db8b 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -35,10 +35,14 @@ #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ +static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, + ngx_quic_socket_t *qsock); static ssize_t ngx_quic_output_packet(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, + ngx_quic_socket_t *qsock); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); -static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); @@ -60,12 +64,30 @@ ngx_quic_max_udp_payload(ngx_connection_t *c) ngx_int_t ngx_quic_output(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ngx_quic_socket_output(c, qc->socket) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) { off_t max; size_t len, min, in_flight; ssize_t n; u_char *p; ngx_uint_t i, pad; + ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; @@ -78,15 +100,18 @@ ngx_quic_output(ngx_connection_t *c) in_flight = cg->in_flight; + path = qsock->path; + for ( ;; ) { p = dst; len = ngx_min(qc->ctp.max_udp_payload_size, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (!qc->validated) { - max = qc->received * 3; - max = (c->sent >= max) ? 0 : max - c->sent; + if (path->state != NGX_QUIC_PATH_VALIDATED) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + len = ngx_min(len, (size_t) max); } @@ -103,7 +128,7 @@ ngx_quic_output(ngx_connection_t *c) min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; - n = ngx_quic_output_packet(c, ctx, p, len, min); + n = ngx_quic_output_packet(c, ctx, p, len, min, qsock); if (n == NGX_ERROR) { return NGX_ERROR; } @@ -117,10 +142,13 @@ ngx_quic_output(ngx_connection_t *c) break; } - n = ngx_quic_send(c, dst, len); + n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); + if (n == NGX_ERROR) { return NGX_ERROR; } + + path->sent += len; } if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { @@ -128,7 +156,6 @@ ngx_quic_output(ngx_connection_t *c) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } - ngx_quic_set_lost_timer(c); return NGX_OK; } @@ -176,14 +203,14 @@ ngx_quic_get_padding_level(ngx_connection_t *c) static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min) + u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) { size_t len, hlen, pad_len; u_char *p; ssize_t flen; ngx_str_t out, res; ngx_int_t rc; - ngx_uint_t nframes; + ngx_uint_t nframes, has_pr; ngx_msec_t now; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -196,9 +223,10 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return 0; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output %s packet max:%uz min:%uz", - ngx_quic_level_name(ctx->level), max, min); + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output sock #%uL %s packet max:%uz min:%uz", + qsock->sid.seqnum, ngx_quic_level_name(ctx->level), + max, min); qc = ngx_quic_get_connection(c); cg = &qc->congestion; @@ -208,7 +236,7 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, : NGX_QUIC_MAX_LONG_HEADER; hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qc->scid.len; + hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len; ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); @@ -216,6 +244,7 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, nframes = 0; p = src; len = 0; + has_pr = 0; for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); @@ -227,6 +256,12 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, max = cg->window; } + if (f->type == NGX_QUIC_FT_PATH_RESPONSE + || f->type == NGX_QUIC_FT_PATH_CHALLENGE) + { + has_pr = 1; + } + if (hlen + len >= max) { break; } @@ -296,15 +331,33 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt.version = qc->version; pkt.log = c->log; pkt.level = ctx->level; - pkt.dcid = qc->scid; - pkt.scid = qc->dcid; + + pkt.dcid.data = qsock->cid->id; + pkt.dcid.len = qsock->cid->len; + + pkt.scid.data = qsock->sid.id; + pkt.scid.len = qsock->sid.len; pad_len = 4; - if (min) { + if (min || has_pr) { hlen = EVP_GCM_TLS_TAG_LEN + ngx_quic_create_header(&pkt, NULL, out.len, NULL); + /* + * An endpoint MUST expand datagrams that contain a + * PATH_CHALLENGE frame to at least the smallest allowed + * maximum datagram size of 1200 bytes, unless the + * anti-amplification limit for the path does not permit + * sending a datagram of this size. + * + * (same applies to PATH_RESPONSE frames) + */ + + if (has_pr) { + min = ngx_max(1200, min); + } + if (min > hlen + pad_len) { pad_len = min - hlen; } @@ -364,11 +417,14 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } -ssize_t -ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen) { - ngx_buf_t b; - ngx_chain_t cl, *res; + ngx_buf_t b; + socklen_t orig_socklen; + ngx_chain_t cl, *res; + struct sockaddr *orig_sockaddr; ngx_memzero(&b, sizeof(ngx_buf_t)); @@ -380,7 +436,17 @@ ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len) cl.buf = &b; cl.next= NULL; + orig_socklen = c->socklen; + orig_sockaddr = c->sockaddr; + + c->sockaddr = sockaddr; + c->socklen = socklen; + res = c->send_chain(c, &cl, 0); + + c->sockaddr = orig_sockaddr; + c->socklen = orig_socklen; + if (res == NGX_CHAIN_ERROR) { return NGX_ERROR; } @@ -441,7 +507,7 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) "quic vnego packet to send len:%uz %*xs", len, len, buf); #endif - (void) ngx_quic_send(c, buf, len); + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); return NGX_ERROR; } @@ -524,7 +590,7 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - (void) ngx_quic_send(c, buf, len); + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); return NGX_DECLINED; } @@ -642,7 +708,9 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - if (ngx_quic_send(c, res.data, res.len) == NGX_ERROR) { + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) + == NGX_ERROR) + { return NGX_ERROR; } @@ -664,8 +732,8 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, - expires, 1) + if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key, + &token, &inpkt->dcid, expires, 1) != NGX_OK) { return NGX_ERROR; @@ -700,7 +768,7 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, "quic packet to send len:%uz %xV", res.len, &res); #endif - len = ngx_quic_send(c, res.data, res.len); + len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); if (len == NGX_ERROR) { return NGX_ERROR; } @@ -718,7 +786,7 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_int_t -ngx_quic_send_new_token(ngx_connection_t *c) +ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) { time_t expires; ngx_str_t token; @@ -727,13 +795,10 @@ ngx_quic_send_new_token(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - if (!qc->conf->retry) { - return NGX_OK; - } - expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) + if (ngx_quic_new_token(c, path->sockaddr, path->socklen, + qc->conf->av_token_key, &token, NULL, expires, 0) != NGX_OK) { return NGX_ERROR; @@ -849,3 +914,75 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return NGX_OK; } + + +ssize_t +ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, struct sockaddr *sockaddr, socklen_t socklen) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_header_t pkt; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + len = ngx_quic_create_frame(NULL, frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return -1; + } + + ngx_quic_log_frame(c->log, frame, 1); + + len = ngx_quic_create_frame(src, frame); + if (len == -1) { + return -1; + } + + if (len < (ssize_t) min) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min - len); + len = min; + } + + pkt.keys = qc->keys; + pkt.flags = NGX_QUIC_PKT_FIXED_BIT; + + if (qc->key_phase) { + pkt.flags |= NGX_QUIC_PKT_KPHASE; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + ngx_quic_set_packet_number(&pkt, ctx); + + pkt.version = qc->version; + pkt.log = c->log; + pkt.level = ctx->level; + + pkt.dcid.data = qc->socket->cid->id; + pkt.dcid.len = qc->socket->cid->len; + + pkt.scid.data = qc->socket->sid.id; + pkt.scid.len = qc->socket->sid.len; + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return -1; + } + + ctx->pnum++; + + len = ngx_quic_send(c, res.data, res.len, sockaddr, socklen); + + return len; +} diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h index 3277122cb..396932fe2 100644 --- a/src/event/quic/ngx_event_quic_output.h +++ b/src/event/quic/ngx_event_quic_output.h @@ -30,11 +30,14 @@ ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c); +ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); +ssize_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, struct sockaddr *sockaddr, socklen_t socklen); + #endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c new file mode 100644 index 000000000..968ad6a73 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.c @@ -0,0 +1,355 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_quic_create_temp_socket(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_str_t *dcid, ngx_quic_path_t *path, + ngx_quic_client_id_t *cid); + +static void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path); + + +ngx_int_t +ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *path; + ngx_quic_socket_t *qsock; + ngx_quic_client_id_t *cid; + + /* + * qc->nclient_ids = 0 + * qc->nsockets = 0 + * qc->max_retired_seqnum = 0 + * qc->client_seqnum = 0 + */ + + ngx_queue_init(&qc->sockets); + ngx_queue_init(&qc->free_sockets); + + ngx_queue_init(&qc->paths); + ngx_queue_init(&qc->free_paths); + + 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 */ + qsock = ngx_quic_alloc_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + /* socket is listening at new server id (autogenerated) */ + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + 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) { + 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; + + /* ngx_quic_get_connection(c) macro is now usable */ + + /* we have a client identified by scid */ + cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); + if (cid == NULL) { + goto failed; + } + + /* the client arrived from this path */ + path = ngx_quic_add_path(c, c->sockaddr, c->socklen); + if (path == NULL) { + goto failed; + } + + if (pkt->validated) { + path->state = NGX_QUIC_PATH_VALIDATED; + path->validated_at = ngx_time(); + } + + /* now bind socket to client and path */ + ngx_quic_connect(c, qsock, path, cid); + + if (ngx_quic_create_temp_socket(c, qc, &pkt->odcid, path, cid) != NGX_OK) { + goto failed; + } + + /* use this socket as default destination */ + qc->socket = qsock; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic active socket is #%uL:%uL:%uL (%s)", + qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, + ngx_quic_path_state_str(qsock->path)); + + return NGX_OK; + +failed: + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + c->udp = NULL; + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_create_temp_socket(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_str_t *dcid, ngx_quic_path_t *path, ngx_quic_client_id_t *cid) +{ + ngx_str_t id; + ngx_quic_socket_t *qsock; + ngx_quic_server_id_t *sid; + + qsock = ngx_quic_alloc_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + sid = &qsock->sid; + + sid->seqnum = NGX_QUIC_UNSET_PN; /* mark socket as temporary */ + + sid->len = dcid->len; + ngx_memcpy(sid->id, dcid->data, dcid->len); + + id.len = sid->len; + id.data = sid->id; + + ngx_insert_udp_connection(c, &qsock->udp, &id); + + 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 #%L listening at sid:%xV nsock:%ui", + (int64_t) sid->seqnum, &id, qc->nsockets); + + ngx_quic_connect(c, qsock, path, cid); + + return NGX_OK; +} + + +ngx_quic_socket_t * +ngx_quic_alloc_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_socket_t *sock; + + if (!ngx_queue_empty(&qc->free_sockets)) { + + q = ngx_queue_head(&qc->free_sockets); + sock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_queue_remove(&sock->queue); + + ngx_memzero(sock, sizeof(ngx_quic_socket_t)); + + } else { + + sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (sock == NULL) { + return NULL; + } + } + + return sock; +} + + +void +ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&qsock->queue); + ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + qc->nsockets--; + + if (qsock->path) { + ngx_quic_unref_path(c, qsock->path); + } + + if (qsock->cid) { + ngx_quic_unref_client_id(c, qsock->cid); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket #%L closed nsock:%ui", + (int64_t) qsock->sid.seqnum, qc->nsockets); +} + + +static void +ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + path->refcnt--; + + if (path->refcnt) { + return; + } + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&path->queue); + ngx_queue_insert_head(&qc->free_paths, &path->queue); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path #%uL addr:%V removed", + path->seqnum, &path->addr_text); +} + + +ngx_int_t +ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock) +{ + ngx_str_t id; + ngx_quic_server_id_t *sid; + + sid = &qsock->sid; + + sid->len = NGX_QUIC_SERVER_CID_LEN; + + if (ngx_quic_create_server_id(c, sid->id) != NGX_OK) { + return NGX_ERROR; + } + + sid->seqnum = qc->server_seqnum++; + + id.data = sid->id; + id.len = sid->len; + + ngx_insert_udp_connection(c, &qsock->udp, &id); + + 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 #%uL listening at sid:%xV nsock:%ui", + sid->seqnum, &id, qc->nsockets); + + return NGX_OK; +} + + +void +ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *sock, + ngx_quic_path_t *path, ngx_quic_client_id_t *cid) +{ + sock->path = path; + path->refcnt++; + + sock->cid = cid; + cid->refcnt++; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket #%L connected to cid #%uL path:%uL", + (int64_t) sock->sid.seqnum, + sock->cid->seqnum, path->seqnum); +} + + +void +ngx_quic_close_sockets(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_quic_close_socket(c, qc->socket); + + if (qc->backup) { + ngx_quic_close_socket(c, qc->backup); + } + + while (!ngx_queue_empty(&qc->sockets)) { + q = ngx_queue_head(&qc->sockets); + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_quic_close_socket(c, qsock); + } +} + + +ngx_quic_socket_t * +ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (qsock->sid.seqnum == seqnum) { + return qsock; + } + } + + return NULL; +} + + +ngx_quic_socket_t * +ngx_quic_get_unconnected_socket(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_socket_t *sock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + sock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (sock->cid == NULL) { + return sock; + } + } + + return NULL; + } + + diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h new file mode 100644 index 000000000..1372822c0 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); +void ngx_quic_close_sockets(ngx_connection_t *c); + +ngx_quic_socket_t *ngx_quic_alloc_socket(ngx_connection_t *c, + ngx_quic_connection_t *qc); +ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock); +void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); + +void ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *qsock, + ngx_quic_path_t *path, ngx_quic_client_id_t *cid); + +ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); +ngx_quic_socket_t *ngx_quic_get_unconnected_socket(ngx_connection_t *c); + + +#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index e68952e26..a4e96d204 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -391,8 +391,10 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ngx_quic_queue_frame(qc, frame); - if (ngx_quic_send_new_token(c) != NGX_OK) { - return NGX_ERROR; + if (qc->conf->retry) { + if (ngx_quic_send_new_token(c, qc->socket->path) != NGX_OK) { + return NGX_ERROR; + } } /* @@ -410,7 +412,8 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) */ ngx_quic_discard_ctx(c, ssl_encryption_handshake); - if (ngx_quic_issue_server_ids(c) != NGX_OK) { + /* start accepting clients on negotiated number of server ids */ + if (ngx_quic_create_sockets(c) != NGX_OK) { return NGX_ERROR; } @@ -424,6 +427,7 @@ ngx_quic_init_connection(ngx_connection_t *c) u_char *p; size_t clen; ssize_t len; + ngx_str_t dcid; ngx_ssl_conn_t *ssl_conn; ngx_quic_connection_t *qc; @@ -453,8 +457,10 @@ ngx_quic_init_connection(ngx_connection_t *c) SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); #endif - if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, - qc->tp.sr_token) + dcid.data = qc->socket->sid.id; + dcid.len = qc->socket->sid.len; + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index b896611e3..32026ae51 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -15,8 +15,8 @@ /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ -static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, - u_char buf[20]); +static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]); ngx_int_t @@ -46,8 +46,9 @@ ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, - ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) +ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t exp, ngx_uint_t is_retry) { int len, iv_len; u_char *p, *iv; @@ -56,7 +57,7 @@ ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - ngx_quic_address_hash(c, !is_retry, in); + ngx_quic_address_hash(sockaddr, socklen, !is_retry, in); p = in + 20; @@ -125,7 +126,8 @@ ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, static void -ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) +ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]) { size_t len; u_char *data; @@ -135,15 +137,15 @@ ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) struct sockaddr_in6 *sin6; #endif - len = (size_t) c->socklen; - data = (u_char *) c->sockaddr; + len = (size_t) socklen; + data = (u_char *) sockaddr; if (no_port) { - switch (c->sockaddr->sa_family) { + switch (sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; + sin6 = (struct sockaddr_in6 *) sockaddr; len = sizeof(struct in6_addr); data = sin6->sin6_addr.s6_addr; @@ -152,7 +154,7 @@ ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) #endif case AF_INET: - sin = (struct sockaddr_in *) c->sockaddr; + sin = (struct sockaddr_in *) sockaddr; len = sizeof(in_addr_t); data = (u_char *) &sin->sin_addr; @@ -236,7 +238,7 @@ ngx_quic_validate_token(ngx_connection_t *c, u_char *key, pkt->retried = (*p++ == 1); - ngx_quic_address_hash(c, !pkt->retried, addr_hash); + ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); if (ngx_memcmp(tdec, addr_hash, 20) != 0) { goto bad_token; diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h index f3185db22..25be94d65 100644 --- a/src/event/quic/ngx_event_quic_tokens.h +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -14,8 +14,9 @@ ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token); -ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, - ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); +ngx_int_t ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + 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); -- cgit v1.2.3 From f36ebdc3cd2cfd2041af8d7a3d7986a434d9c080 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 28 Apr 2021 13:37:18 +0300 Subject: QUIC: fixed build with NGX_QUIC_DEBUG_ALLOC enabled. --- src/event/quic/ngx_event_quic_connection.h | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index d5e32c66a..2e1206129 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -11,6 +11,12 @@ #include #include + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + 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; @@ -253,19 +259,10 @@ void ngx_quic_discard_ctx(ngx_connection_t *c, void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); void ngx_quic_shutdown_quic(ngx_connection_t *c); - -/********************************* DEBUG *************************************/ - #if (NGX_DEBUG) void ngx_quic_connstate_dbg(ngx_connection_t *c); #else #define ngx_quic_connstate_dbg(c) #endif - -/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ -/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ -/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ -/* #define NGX_QUIC_DEBUG_CRYPTO */ - #endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ -- cgit v1.2.3 From acc3ad0060d31609a359b3ace61bc4a0ebb780f5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 13:28:05 +0300 Subject: HTTP/3: reject empty DATA and HEADERS frames on control stream. Previously only non-empty frames were rejected. --- src/http/v3/ngx_http_v3_parse.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 18255a677..3d0b09197 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1026,7 +1026,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse frame type:%ui", st->type); - if (ngx_http_v3_is_v2_frame(st->type)) { + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_HEADERS) + { return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } @@ -1069,10 +1072,6 @@ ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) st->state = sw_max_push_id; break; - case NGX_HTTP_V3_FRAME_DATA: - case NGX_HTTP_V3_FRAME_HEADERS: - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; - default: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse skip unknown frame"); -- cgit v1.2.3 From bbbc80465bae44c4f9a1236683addbc51e7a8710 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 4 May 2021 13:38:59 +0300 Subject: HTTP/3: fixed decoder stream stubs. Now ngx_http_v3_ack_header() and ngx_http_v3_inc_insert_count() always generate decoder error. Our implementation does not use dynamic tables and does not expect client to send Section Acknowledgement or Insert Count Increment. Stream Cancellation, on the other hand, is allowed to be sent anyway. This is why ngx_http_v3_cancel_stream() does not return an error. --- src/http/v3/ngx_http_v3_tables.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 5389d0e2f..200e8a396 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -390,9 +390,9 @@ ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 ack header %ui", stream_id); - /* XXX */ + /* we do not use dynamic tables */ - return NGX_OK; + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; } @@ -402,7 +402,7 @@ ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 cancel stream %ui", stream_id); - /* XXX */ + /* we do not use dynamic tables */ return NGX_OK; } @@ -414,9 +414,9 @@ ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 increment insert count %ui", inc); - /* XXX */ + /* we do not use dynamic tables */ - return NGX_OK; + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; } -- cgit v1.2.3 From a40fa4aa9640a574cc6413dabada256f837d8cb9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 15:15:17 +0300 Subject: HTTP/3: moved Stream Cancellation stub to ngx_http_v3_streams.c. --- src/http/v3/ngx_http_v3_streams.c | 12 ++++++++++++ src/http/v3/ngx_http_v3_tables.c | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index f2036982f..b300dcc01 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -891,3 +891,15 @@ ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) return NGX_OK; } + + +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, + "http3 cancel stream %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 200e8a396..1b4a73ab0 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -396,18 +396,6 @@ ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) } -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, - "http3 cancel stream %ui", stream_id); - - /* we do not use dynamic tables */ - - return NGX_OK; -} - - ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) { -- cgit v1.2.3 From 82f8734935ef28fbda4450fd88410b7d1f359c62 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 30 Apr 2021 19:10:11 +0300 Subject: HTTP/3: ngx_http_v3_get_session() macro. It's used instead of accessing c->quic->parent->data directly. Apart from being simpler, it allows to change the way session is stored in the future by changing the macro. --- src/http/v3/ngx_http_v3.h | 3 +++ src/http/v3/ngx_http_v3_filter_module.c | 4 ++-- src/http/v3/ngx_http_v3_request.c | 4 ++-- src/http/v3/ngx_http_v3_streams.c | 14 +++++++------- src/http/v3/ngx_http_v3_tables.c | 21 ++++++++++----------- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 45d1a3671..9ee809fa1 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -74,6 +74,9 @@ #define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_connection_t *) (c)->quic->parent->data) + #define ngx_http_v3_get_module_loc_conf(c, module) \ ngx_http_get_module_loc_conf( \ ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 30e38fcfb..b8b439c24 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -783,7 +783,7 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_http_v3_connection_t *h3c; c = r->connection; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -858,7 +858,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, goto failed; } - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); c->data = hc; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c459efef5..23b827aed 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,7 +81,7 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); @@ -188,7 +188,7 @@ ngx_http_v3_cleanup_request(void *data) ngx_http_core_loc_conf_t *clcf; ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index b300dcc01..513738469 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -148,7 +148,7 @@ ngx_http_v3_close_uni_stream(ngx_connection_t *c) ngx_http_v3_uni_stream_t *us; us = c->data; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); @@ -178,7 +178,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) c = rev->data; us = c->data; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); @@ -410,7 +410,7 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) goto failed; } - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); h3c->npushing++; cln->handler = ngx_http_v3_push_cleanup; @@ -466,7 +466,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) index = -1; } - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); if (index >= 0) { if (h3c->known_streams[index]) { @@ -525,7 +525,7 @@ ngx_http_v3_send_settings(ngx_connection_t *c) ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -837,7 +837,7 @@ ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) { ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 MAX_PUSH_ID:%uL", max_push_id); @@ -860,7 +860,7 @@ ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) ngx_http_v3_push_t *push; ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 CANCEL_PUSH:%uL", push_id); diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 1b4a73ab0..c2ce14660 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -198,7 +198,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -250,8 +250,7 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 set capacity %ui", capacity); - pc = c->quic->parent; - h3c = pc->data; + h3c = ngx_http_v3_get_session(c); h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); if (capacity > h3scf->max_table_capacity) { @@ -278,6 +277,8 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) } if (dt->elts == NULL) { + pc = c->quic->parent; + cln = ngx_pool_cleanup_add(pc->pool, 0); if (cln == NULL) { return NGX_ERROR; @@ -324,7 +325,7 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; if (need > dt->capacity) { @@ -367,7 +368,7 @@ ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; if (dt->base + dt->nelts <= index) { @@ -451,7 +452,7 @@ ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; if (index < dt->base || index - dt->base >= dt->nelts) { @@ -494,7 +495,7 @@ ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) return NGX_OK; } - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); @@ -536,15 +537,13 @@ ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) { size_t n; - ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_v3_block_t *block; ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; - pc = c->quic->parent; - h3c = pc->data; + h3c = ngx_http_v3_get_session(c); dt = &h3c->table; n = dt->base + dt->nelts; @@ -624,7 +623,7 @@ ngx_http_v3_new_header(ngx_connection_t *c) ngx_http_v3_block_t *block; ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 new dynamic header, blocked:%ui", h3c->nblocked); -- cgit v1.2.3 From 38773a3c1130d34715f1cce24527a10258447354 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 14:53:36 +0300 Subject: HTTP/3: reference h3c directly from ngx_http_connection_t. Previously, an ngx_http_v3_connection_t object was created for HTTP/3 and then assinged to c->data instead of the generic ngx_http_connection_t object. Now a direct reference is added to ngx_http_connection_t, which is less confusing and does not require a flag for http3. --- src/http/modules/ngx_http_quic_module.c | 2 +- src/http/modules/ngx_http_quic_module.h | 4 ++++ src/http/ngx_http.h | 2 ++ src/http/ngx_http_request.h | 5 ++++- src/http/v3/ngx_http_v3.h | 19 +++++++------------ src/http/v3/ngx_http_v3_streams.c | 23 +++++++++-------------- src/http/v3/ngx_http_v3_tables.c | 7 +++---- 7 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index b7661b42c..2354dfd8b 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -201,7 +201,7 @@ ngx_http_quic_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init quic stream"); - phc = c->quic->parent->data; + phc = ngx_http_quic_get_connection(c); if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h index 21d4a40a1..8cadfbb87 100644 --- a/src/http/modules/ngx_http_quic_module.h +++ b/src/http/modules/ngx_http_quic_module.h @@ -18,6 +18,10 @@ #define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" +#define ngx_http_quic_get_connection(c) \ + ((ngx_http_connection_t *) (c)->quic->parent->data) + + ngx_int_t ngx_http_quic_init(ngx_connection_t *c); diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index f5d772e65..6f2d38d8b 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -21,6 +21,8 @@ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; +typedef struct ngx_http_v3_connection_s + ngx_http_v3_connection_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 5231ad6f2..01ae716fe 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -318,6 +318,10 @@ typedef struct { #endif #endif +#if (NGX_HTTP_V3 || NGX_COMPAT) + ngx_http_v3_connection_t *v3_session; +#endif + ngx_chain_t *busy; ngx_int_t nbusy; @@ -325,7 +329,6 @@ typedef struct { unsigned ssl:1; unsigned proxy_protocol:1; - unsigned http3:1; } ngx_http_connection_t; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 9ee809fa1..10ea94592 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -74,18 +74,15 @@ #define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 -#define ngx_http_v3_get_session(c) \ - ((ngx_http_v3_connection_t *) (c)->quic->parent->data) +#define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session #define ngx_http_v3_get_module_loc_conf(c, module) \ - ngx_http_get_module_loc_conf( \ - ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ - module) + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) #define ngx_http_v3_get_module_srv_conf(c, module) \ - ngx_http_get_module_srv_conf( \ - ((ngx_http_v3_connection_t *) c->quic->parent->data)->hc.conf_ctx, \ - module) + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) #define ngx_http_v3_finalize_connection(c, code, reason) \ ngx_quic_finalize_connection(c->quic->parent, code, reason) @@ -130,9 +127,7 @@ typedef struct { } ngx_http_v3_dynamic_table_t; -typedef struct { - /* the http connection must be first */ - ngx_http_connection_t hc; +struct ngx_http_v3_connection_s { ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; @@ -149,7 +144,7 @@ typedef struct { ngx_uint_t goaway; /* unsigned goaway:1; */ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; -} ngx_http_v3_connection_t; +}; void ngx_http_v3_init(ngx_connection_t *c); diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 513738469..cf3204edd 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -46,13 +46,13 @@ ngx_http_v3_init_session(ngx_connection_t *c) { ngx_connection_t *pc; ngx_pool_cleanup_t *cln; - ngx_http_connection_t *phc; + ngx_http_connection_t *hc; ngx_http_v3_connection_t *h3c; pc = c->quic->parent; - phc = pc->data; + hc = pc->data; - if (phc->http3) { + if (hc->v3_session) { return NGX_OK; } @@ -63,8 +63,6 @@ ngx_http_v3_init_session(ngx_connection_t *c) return NGX_ERROR; } - h3c->hc = *phc; - h3c->hc.http3 = 1; h3c->max_push_id = (uint64_t) -1; ngx_queue_init(&h3c->blocked); @@ -83,7 +81,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) cln->handler = ngx_http_v3_cleanup_session; cln->data = h3c; - pc->data = h3c; + hc->v3_session = h3c; return ngx_http_v3_send_settings(c); } @@ -519,13 +517,10 @@ failed: static 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_srv_conf_t *h3scf; - ngx_http_v3_connection_t *h3c; - - h3c = ngx_http_v3_get_session(c); + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_srv_conf_t *h3scf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -534,7 +529,7 @@ ngx_http_v3_send_settings(ngx_connection_t *c) return NGX_DECLINED; } - h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); n = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index c2ce14660..46dcc6734 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -251,7 +251,7 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) "http3 set capacity %ui", capacity); h3c = ngx_http_v3_get_session(c); - h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); if (capacity > h3scf->max_table_capacity) { ngx_log_error(NGX_LOG_INFO, c->log, 0, @@ -498,7 +498,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_get_module_srv_conf(h3c->hc.conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); max_entries = h3scf->max_table_capacity / 32; full_range = 2 * max_entries; @@ -582,8 +582,7 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) } if (block->queue.prev == NULL) { - h3scf = ngx_http_get_module_srv_conf(h3c->hc.conf_ctx, - ngx_http_v3_module); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); if (h3c->nblocked == h3scf->max_blocked_streams) { ngx_log_error(NGX_LOG_INFO, c->log, 0, -- cgit v1.2.3 From 0ea300d35eb066631a21b2042cfe9cd2ea94b15f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 12:54:10 +0300 Subject: HTTP/3: renamed ngx_http_v3_connection_t to ngx_http_v3_session_t. --- src/http/ngx_http.h | 3 +-- src/http/ngx_http_request.h | 2 +- src/http/v3/ngx_http_v3.h | 2 +- src/http/v3/ngx_http_v3_filter_module.c | 14 +++++++------- src/http/v3/ngx_http_v3_request.c | 4 ++-- src/http/v3/ngx_http_v3_streams.c | 34 ++++++++++++++++----------------- src/http/v3/ngx_http_v3_tables.c | 22 ++++++++++----------- 7 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 6f2d38d8b..70109adc2 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -21,8 +21,7 @@ typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; -typedef struct ngx_http_v3_connection_s - ngx_http_v3_connection_t; +typedef struct ngx_http_v3_session_s ngx_http_v3_session_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 01ae716fe..60ef4fec1 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -319,7 +319,7 @@ typedef struct { #endif #if (NGX_HTTP_V3 || NGX_COMPAT) - ngx_http_v3_connection_t *v3_session; + ngx_http_v3_session_t *v3_session; #endif ngx_chain_t *busy; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 10ea94592..d936b4389 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -127,7 +127,7 @@ typedef struct { } ngx_http_v3_dynamic_table_t; -struct ngx_http_v3_connection_s { +struct ngx_http_v3_session_s { ngx_http_v3_dynamic_table_t table; ngx_event_t keepalive; diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index b8b439c24..ce0b478c2 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -775,12 +775,12 @@ static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_chain_t ***ll) { - uint64_t push_id; - ngx_int_t rc; - ngx_chain_t *cl; - ngx_connection_t *c; - ngx_http_v3_srv_conf_t *h3scf; - ngx_http_v3_connection_t *h3c; + uint64_t push_id; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; c = r->connection; h3c = ngx_http_v3_get_session(c); @@ -838,8 +838,8 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ngx_http_request_t *r; ngx_http_log_ctx_t *ctx; ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; ngx_http_core_srv_conf_t *cscf; - ngx_http_v3_connection_t *h3c; pc = pr->connection; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 23b827aed..9d7ca952d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -59,7 +59,7 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_pool_cleanup_t *cln; ngx_http_request_t *r; ngx_http_connection_t *hc; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -185,8 +185,8 @@ ngx_http_v3_cleanup_request(void *data) { ngx_connection_t *c = data; + ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; - ngx_http_v3_connection_t *h3c; h3c = ngx_http_v3_get_session(c); diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index cf3204edd..4c0da6917 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -47,7 +47,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; pc = c->quic->parent; hc = pc->data; @@ -58,7 +58,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); - h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_connection_t)); + h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); if (h3c == NULL) { return NGX_ERROR; } @@ -104,7 +104,7 @@ ngx_http_v3_keepalive_handler(ngx_event_t *ev) static void ngx_http_v3_cleanup_session(void *data) { - ngx_http_v3_connection_t *h3c = data; + ngx_http_v3_session_t *h3c = data; if (h3c->keepalive.timer_set) { ngx_del_timer(&h3c->keepalive); @@ -142,7 +142,7 @@ static void ngx_http_v3_close_uni_stream(ngx_connection_t *c) { ngx_pool_t *pool; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; us = c->data; @@ -171,7 +171,7 @@ ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) ssize_t n; ngx_int_t index, rc; ngx_connection_t *c; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; c = rev->data; @@ -379,12 +379,12 @@ ngx_http_v3_dummy_write_handler(ngx_event_t *wev) ngx_connection_t * ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) { - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; - size_t n; - ngx_connection_t *sc; - ngx_pool_cleanup_t *cln; - ngx_http_v3_push_t *push; - ngx_http_v3_connection_t *h3c; + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; + size_t n; + ngx_connection_t *sc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_push_t *push; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create push stream id:%uL", push_id); @@ -447,7 +447,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) size_t n; ngx_int_t index; ngx_connection_t *sc; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; switch (type) { @@ -830,7 +830,7 @@ ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) { - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; h3c = ngx_http_v3_get_session(c); @@ -850,10 +850,10 @@ ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) { - ngx_queue_t *q; - ngx_http_request_t *r; - ngx_http_v3_push_t *push; - ngx_http_v3_connection_t *h3c; + ngx_queue_t *q; + ngx_http_request_t *r; + ngx_http_v3_push_t *push; + ngx_http_v3_session_t *h3c; h3c = ngx_http_v3_get_session(c); diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 46dcc6734..c7bdc2685 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -189,7 +189,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) u_char *p; size_t size; ngx_http_v3_header_t *h; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; size = ngx_http_v3_table_entry_size(name, value); @@ -243,8 +243,8 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_v3_header_t **elts; + ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; - ngx_http_v3_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -322,7 +322,7 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) size_t size, target; ngx_uint_t n; ngx_http_v3_header_t *h; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; h3c = ngx_http_v3_get_session(c); @@ -363,7 +363,7 @@ ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) { ngx_str_t name, value; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); @@ -449,7 +449,7 @@ ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) { ngx_http_v3_header_t *h; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; h3c = ngx_http_v3_get_session(c); @@ -486,7 +486,7 @@ 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_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; /* QPACK 4.5.1.1. Required Insert Count */ @@ -539,8 +539,8 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) size_t n; 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_connection_t *h3c; ngx_http_v3_dynamic_table_t *dt; h3c = ngx_http_v3_get_session(c); @@ -617,10 +617,10 @@ ngx_http_v3_unblock(void *data) static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c) { - ngx_queue_t *q; - ngx_connection_t *bc; - ngx_http_v3_block_t *block; - ngx_http_v3_connection_t *h3c; + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; h3c = ngx_http_v3_get_session(c); -- cgit v1.2.3 From 891fedf52d2c4a85e724e9abbb403334ec55c861 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 27 Apr 2021 21:32:50 +0300 Subject: HTTP/3: renamed ngx_http_v3_client_XXX() functions. The functions are renamed to ngx_http_v3_send_XXX() similar to ngx_http_v3_send_settings() and ngx_http_v3_send_goaway(). --- src/http/v3/ngx_http_v3.h | 14 +++++++------- src/http/v3/ngx_http_v3_parse.c | 2 +- src/http/v3/ngx_http_v3_streams.c | 15 +++++++-------- src/http/v3/ngx_http_v3_tables.c | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index d936b4389..32d1fd509 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -195,18 +195,18 @@ ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id); ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); -ngx_int_t ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, +ngx_int_t ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); -ngx_int_t ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, +ngx_int_t ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_v3_client_set_capacity(ngx_connection_t *c, +ngx_int_t ngx_http_v3_send_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); -ngx_int_t ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_client_ack_header(ngx_connection_t *c, +ngx_int_t ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_send_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_client_cancel_stream(ngx_connection_t *c, +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 3d0b09197..def9aadc7 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -309,7 +309,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_client_ack_header(c, c->quic->id) != NGX_OK) { + if (ngx_http_v3_send_ack_header(c, c->quic->id) != NGX_OK) { return NGX_ERROR; } } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 4c0da6917..a20008c19 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -597,7 +597,7 @@ failed: ngx_int_t -ngx_http_v3_client_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, +ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) { u_char *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2]; @@ -643,8 +643,7 @@ failed: ngx_int_t -ngx_http_v3_client_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value) +ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; @@ -693,7 +692,7 @@ failed: ngx_int_t -ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +ngx_http_v3_send_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; @@ -720,7 +719,7 @@ ngx_http_v3_client_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) ngx_int_t -ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index) +ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; @@ -747,7 +746,7 @@ ngx_http_v3_client_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_int_t -ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) +ngx_http_v3_send_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; @@ -774,7 +773,7 @@ ngx_http_v3_client_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) ngx_int_t -ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; @@ -801,7 +800,7 @@ ngx_http_v3_client_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_int_t -ngx_http_v3_client_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index c7bdc2685..f83236c01 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -224,7 +224,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) /* TODO increment can be sent less often */ - if (ngx_http_v3_client_inc_insert_count(c, 1) != NGX_OK) { + if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 32f98ecbb13acc9c22db4dd7e9bec20eb30b945a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 15:00:17 +0300 Subject: HTTP/3: moved parsing uni stream type to ngx_http_v3_parse.c. Previously it was parsed in ngx_http_v3_streams.c, while the streams were parsed in ngx_http_v3_parse.c. Now all parsing is done in one file. This simplifies parsing API and cleans up ngx_http_v3_streams.c. --- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_parse.c | 123 ++++++++++++++++++++++++++++---- src/http/v3/ngx_http_v3_parse.h | 17 +++-- src/http/v3/ngx_http_v3_streams.c | 143 ++++++++++---------------------------- 4 files changed, 159 insertions(+), 125 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 32d1fd509..736d448b5 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -169,6 +169,7 @@ uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); 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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index def9aadc7..fcb39b209 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -14,9 +14,6 @@ ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) -static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, - ngx_http_v3_parse_settings_t *st, u_char ch); - static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, ngx_http_v3_parse_varlen_int_t *st, u_char ch); static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, @@ -39,11 +36,21 @@ static ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, static ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, + ngx_http_v3_parse_control_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, u_char ch); + +static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, + ngx_http_v3_parse_encoder_t *st, u_char ch); static ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch); static ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, + ngx_http_v3_parse_decoder_t *st, u_char ch); + 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); @@ -986,11 +993,10 @@ ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, } -ngx_int_t -ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch) +static ngx_int_t +ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, + u_char ch) { - ngx_http_v3_parse_control_t *st = data; - ngx_int_t rc; enum { sw_start = 0, @@ -1208,11 +1214,10 @@ done: } -ngx_int_t -ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch) +static ngx_int_t +ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, + u_char ch) { - ngx_http_v3_parse_encoder_t *st = data; - ngx_int_t rc; enum { sw_start = 0, @@ -1500,11 +1505,10 @@ done: } -ngx_int_t -ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch) +static ngx_int_t +ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, + u_char ch) { - ngx_http_v3_parse_decoder_t *st = data; - ngx_int_t rc; enum { sw_start = 0, @@ -1674,3 +1678,92 @@ 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, + u_char ch) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_control, + sw_encoder, + sw_decoder, + sw_unknown + }; + + 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, ch); + 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: + + rc = ngx_http_v3_parse_control(c, &st->u.control, ch); + if (rc != NGX_OK) { + return rc; + } + + break; + + case sw_encoder: + + rc = ngx_http_v3_parse_encoder(c, &st->u.encoder, ch); + if (rc != NGX_OK) { + return rc; + } + + break; + + case sw_decoder: + + rc = ngx_http_v3_parse_decoder(c, &st->u.decoder, ch); + if (rc != NGX_OK) { + return rc; + } + + break; + + case sw_unknown: + break; + } + + return NGX_AGAIN; +} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index f039ac28d..c187b7bc2 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -106,6 +106,17 @@ typedef struct { } 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; + + typedef struct { ngx_uint_t state; ngx_uint_t type; @@ -128,10 +139,8 @@ ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, u_char ch); ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, u_char ch); - -ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, void *data, u_char ch); -ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, void *data, u_char ch); -ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, void *data, u_char ch); +ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, + ngx_http_v3_parse_uni_t *st, u_char ch); #endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index a20008c19..292e6653b 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -10,13 +10,8 @@ #include -typedef ngx_int_t (*ngx_http_v3_handler_pt)(ngx_connection_t *c, void *data, - u_char ch); - - typedef struct { - ngx_http_v3_handler_pt handler; - void *data; + ngx_http_v3_parse_uni_t parse; ngx_int_t index; } ngx_http_v3_uni_stream_t; @@ -32,7 +27,6 @@ typedef struct { static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); static void ngx_http_v3_cleanup_session(void *data); static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); -static void ngx_http_v3_read_uni_stream_type(ngx_event_t *rev); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); static void ngx_http_v3_push_cleanup(void *data); @@ -131,10 +125,10 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) c->data = us; - c->read->handler = ngx_http_v3_read_uni_stream_type; + c->read->handler = ngx_http_v3_uni_read_handler; c->write->handler = ngx_http_v3_dummy_write_handler; - ngx_http_v3_read_uni_stream_type(c->read); + ngx_http_v3_uni_read_handler(c->read); } @@ -164,118 +158,59 @@ ngx_http_v3_close_uni_stream(ngx_connection_t *c) } -static void -ngx_http_v3_read_uni_stream_type(ngx_event_t *rev) +ngx_int_t +ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) { - u_char ch; - ssize_t n; - ngx_int_t index, rc; - ngx_connection_t *c; + ngx_int_t index; ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; - c = rev->data; - us = c->data; - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read stream type"); - - while (rev->ready) { - - n = c->recv(c, &ch, 1); - - if (n == NGX_AGAIN) { - break; - } - - if (n == 0) { - rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; - goto failed; - } - - if (n != 1) { - rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; - goto failed; - } - - switch (ch) { - - case NGX_HTTP_V3_STREAM_ENCODER: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 encoder stream"); - - index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; - us->handler = ngx_http_v3_parse_encoder; - n = sizeof(ngx_http_v3_parse_encoder_t); - - break; - - case NGX_HTTP_V3_STREAM_DECODER: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 decoder stream"); + switch (type) { - index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; - us->handler = ngx_http_v3_parse_decoder; - n = sizeof(ngx_http_v3_parse_decoder_t); + case NGX_HTTP_V3_STREAM_ENCODER: - break; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + break; - case NGX_HTTP_V3_STREAM_CONTROL: + case NGX_HTTP_V3_STREAM_DECODER: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 control stream"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + break; - index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; - us->handler = ngx_http_v3_parse_control; - n = sizeof(ngx_http_v3_parse_control_t); + case NGX_HTTP_V3_STREAM_CONTROL: - break; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; - default: + break; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 stream 0x%02xi", (ngx_int_t) ch); - index = -1; - n = 0; - } + default: - if (index >= 0) { - if (h3c->known_streams[index]) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); - rc = NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; - goto failed; - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xL", type); + index = -1; + } - us->index = index; - h3c->known_streams[index] = c; - } + if (index >= 0) { + h3c = ngx_http_v3_get_session(c); - if (n) { - us->data = ngx_pcalloc(c->pool, n); - if (us->data == NULL) { - rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; - goto failed; - } + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; } - rev->handler = ngx_http_v3_uni_read_handler; - ngx_http_v3_uni_read_handler(rev); - return; - } + h3c->known_streams[index] = c; - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; - goto failed; + us = c->data; + us->index = index; } - return; - -failed: - - ngx_http_v3_finalize_connection(c, rc, "could not read stream type"); - ngx_http_v3_close_uni_stream(c); + return NGX_OK; } @@ -317,13 +252,9 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) break; } - if (us->handler == NULL) { - continue; - } - for (i = 0; i < n; i++) { - rc = us->handler(c, us->data, buf[i]); + rc = ngx_http_v3_parse_uni(c, &us->parse, buf[i]); if (rc == NGX_DONE) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, -- cgit v1.2.3 From de75c7e3e21822a909391a4b3ccaac7914c8641d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 15:09:23 +0300 Subject: HTTP/3: separate header files for existing source files. --- auto/modules | 5 ++- src/http/v3/ngx_http_v3.h | 80 +++------------------------------------ src/http/v3/ngx_http_v3_encode.h | 34 +++++++++++++++++ src/http/v3/ngx_http_v3_streams.h | 43 +++++++++++++++++++++ src/http/v3/ngx_http_v3_tables.h | 52 +++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 75 deletions(-) create mode 100644 src/http/v3/ngx_http_v3_encode.h create mode 100644 src/http/v3/ngx_http_v3_streams.h create mode 100644 src/http/v3/ngx_http_v3_tables.h diff --git a/auto/modules b/auto/modules index 95b722d8c..464111263 100644 --- a/auto/modules +++ b/auto/modules @@ -427,7 +427,10 @@ if [ $HTTP = YES ]; then ngx_module_name=ngx_http_v3_module ngx_module_incs=src/http/v3 ngx_module_deps="src/http/v3/ngx_http_v3.h \ - src/http/v3/ngx_http_v3_parse.h" + src/http/v3/ngx_http_v3_encode.h \ + src/http/v3/ngx_http_v3_parse.h \ + src/http/v3/ngx_http_v3_tables.h \ + src/http/v3/ngx_http_v3_streams.h" ngx_module_srcs="src/http/v3/ngx_http_v3_encode.c \ src/http/v3/ngx_http_v3_parse.c \ src/http/v3/ngx_http_v3_tables.c \ diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 736d448b5..4c25a806a 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -12,7 +12,11 @@ #include #include #include + #include +#include +#include +#include #define NGX_HTTP_V3_ALPN_ADVERTISE "\x02h3" @@ -112,21 +116,6 @@ struct ngx_http_v3_parse_s { }; -typedef struct { - ngx_str_t name; - ngx_str_t value; -} ngx_http_v3_header_t; - - -typedef struct { - ngx_http_v3_header_t **elts; - ngx_uint_t nelts; - ngx_uint_t base; - size_t size; - size_t capacity; -} ngx_http_v3_dynamic_table_t; - - struct ngx_http_v3_session_s { ngx_http_v3_dynamic_table_t table; @@ -148,68 +137,11 @@ struct ngx_http_v3_session_s { void ngx_http_v3_init(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init_session(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); -uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); -uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, - ngx_uint_t prefix); - -uintptr_t ngx_http_v3_encode_header_block_prefix(u_char *p, - ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); -uintptr_t ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, - ngx_uint_t index); -uintptr_t ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, - ngx_uint_t index, u_char *data, size_t len); -uintptr_t ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, - ngx_str_t *value); -uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index); -uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, - u_char *data, size_t len); - -ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); -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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, - uint64_t push_id); -ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); -ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value); -ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value); -ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); -ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); -ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, - ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, - ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, - ngx_uint_t *insert_count); -ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, - ngx_uint_t insert_count); -ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, - uint64_t value); -ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, - uint64_t max_push_id); -ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); - -ngx_int_t ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value); -ngx_int_t ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value); -ngx_int_t ngx_http_v3_send_set_capacity(ngx_connection_t *c, - ngx_uint_t capacity); -ngx_int_t ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_send_ack_header(ngx_connection_t *c, - ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, - ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, - ngx_uint_t inc); - extern ngx_module_t ngx_http_v3_module; diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h new file mode 100644 index 000000000..583c5675b --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_ +#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_ + + +#include +#include +#include + + +uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); +uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, + ngx_uint_t prefix); + +uintptr_t ngx_http_v3_encode_header_block_prefix(u_char *p, + ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); +uintptr_t ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index); +uintptr_t ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index, u_char *data, size_t len); +uintptr_t ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, + ngx_str_t *value); +uintptr_t ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index); +uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, + u_char *data, size_t len); + + +#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h new file mode 100644 index 000000000..c48e6425c --- /dev/null +++ b/src/http/v3/ngx_http_v3_streams.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_STREAMS_H_INCLUDED_ +#define _NGX_HTTP_V3_STREAMS_H_INCLUDED_ + + +#include +#include +#include + + +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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, + uint64_t push_id); +ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, + uint64_t max_push_id); +ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); +ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); + +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); +ngx_int_t ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_send_set_capacity(ngx_connection_t *c, + ngx_uint_t capacity); +ngx_int_t ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_send_ack_header(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +#endif /* _NGX_HTTP_V3_STREAMS_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_tables.h b/src/http/v3/ngx_http_v3_tables.h new file mode 100644 index 000000000..6cf247541 --- /dev/null +++ b/src/http/v3/ngx_http_v3_tables.h @@ -0,0 +1,52 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_TABLES_H_INCLUDED_ +#define _NGX_HTTP_V3_TABLES_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_header_t; + + +typedef struct { + ngx_http_v3_header_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; +} ngx_http_v3_dynamic_table_t; + + +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); + + +#endif /* _NGX_HTTP_V3_TABLES_H_INCLUDED_ */ -- cgit v1.2.3 From 9e05c357cc20c6673480e45e554a5b1c69b0e413 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 15:15:48 +0300 Subject: HTTP/3: moved session initialization to a separate file. Previously it was in ngx_http_v3_streams.c, but it's unrelated to streams. --- auto/modules | 3 +- src/http/v3/ngx_http_v3.c | 85 +++++++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 76 +--------------------------------- src/http/v3/ngx_http_v3_streams.h | 1 + 4 files changed, 89 insertions(+), 76 deletions(-) create mode 100644 src/http/v3/ngx_http_v3.c diff --git a/auto/modules b/auto/modules index 464111263..d454466af 100644 --- a/auto/modules +++ b/auto/modules @@ -431,7 +431,8 @@ if [ $HTTP = YES ]; then src/http/v3/ngx_http_v3_parse.h \ src/http/v3/ngx_http_v3_tables.h \ src/http/v3/ngx_http_v3_streams.h" - ngx_module_srcs="src/http/v3/ngx_http_v3_encode.c \ + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_encode.c \ src/http/v3/ngx_http_v3_parse.c \ src/http/v3/ngx_http_v3_tables.c \ src/http/v3/ngx_http_v3_streams.c \ diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c new file mode 100644 index 000000000..461388bae --- /dev/null +++ b/src/http/v3/ngx_http_v3.c @@ -0,0 +1,85 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); +static void ngx_http_v3_cleanup_session(void *data); + + +ngx_int_t +ngx_http_v3_init_session(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + + pc = c->quic->parent; + hc = pc->data; + + if (hc->v3_session) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); + + h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); + if (h3c == NULL) { + return NGX_ERROR; + } + + h3c->max_push_id = (uint64_t) -1; + + ngx_queue_init(&h3c->blocked); + ngx_queue_init(&h3c->pushing); + + h3c->keepalive.log = pc->log; + h3c->keepalive.data = pc; + h3c->keepalive.handler = ngx_http_v3_keepalive_handler; + h3c->keepalive.cancelable = 1; + + cln = ngx_pool_cleanup_add(pc->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_cleanup_session; + cln->data = h3c; + + hc->v3_session = h3c; + + return ngx_http_v3_send_settings(c); +} + + +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_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); +} + + +static void +ngx_http_v3_cleanup_session(void *data) +{ + ngx_http_v3_session_t *h3c = data; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } +} diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 292e6653b..0e55dbe0d 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -24,86 +24,12 @@ typedef struct { } ngx_http_v3_push_t; -static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); -static void ngx_http_v3_cleanup_session(void *data); static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); static void ngx_http_v3_push_cleanup(void *data); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); -static ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); - - -ngx_int_t -ngx_http_v3_init_session(ngx_connection_t *c) -{ - ngx_connection_t *pc; - ngx_pool_cleanup_t *cln; - ngx_http_connection_t *hc; - ngx_http_v3_session_t *h3c; - - pc = c->quic->parent; - hc = pc->data; - - if (hc->v3_session) { - return NGX_OK; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); - - h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); - if (h3c == NULL) { - return NGX_ERROR; - } - - h3c->max_push_id = (uint64_t) -1; - - ngx_queue_init(&h3c->blocked); - ngx_queue_init(&h3c->pushing); - - h3c->keepalive.log = pc->log; - h3c->keepalive.data = pc; - h3c->keepalive.handler = ngx_http_v3_keepalive_handler; - h3c->keepalive.cancelable = 1; - - cln = ngx_pool_cleanup_add(pc->pool, 0); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->handler = ngx_http_v3_cleanup_session; - cln->data = h3c; - - hc->v3_session = h3c; - - return ngx_http_v3_send_settings(c); -} - - -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_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "keepalive timeout"); -} - - -static void -ngx_http_v3_cleanup_session(void *data) -{ - ngx_http_v3_session_t *h3c = data; - - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); - } -} void @@ -445,7 +371,7 @@ failed: } -static ngx_int_t +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c) { u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h index c48e6425c..75325f5d4 100644 --- a/src/http/v3/ngx_http_v3_streams.h +++ b/src/http/v3/ngx_http_v3_streams.h @@ -24,6 +24,7 @@ ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); ngx_int_t ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); -- cgit v1.2.3 From 541feb5bd913294c0ef068b03293b67d0da7ef21 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 28 Apr 2021 11:30:27 +0300 Subject: HTTP/3: clean up table from session cleanup handler. Previously table had a separate cleanup handler. --- src/http/v3/ngx_http_v3.c | 2 ++ src/http/v3/ngx_http_v3_tables.c | 29 ++++++++++------------------- src/http/v3/ngx_http_v3_tables.h | 1 + 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 461388bae..a1638c504 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -79,6 +79,8 @@ ngx_http_v3_cleanup_session(void *data) { ngx_http_v3_session_t *h3c = data; + ngx_http_v3_cleanup_table(h3c); + if (h3c->keepalive.timer_set) { ngx_del_timer(&h3c->keepalive); } diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index f83236c01..e07332c96 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -14,7 +14,6 @@ static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); -static void ngx_http_v3_cleanup_table(void *data); static void ngx_http_v3_unblock(void *data); static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c); @@ -240,8 +239,6 @@ ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) { ngx_uint_t max, prev_max; - ngx_connection_t *pc; - ngx_pool_cleanup_t *cln; ngx_http_v3_header_t **elts; ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; @@ -276,18 +273,7 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) return NGX_ERROR; } - if (dt->elts == NULL) { - pc = c->quic->parent; - - cln = ngx_pool_cleanup_add(pc->pool, 0); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->handler = ngx_http_v3_cleanup_table; - cln->data = dt; - - } else { + if (dt->elts) { ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); ngx_free(dt->elts); } @@ -301,12 +287,17 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) } -static void -ngx_http_v3_cleanup_table(void *data) +void +ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) { - ngx_http_v3_dynamic_table_t *dt = data; + ngx_uint_t n; + ngx_http_v3_dynamic_table_t *dt; + + dt = &h3c->table; - ngx_uint_t n; + if (dt->elts == NULL) { + return; + } for (n = 0; n < dt->nelts; n++) { ngx_free(dt->elts[n]); diff --git a/src/http/v3/ngx_http_v3_tables.h b/src/http/v3/ngx_http_v3_tables.h index 6cf247541..36589f171 100644 --- a/src/http/v3/ngx_http_v3_tables.h +++ b/src/http/v3/ngx_http_v3_tables.h @@ -29,6 +29,7 @@ typedef struct { } ngx_http_v3_dynamic_table_t; +void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, -- cgit v1.2.3 From 59fe6ca97ae66972744cb68a3f29ac7f8d0a3002 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 6 May 2021 12:36:14 +0300 Subject: QUIC: consider NEW_CONNECTION_ID a probing frame. According to quic-transport, 9.1: PATH_CHALLENGE, PATH_RESPONSE, NEW_CONNECTION_ID, and PADDING frames are "probing frames", and all other frames are "non-probing frames". --- src/event/quic/ngx_event_quic.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 4088960b7..e19795487 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1128,6 +1128,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PADDING: case NGX_QUIC_FT_PATH_CHALLENGE: case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_NEW_CONNECTION_ID: break; /* non-probing frames */ -- cgit v1.2.3 From c8b273fd997dd021d049105ed10f6bf054f239ba Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 5 May 2021 18:11:55 +0300 Subject: QUIC: relaxed client id requirements. Client IDs cannot be reused on different paths. This change allows to reuse client id previosly seen on the same path (but with different dcid) in case when no unused client IDs are available. --- src/event/quic/ngx_event_quic_connid.c | 29 +++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_connid.h | 2 ++ src/event/quic/ngx_event_quic_migration.c | 19 +++++++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index a30e7ef35..4dbb2bbd6 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -325,6 +325,35 @@ ngx_quic_next_client_id(ngx_connection_t *c) } +ngx_quic_client_id_t * +ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* best guess: cid used by active path is good for us */ + if (qc->socket->path == path) { + return qc->socket->cid; + } + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (qsock->path && qsock->path == path) { + return qsock->cid; + } + } + + return NULL; +} + + ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h index fc7850a9c..a0552e9dd 100644 --- a/src/event/quic/ngx_event_quic_connid.h +++ b/src/event/quic/ngx_event_quic_connid.h @@ -23,6 +23,8 @@ ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, uint64_t seqnum, u_char *token); ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); +ngx_quic_client_id_t *ngx_quic_used_client_id(ngx_connection_t *c, + ngx_quic_path_t *path); void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid); #endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index f1c923f83..74dc0113b 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -348,16 +348,23 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) } } + /* prefer unused client IDs if available */ cid = ngx_quic_next_client_id(c); if (cid == NULL) { - qc = ngx_quic_get_connection(c); - qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; - qc->error_reason = "no available client ids for new path"; - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "no available client ids for new path"); + /* try to reuse connection ID used on the same path */ + cid = ngx_quic_used_client_id(c, path); + if (cid == NULL) { - return NGX_ERROR; + qc = ngx_quic_get_connection(c); + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "no available client ids for new path"; + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no available client ids for new path"); + + return NGX_ERROR; + } } ngx_quic_connect(c, qsock, path, cid); -- cgit v1.2.3 From 8f0d5edf63b385c013571439c4af3b2f0fe2c856 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 5 May 2021 19:32:49 +0300 Subject: QUIC: simplified sending 1-RTT only frames. --- src/event/quic/ngx_event_quic.c | 10 ++++------ src/event/quic/ngx_event_quic_connid.c | 19 +++++++------------ src/event/quic/ngx_event_quic_connid.h | 4 ++-- src/event/quic/ngx_event_quic_migration.c | 9 ++++----- src/event/quic/ngx_event_quic_migration.h | 4 ++-- 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index e19795487..b1aa758ee 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1265,8 +1265,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PATH_CHALLENGE: - if (ngx_quic_handle_path_challenge_frame(c, pkt, - &frame.u.path_challenge) + if (ngx_quic_handle_path_challenge_frame(c, &frame.u.path_challenge) != NGX_OK) { return NGX_ERROR; @@ -1276,8 +1275,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PATH_RESPONSE: - if (ngx_quic_handle_path_response_frame(c, pkt, - &frame.u.path_response) + if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response) != NGX_OK) { return NGX_ERROR; @@ -1287,7 +1285,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_NEW_CONNECTION_ID: - if (ngx_quic_handle_new_connection_id_frame(c, pkt, &frame.u.ncid) + if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid) != NGX_OK) { return NGX_ERROR; @@ -1297,7 +1295,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_RETIRE_CONNECTION_ID: - if (ngx_quic_handle_retire_connection_id_frame(c, pkt, + if (ngx_quic_handle_retire_connection_id_frame(c, &frame.u.retire_cid) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 4dbb2bbd6..ca9218fcb 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -16,7 +16,7 @@ static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); #endif static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum); + uint64_t seqnum); static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); @@ -75,7 +75,7 @@ ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f) + ngx_quic_new_conn_id_frame_t *f) { ngx_str_t id; ngx_queue_t *q; @@ -94,9 +94,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, * done so for that sequence number. */ - if (ngx_quic_send_retire_connection_id(c, pkt->level, f->seqnum) - != NGX_OK) - { + if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) { return NGX_ERROR; } @@ -174,9 +172,7 @@ retire: /* this connection id must be retired */ - if (ngx_quic_send_retire_connection_id(c, pkt->level, cid->seqnum) - != NGX_OK) - { + if (ngx_quic_send_retire_connection_id(c, cid->seqnum) != NGX_OK) { return NGX_ERROR; } @@ -210,8 +206,7 @@ done: static ngx_int_t -ngx_quic_send_retire_connection_id(ngx_connection_t *c, - enum ssl_encryption_level_t level, uint64_t seqnum) +ngx_quic_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum) { ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -223,7 +218,7 @@ ngx_quic_send_retire_connection_id(ngx_connection_t *c, return NGX_ERROR; } - frame->level = level; + frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; frame->u.retire_cid.sequence_number = seqnum; @@ -356,7 +351,7 @@ ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path) ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f) + ngx_quic_retire_cid_frame_t *f) { ngx_quic_path_t *path; ngx_quic_socket_t *qsock, **tmp; diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h index a0552e9dd..f823e84dc 100644 --- a/src/event/quic/ngx_event_quic_connid.h +++ b/src/event/quic/ngx_event_quic_connid.h @@ -13,9 +13,9 @@ ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_retire_cid_frame_t *f); + ngx_quic_retire_cid_frame_t *f); ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_new_conn_id_frame_t *f); + ngx_quic_new_conn_id_frame_t *f); ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 74dc0113b..d6300012d 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -22,7 +22,7 @@ static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c); ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) + ngx_quic_path_challenge_frame_t *f) { off_t max, pad; ssize_t sent; @@ -33,7 +33,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); - frame.level = pkt->level; + frame.level = ssl_encryption_application; frame.type = NGX_QUIC_FT_PATH_RESPONSE; frame.u.path_response = *f; @@ -70,7 +70,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, return NGX_ERROR; } - fp->level = pkt->level; + fp->level = ssl_encryption_application; fp->type = NGX_QUIC_FT_PING; ngx_quic_queue_frame(qc, fp); @@ -82,7 +82,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) + ngx_quic_path_challenge_frame_t *f) { ngx_queue_t *q; ngx_quic_path_t *path, *prev; @@ -557,7 +557,6 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_add_timer(&qc->path_validation, pto); } - return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 3de1d2c51..7c37c9085 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -26,9 +26,9 @@ ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); + ngx_quic_path_challenge_frame_t *f); ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, - ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); + ngx_quic_path_challenge_frame_t *f); ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, socklen_t socklen); -- cgit v1.2.3 From a8c8b33144a8e46a87113ed5bd8acb4b9aef18eb Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 17:15:20 +0300 Subject: QUIC: generic buffering for stream input. Previously each stream had an input buffer. Now memory is allocated as bytes arrive. Generic buffering mechanism is used for this. --- src/event/quic/ngx_event_quic.h | 3 +- src/event/quic/ngx_event_quic_ack.c | 10 +-- src/event/quic/ngx_event_quic_frames.c | 3 +- src/event/quic/ngx_event_quic_frames.h | 1 + src/event/quic/ngx_event_quic_streams.c | 135 +++++++++++++++++--------------- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 4492213f9..a18f2954e 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -79,7 +79,8 @@ struct ngx_quic_stream_s { uint64_t id; uint64_t acked; uint64_t send_max_data; - ngx_buf_t *b; + uint64_t recv_max_data; + ngx_chain_t *in; ngx_quic_frames_stream_t *fs; ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 84e83917f..ecd76f3c3 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -432,8 +432,6 @@ ngx_quic_detect_lost(ngx_connection_t *c) void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { - size_t n; - ngx_buf_t *b; ngx_queue_t *q; ngx_quic_frame_t *f, *start; ngx_quic_stream_t *qs; @@ -497,13 +495,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) break; } - b = qs->b; - n = qs->fs->received + (b->pos - b->start) + (b->end - b->last); - - if (f->u.max_stream_data.limit < n) { - f->u.max_stream_data.limit = n; - } - + f->u.max_stream_data.limit = qs->recv_max_data; ngx_quic_queue_frame(qc, f); break; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index e0914b92f..aaa7166c7 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -13,7 +13,6 @@ #define NGX_QUIC_BUFFER_SIZE 4096 -static void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len); @@ -84,7 +83,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) } -static void +void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) { ngx_buf_t *b, *shadow; diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 2066d9516..c7d08cb5d 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -28,6 +28,7 @@ ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit); +void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index e8dd06657..9a3d28132 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -16,7 +16,7 @@ static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, - uint64_t id, size_t rcvbuf_size); + uint64_t id); 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, @@ -30,7 +30,6 @@ static void ngx_quic_stream_cleanup_handler(void *data); ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { - size_t rcvbuf_size; uint64_t id; ngx_quic_stream_t *qs, *nqs; ngx_quic_connection_t *qc; @@ -58,7 +57,6 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) qc->streams.server_max_streams_bidi, id); qc->streams.server_streams_bidi++; - rcvbuf_size = qc->tp.initial_max_stream_data_bidi_local; } else { if (qc->streams.server_streams_uni @@ -81,10 +79,9 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) qc->streams.server_max_streams_uni, id); qc->streams.server_streams_uni++; - rcvbuf_size = 0; } - nqs = ngx_quic_create_stream(qs->parent, id, rcvbuf_size); + nqs = ngx_quic_create_stream(qs->parent, id); if (nqs == NULL) { return NULL; } @@ -235,7 +232,6 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) static ngx_quic_stream_t * ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) { - size_t n; uint64_t min_id; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -272,7 +268,6 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) min_id = (qc->streams.client_streams_uni << 2) | NGX_QUIC_STREAM_UNIDIRECTIONAL; qc->streams.client_streams_uni = (id >> 2) + 1; - n = qc->tp.initial_max_stream_data_uni; } else { @@ -296,11 +291,6 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) min_id = (qc->streams.client_streams_bidi << 2); qc->streams.client_streams_bidi = (id >> 2) + 1; - n = qc->tp.initial_max_stream_data_bidi_remote; - } - - if (n < NGX_QUIC_STREAM_BUFSIZE) { - n = NGX_QUIC_STREAM_BUFSIZE; } /* @@ -314,7 +304,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) for ( /* void */ ; min_id < id; min_id += 0x04) { - qs = ngx_quic_create_stream(c, min_id, n); + qs = ngx_quic_create_stream(c, min_id); if (qs == NULL) { return NULL; } @@ -326,12 +316,12 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) } } - return ngx_quic_create_stream(c, id, n); + return ngx_quic_create_stream(c, id); } static ngx_quic_stream_t * -ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) { ngx_log_t *log; ngx_pool_t *pool; @@ -360,12 +350,6 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) qs->parent = c; qs->id = id; - qs->b = ngx_create_temp_buf(pool, rcvbuf_size); - if (qs->b == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - qs->fs = ngx_pcalloc(pool, sizeof(ngx_quic_frames_stream_t)); if (qs->fs == NULL) { ngx_destroy_pool(pool); @@ -420,13 +404,19 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { qs->send_max_data = qc->ctp.initial_max_stream_data_uni; + + } else { + qs->recv_max_data = qc->tp.initial_max_stream_data_uni; } } else { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; + } else { qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; } } @@ -449,8 +439,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id, size_t rcvbuf_size) static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ssize_t len; + ssize_t len, n; ngx_buf_t *b; + ngx_chain_t *cl, **ll; ngx_event_t *rev; ngx_connection_t *pc; ngx_quic_frame_t *frame; @@ -458,7 +449,6 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_connection_t *qc; qs = c->quic; - b = qs->b; pc = qs->parent; qc = ngx_quic_get_connection(pc); rev = c->read; @@ -467,11 +457,11 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream recv id:0x%xL eof:%d avail:%z", - qs->id, rev->pending_eof, b->last - b->pos); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream recv id:0x%xL eof:%d", + qs->id, rev->pending_eof); - if (b->pos == b->last) { + if (qs->in == NULL) { rev->ready = 0; if (rev->pending_eof) { @@ -484,16 +474,33 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_AGAIN; } - len = ngx_min(b->last - b->pos, (ssize_t) size); + len = 0; + cl = qs->in; + + for (ll = &cl; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; - ngx_memcpy(buf, b->pos, len); + n = ngx_min(b->last - b->pos, (ssize_t) size); + buf = ngx_cpymem(buf, b->pos, n); + + len += n; + size -= n; + b->pos += n; + + if (b->pos != b->last) { + break; + } + } + + qs->in = *ll; + *ll = NULL; + + ngx_quic_free_bufs(pc, cl); - b->pos += len; qc->streams.received += len; + qs->recv_max_data += len; - if (b->pos == b->last) { - b->pos = b->start; - b->last = b->start; + if (qs->in == NULL) { rev->ready = rev->pending_eof; } @@ -510,8 +517,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->fs->received + (b->pos - b->start) - + (b->end - b->last); + frame->u.max_stream_data.limit = qs->recv_max_data; ngx_quic_queue_frame(qc, frame); } @@ -714,6 +720,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_frames(pc, &qs->fs->frames); + ngx_quic_free_bufs(pc, qs->in); if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ @@ -808,9 +815,7 @@ ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - size_t window; uint64_t last; - ngx_buf_t *b; ngx_pool_t *pool; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -846,10 +851,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sc = qs->connection; fs = qs->fs; - b = qs->b; - window = b->end - b->last; - if (last > window) { + if (last > qs->recv_max_data) { qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; goto cleanup; } @@ -867,10 +870,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } fs = qs->fs; - b = qs->b; - window = (b->pos - b->start) + (b->end - b->last); - if (last > fs->received && last - fs->received > window) { + if (last > qs->recv_max_data) { qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; return NGX_ERROR; } @@ -892,10 +893,11 @@ cleanup: ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) { + ssize_t n; uint64_t id; ngx_buf_t *b; ngx_event_t *rev; - ngx_chain_t *cl; + ngx_chain_t *cl, **ll; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -905,25 +907,35 @@ ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) f = &frame->u.stream; id = f->stream_id; + cl = frame->data; - b = qs->b; + for (ll = &qs->in; *ll; ll = &(*ll)->next) { + if ((*ll)->next) { + continue; + } - if ((size_t) ((b->pos - b->start) + (b->end - b->last)) < f->length) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no space in stream buffer"); - return NGX_ERROR; - } + /* append to last buffer */ - if ((size_t) (b->end - b->last) < f->length) { - b->last = ngx_movemem(b->start, b->pos, b->last - b->pos); - b->pos = b->start; + b = (*ll)->buf; + + while (cl && b->last != b->end) { + n = ngx_min(cl->buf->last - cl->buf->pos, b->end - b->last); + b->last = ngx_cpymem(b->last, cl->buf->pos, n); + cl->buf->pos += n; + + if (cl->buf->pos == cl->buf->last) { + cl = cl->next; + } + } } - for (cl = frame->data; cl; cl = cl->next) { - b->last = ngx_cpymem(b->last, cl->buf->pos, - cl->buf->last - cl->buf->pos); + cl = ngx_quic_copy_chain(c, cl, 0); + if (cl == NGX_CHAIN_ERROR) { + return NGX_ERROR; } + *ll = cl; + rev = qs->connection->read; rev->ready = 1; @@ -995,8 +1007,7 @@ ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) { - size_t n; - ngx_buf_t *b; + uint64_t limit; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1023,14 +1034,12 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, return NGX_OK; } - b = qs->b; - n = b->end - b->last; + limit = qs->recv_max_data; qs->connection->listening->handler(qs->connection); } else { - b = qs->b; - n = qs->fs->received + (b->pos - b->start) + (b->end - b->last); + limit = qs->recv_max_data; } frame = ngx_quic_alloc_frame(c); @@ -1041,7 +1050,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, frame->level = pkt->level; frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; frame->u.max_stream_data.id = f->id; - frame->u.max_stream_data.limit = n; + frame->u.max_stream_data.limit = limit; ngx_quic_queue_frame(qc, frame); -- cgit v1.2.3 From 66f736391ea5d0bc459c3f389a8cbfbb36fa9c86 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 18 May 2021 18:17:25 +0300 Subject: HTTP/3: fixed server push after 9ec3e71f8a61. When using server push, a segfault occured because ngx_http_v3_create_push_request() accessed ngx_http_v3_session_t object the old way. Prior to 9ec3e71f8a61, HTTP/3 session was stored directly in c->data. Now it's referenced by the v3_session field of ngx_http_connection_t. --- src/http/v3/ngx_http_v3_filter_module.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index ce0b478c2..93f3318cb 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -837,8 +837,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ngx_connection_t *c, *pc; ngx_http_request_t *r; ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc; - ngx_http_v3_session_t *h3c; + ngx_http_connection_t *hc, *phc; ngx_http_core_srv_conf_t *cscf; pc = pr->connection; @@ -858,8 +857,8 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, goto failed; } - h3c = ngx_http_v3_get_session(c); - ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); + phc = ngx_http_quic_get_connection(pc); + ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); c->data = hc; ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); -- cgit v1.2.3 From f1378601010b8f2857ac7c043ec4ac6d534543bb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Sat, 22 May 2021 18:40:45 +0300 Subject: QUIC: unroll and inline ngx_quic_varint_len()/ngx_quic_build_int(). According to profiling, those two are among most frequently called, so inlining is generally useful, and unrolling should help with it. Further, this fixes undefined behaviour seen with invalid values. Inspired by Yu Liu. --- src/event/quic/ngx_event_quic_transport.c | 53 +++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 2524223d6..c13155c55 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -66,6 +66,9 @@ #define ngx_quic_write_uint32_aligned(p, s) \ (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) +#define ngx_quic_build_int_set(p, value, len, bits) \ + (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) + #define NGX_QUIC_VERSION(c) (0xff000000 + (c)) @@ -244,40 +247,56 @@ ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) } -static ngx_uint_t +static ngx_inline ngx_uint_t ngx_quic_varint_len(uint64_t value) { - ngx_uint_t bits; + if (value < (1 << 6)) { + return 1; + } - bits = 0; - while (value >> ((8 << bits) - 2)) { - bits++; + if (value < (1 << 14)) { + return 2; } - return 1 << bits; + if (value < (1 << 30)) { + return 4; + } + + return 8; } -static void +static ngx_inline void ngx_quic_build_int(u_char **pos, uint64_t value) { - u_char *p; - ngx_uint_t bits, len; + u_char *p; p = *pos; - bits = 0; - while (value >> ((8 << bits) - 2)) { - bits++; - } + if (value < (1 << 6)) { + ngx_quic_build_int_set(p, value, 0, 0); - len = (1 << bits); + } else if (value < (1 << 14)) { + ngx_quic_build_int_set(p, value, 1, 1); + ngx_quic_build_int_set(p, value, 0, 0); - while (len--) { - *p++ = value >> (len * 8); + } else if (value < (1 << 30)) { + ngx_quic_build_int_set(p, value, 3, 2); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + + } else { + ngx_quic_build_int_set(p, value, 7, 3); + ngx_quic_build_int_set(p, value, 6, 0); + ngx_quic_build_int_set(p, value, 5, 0); + ngx_quic_build_int_set(p, value, 4, 0); + ngx_quic_build_int_set(p, value, 3, 0); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); } - **pos |= bits << 6; *pos = p; } -- cgit v1.2.3 From cd86cf34db80c517c8e26bb81bc393065eb96eda Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 May 2021 13:55:12 +0300 Subject: QUIC: refactored CRYPTO and STREAM buffer ordering. Generic function ngx_quic_order_bufs() is introduced. This function creates and maintains a chain of buffers with holes. Holes are marked with b->sync flag. Several buffers and holes in this chain may share the same underlying memory buffer. When processing STREAM frames with this function, frame data is copied only once to the right place in the stream input chain. Previously data could be copied twice. First when buffering an out-of-order frame data, and then when filling stream buffer from ordered frame queue. Now there's only one data chain for both tasks. --- src/event/quic/ngx_event_quic.c | 6 +- src/event/quic/ngx_event_quic.h | 5 +- src/event/quic/ngx_event_quic_connection.h | 14 +- src/event/quic/ngx_event_quic_frames.c | 261 ++++++++--------------------- src/event/quic/ngx_event_quic_frames.h | 7 +- src/event/quic/ngx_event_quic_ssl.c | 111 ++++++++---- src/event/quic/ngx_event_quic_ssl.h | 3 - src/event/quic/ngx_event_quic_streams.c | 162 ++++++++---------- src/event/quic/ngx_event_quic_streams.h | 2 - 9 files changed, 220 insertions(+), 351 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index b1aa758ee..cc83df0ce 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -266,10 +266,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->send_ctx[1].level = ssl_encryption_handshake; qc->send_ctx[2].level = ssl_encryption_application; - for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { - ngx_queue_init(&qc->crypto[i].frames); - } - ngx_queue_init(&qc->free_frames); qc->avg_rtt = NGX_QUIC_INITIAL_RTT; @@ -1022,6 +1018,8 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ctx = ngx_quic_get_send_ctx(qc, level); + ngx_quic_free_bufs(c, ctx->crypto); + while (!ngx_queue_empty(&ctx->sent)) { q = ngx_queue_head(&ctx->sent); ngx_queue_remove(q); diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index a18f2954e..83e72a6f3 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -70,8 +70,6 @@ typedef struct { } ngx_quic_conf_t; -typedef struct ngx_quic_frames_stream_s ngx_quic_frames_stream_t; - struct ngx_quic_stream_s { ngx_rbtree_node_t node; ngx_connection_t *parent; @@ -80,8 +78,9 @@ struct ngx_quic_stream_s { uint64_t acked; uint64_t send_max_data; uint64_t recv_max_data; + uint64_t recv_offset; + uint64_t final_size; ngx_chain_t *in; - ngx_quic_frames_stream_t *fs; ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 2e1206129..784378647 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -140,14 +140,6 @@ typedef struct { } ngx_quic_congestion_t; -struct ngx_quic_frames_stream_s { - uint64_t sent; - uint64_t received; - ngx_queue_t frames; /* reorder queue */ - size_t total; /* size of buffered data */ -}; - - /* * 12.3. Packet Numbers * @@ -159,6 +151,10 @@ struct ngx_quic_frames_stream_s { struct ngx_quic_send_ctx_s { enum ssl_encryption_level_t level; + ngx_chain_t *crypto; + uint64_t crypto_received; + uint64_t crypto_sent; + uint64_t pnum; /* to be sent */ uint64_t largest_ack; /* received from peer */ uint64_t largest_pn; /* received from peer */ @@ -203,8 +199,6 @@ struct ngx_quic_connection_s { ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; - ngx_quic_frames_stream_t crypto[NGX_QUIC_ENCRYPTION_LAST]; - ngx_quic_keys_t *keys; ngx_quic_conf_t *conf; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index aaa7166c7..3177fa992 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -16,11 +16,6 @@ static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len); -static ngx_int_t ngx_quic_buffer_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *stream, ngx_quic_frame_t *f); -static ngx_int_t ngx_quic_adjust_frame_offset(ngx_connection_t *c, - ngx_quic_frame_t *f, uint64_t offset_in); - ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c) @@ -83,6 +78,26 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) } +void +ngx_quic_trim_bufs(ngx_chain_t *in, size_t size) +{ + size_t n; + ngx_buf_t *b; + + while (in && size > 0) { + b = in->buf; + n = ngx_min((size_t) (b->last - b->pos), size); + + b->pos += n; + size -= n; + + if (b->pos == b->last) { + in = in->next; + } + } +} + + void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) { @@ -469,217 +484,75 @@ done: ngx_int_t -ngx_quic_handle_ordered_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame, ngx_quic_frame_handler_pt handler, void *data) +ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, + size_t offset) { - size_t full_len; - ngx_int_t rc; - ngx_queue_t *q; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - if (f->offset > fs->received) { - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic out-of-order frame: expecting:%uL got:%uL", - fs->received, f->offset); - - return ngx_quic_buffer_frame(c, fs, frame); - } - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - return handler == ngx_quic_crypto_input ? NGX_DECLINED : NGX_OK; - } - - /* intersecting data range, frame modified */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; - } - - /* rc == NGX_OK */ + u_char *p; + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl, *sl; - fs->received += f->length; + while (in) { + cl = *out; - /* now check the queue if we can continue with buffered frames */ + if (cl == NULL) { + cl = ngx_quic_alloc_buf(c); + if (cl == NULL) { + return NGX_ERROR; + } - do { - q = ngx_queue_head(&fs->frames); - if (q == ngx_queue_sentinel(&fs->frames)) { - break; + cl->buf->last = cl->buf->end; + cl->buf->sync = 1; /* hole */ + cl->next = NULL; + *out = cl; } - frame = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &frame->u.ord; + b = cl->buf; + n = b->last - b->pos; - if (f->offset > fs->received) { - /* gap found, nothing more to do */ - break; + if (n <= offset) { + offset -= n; + out = &cl->next; + continue; } - full_len = f->length; - - if (f->offset < fs->received) { - - if (ngx_quic_adjust_frame_offset(c, frame, fs->received) - == NGX_DONE) - { - /* old/duplicate data range */ - ngx_queue_remove(q); - fs->total -= f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic skipped buffered frame, total:%ui", - fs->total); - ngx_quic_free_frame(c, frame); - continue; + if (b->sync && offset > 0) { + sl = ngx_quic_split_bufs(c, cl, offset); + if (sl == NGX_CHAIN_ERROR) { + return NGX_ERROR; } - /* frame was adjusted, proceed to input */ - } - - /* f->offset == fs->received */ - - rc = handler(c, frame, data); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - - } else if (rc == NGX_DONE) { - /* handler destroyed stream, queue no longer exists */ - return NGX_OK; + cl->next = sl; + continue; } - fs->received += f->length; - fs->total -= full_len; - - ngx_queue_remove(q); - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic consumed buffered frame, total:%ui", fs->total); - - ngx_quic_free_frame(c, frame); - - } while (1); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_adjust_frame_offset(ngx_connection_t *c, ngx_quic_frame_t *frame, - uint64_t offset_in) -{ - size_t tail, n; - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_ordered_frame_t *f; - - f = &frame->u.ord; - - tail = offset_in - f->offset; + for (p = b->pos + offset; p != b->last && in; /* void */ ) { + n = ngx_min(b->last - p, in->buf->last - in->buf->pos); - if (tail >= f->length) { - /* range preceeding already received data or duplicate, ignore */ - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic old or duplicate data in ordered frame, ignored"); - return NGX_DONE; - } - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic adjusted ordered frame data start to expected offset"); - - /* intersecting range: adjust data size */ - - f->offset += tail; - f->length -= tail; + if (b->sync) { + ngx_memcpy(p, in->buf->pos, n); + } - for (cl = frame->data; cl; cl = cl->next) { - b = cl->buf; - n = ngx_buf_size(b); + p += n; + in->buf->pos += n; + offset += n; - if (n >= tail) { - b->pos += tail; - break; + if (in->buf->pos == in->buf->last) { + in = in->next; + } } - cl->buf->pos = cl->buf->last; - tail -= n; - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_buffer_frame(ngx_connection_t *c, ngx_quic_frames_stream_t *fs, - ngx_quic_frame_t *frame) -{ - ngx_queue_t *q; - ngx_quic_frame_t *dst, *item; - ngx_quic_ordered_frame_t *f, *df; - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_buffer_frame"); - - f = &frame->u.ord; - - /* frame start offset is in the future, buffer it */ - - dst = ngx_quic_alloc_frame(c); - if (dst == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(dst, frame, sizeof(ngx_quic_frame_t)); - - dst->data = ngx_quic_copy_chain(c, frame->data, 0); - if (dst->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } - - df = &dst->u.ord; - - fs->total += f->length; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ordered frame with unexpected offset:" - " buffered total:%ui", fs->total); - - if (ngx_queue_empty(&fs->frames)) { - ngx_queue_insert_after(&fs->frames, &dst->queue); - return NGX_OK; - } - - for (q = ngx_queue_last(&fs->frames); - q != ngx_queue_sentinel(&fs->frames); - q = ngx_queue_prev(q)) - { - item = ngx_queue_data(q, ngx_quic_frame_t, queue); - f = &item->u.ord; + if (b->sync && p != b->pos) { + sl = ngx_quic_split_bufs(c, cl, p - b->pos); + if (sl == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } - if (f->offset < df->offset) { - ngx_queue_insert_after(q, &dst->queue); - return NGX_OK; + cl->next = sl; + cl->buf->sync = 0; } } - ngx_queue_insert_after(&fs->frames, &dst->queue); - return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index c7d08cb5d..f4c147682 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -28,11 +28,10 @@ ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit); +void ngx_quic_trim_bufs(ngx_chain_t *in, size_t size); void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); - -ngx_int_t ngx_quic_handle_ordered_frame(ngx_connection_t *c, - ngx_quic_frames_stream_t *fs, ngx_quic_frame_t *frame, - ngx_quic_frame_handler_pt handler, void *data); +ngx_int_t ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, + ngx_chain_t *in, size_t offset); #if (NGX_DEBUG) void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index a4e96d204..3ade0b5ac 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -33,6 +33,7 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); static SSL_QUIC_METHOD quic_method = { @@ -149,14 +150,14 @@ static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len) { - u_char *p, *end; - size_t client_params_len; - const uint8_t *client_params; - ngx_quic_tp_t ctp; - ngx_quic_frame_t *frame; - ngx_connection_t *c; - ngx_quic_connection_t *qc; - ngx_quic_frames_stream_t *fs; + u_char *p, *end; + size_t client_params_len; + const uint8_t *client_params; + ngx_quic_tp_t ctp; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = ngx_quic_get_connection(c); @@ -228,7 +229,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, qc->client_tp_done = 1; } - fs = &qc->crypto[level]; + ctx = ngx_quic_get_send_ctx(qc, level); frame = ngx_quic_alloc_frame(c); if (frame == NULL) { @@ -242,10 +243,10 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; - frame->u.crypto.offset = fs->sent; + frame->u.crypto.offset = ctx->crypto_sent; frame->u.crypto.length = len; - fs->sent += len; + ctx->crypto_sent += len; ngx_quic_queue_frame(qc, frame); @@ -272,57 +273,97 @@ ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - uint64_t last; - ngx_int_t rc; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - ngx_quic_crypto_frame_t *f; - ngx_quic_frames_stream_t *fs; + size_t len; + uint64_t last; + ngx_buf_t *b; + ngx_chain_t *cl, **ll; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; qc = ngx_quic_get_connection(c); - fs = &qc->crypto[pkt->level]; + ctx = ngx_quic_get_send_ctx(qc, pkt->level); f = &frame->u.crypto; /* no overflow since both values are 62-bit */ last = f->offset + f->length; - if (last > fs->received && last - fs->received > NGX_QUIC_MAX_BUFFERED) { + if (last > ctx->crypto_received + NGX_QUIC_MAX_BUFFERED) { qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; return NGX_ERROR; } - rc = ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_crypto_input, - NULL); - if (rc != NGX_DECLINED) { - return rc; + if (last <= ctx->crypto_received) { + if (pkt->level == ssl_encryption_initial) { + /* speeding up handshake completion */ + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; } - /* speeding up handshake completion */ + if (f->offset > ctx->crypto_received) { + return ngx_quic_order_bufs(c, &ctx->crypto, frame->data, + f->offset - ctx->crypto_received); + } - if (pkt->level == ssl_encryption_initial) { - ctx = ngx_quic_get_send_ctx(qc, pkt->level); + ngx_quic_trim_bufs(frame->data, ctx->crypto_received - f->offset); - if (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); + if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { + return NGX_ERROR; + } - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); - while (!ngx_queue_empty(&ctx->sent)) { - ngx_quic_resend_frames(c, ctx); - } + ngx_quic_trim_bufs(ctx->crypto, last - ctx->crypto_received); + ctx->crypto_received = last; + + cl = ctx->crypto; + ll = &cl; + len = 0; + + while (*ll) { + b = (*ll)->buf; + + if (b->sync && b->pos != b->last) { + /* hole */ + break; + } + + len += b->last - b->pos; + ll = &(*ll)->next; + } + + ctx->crypto_received += len; + ctx->crypto = *ll; + *ll = NULL; + + if (cl) { + if (ngx_quic_crypto_input(c, cl) != NGX_OK) { + return NGX_ERROR; } + + ngx_quic_free_bufs(c, cl); } return NGX_OK; } -ngx_int_t -ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) { int n, sslerr; ngx_buf_t *b; ngx_chain_t *cl; ngx_ssl_conn_t *ssl_conn; + ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -334,7 +375,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) (int) SSL_quic_read_level(ssl_conn), (int) SSL_quic_write_level(ssl_conn)); - for (cl = frame->data; cl; cl = cl->next) { + for (cl = data; cl; cl = cl->next) { b = cl->buf; if (!SSL_provide_quic_data(ssl_conn, SSL_quic_read_level(ssl_conn), diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h index 68656b85c..ee0aa07c9 100644 --- a/src/event/quic/ngx_event_quic_ssl.h +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -16,7 +16,4 @@ ngx_int_t ngx_quic_init_connection(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_crypto_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); - #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 9a3d28132..816da61d5 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -349,14 +349,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qs->node.key = id; qs->parent = c; qs->id = id; - - qs->fs = ngx_pcalloc(pool, sizeof(ngx_quic_frames_stream_t)); - if (qs->fs == NULL) { - ngx_destroy_pool(pool); - return NULL; - } - - ngx_queue_init(&qs->fs->frames); + qs->final_size = (uint64_t) -1; log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { @@ -457,14 +450,14 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream recv id:0x%xL eof:%d", - qs->id, rev->pending_eof); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv eof:%d buf:%uz", + qs->id, rev->pending_eof, size); - if (qs->in == NULL) { + if (qs->in == NULL || qs->in->buf->sync) { rev->ready = 0; - if (rev->pending_eof) { + if (qs->recv_offset == qs->final_size) { rev->eof = 1; return 0; } @@ -480,6 +473,11 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) for (ll = &cl; *ll; ll = &(*ll)->next) { b = (*ll)->buf; + if (b->sync) { + /* hole */ + break; + } + n = ngx_min(b->last - b->pos, (ssize_t) size); buf = ngx_cpymem(buf, b->pos, n); @@ -499,14 +497,14 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) qc->streams.received += len; qs->recv_max_data += len; + qs->recv_offset += len; if (qs->in == NULL) { rev->ready = rev->pending_eof; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv len:%z of size:%uz", - qs->id, len, size); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z", qs->id, len); if (!rev->pending_eof) { frame = ngx_quic_alloc_frame(pc); @@ -719,7 +717,6 @@ ngx_quic_stream_cleanup_handler(void *data) "quic stream id:0x%xL cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); - ngx_quic_free_frames(pc, &qs->fs->frames); ngx_quic_free_bufs(pc, qs->in); if (qc->closing) { @@ -815,13 +812,12 @@ ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - uint64_t last; - ngx_pool_t *pool; - ngx_connection_t *sc; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; - ngx_quic_frames_stream_t *fs; + uint64_t last; + ngx_pool_t *pool; + ngx_connection_t *sc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; qc = ngx_quic_get_connection(c); f = &frame->u.stream; @@ -850,17 +846,22 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } sc = qs->connection; - fs = qs->fs; if (last > qs->recv_max_data) { qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; goto cleanup; } - if (ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - qs) - != NGX_OK) - { + if (f->fin) { + sc->read->pending_eof = 1; + qs->final_size = last; + } + + if (f->offset == 0) { + sc->read->ready = 1; + } + + if (ngx_quic_order_bufs(c, &qs->in, frame->data, f->offset) != NGX_OK) { goto cleanup; } @@ -869,90 +870,50 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - fs = qs->fs; - if (last > qs->recv_max_data) { qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; return NGX_ERROR; } - return ngx_quic_handle_ordered_frame(c, fs, frame, ngx_quic_stream_input, - qs); - -cleanup: - - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_quic_stream_input(ngx_connection_t *c, ngx_quic_frame_t *frame, void *data) -{ - ssize_t n; - uint64_t id; - ngx_buf_t *b; - ngx_event_t *rev; - ngx_chain_t *cl, **ll; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; - ngx_quic_stream_frame_t *f; - - qc = ngx_quic_get_connection(c); - qs = data; - - f = &frame->u.stream; - id = f->stream_id; - cl = frame->data; - - for (ll = &qs->in; *ll; ll = &(*ll)->next) { - if ((*ll)->next) { - continue; - } + if (qs->final_size != (uint64_t) -1 && last > qs->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } - /* append to last buffer */ + if (last <= qs->recv_offset) { + return NGX_OK; + } - b = (*ll)->buf; + if (f->offset < qs->recv_offset) { + ngx_quic_trim_bufs(frame->data, qs->recv_offset - f->offset); + f->offset = qs->recv_offset; + } - while (cl && b->last != b->end) { - n = ngx_min(cl->buf->last - cl->buf->pos, b->end - b->last); - b->last = ngx_cpymem(b->last, cl->buf->pos, n); - cl->buf->pos += n; + if (f->offset == qs->recv_offset) { + qs->connection->read->ready = 1; + } - if (cl->buf->pos == cl->buf->last) { - cl = cl->next; - } + if (f->fin) { + if (qs->final_size != (uint64_t) -1 && qs->final_size != last) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; } - } - cl = ngx_quic_copy_chain(c, cl, 0); - if (cl == NGX_CHAIN_ERROR) { - return NGX_ERROR; + qs->connection->read->pending_eof = 1; + qs->final_size = last; } - *ll = cl; - - rev = qs->connection->read; - rev->ready = 1; + return ngx_quic_order_bufs(c, &qs->in, frame->data, + f->offset - qs->recv_offset); - if (f->fin) { - rev->pending_eof = 1; - } +cleanup: - if (rev->active) { - rev->handler(rev); - } + pool = sc->pool; - /* check if stream was destroyed by handler */ - if (ngx_quic_find_stream(&qc->streams.tree, id) == NULL) { - return NGX_DONE; - } + ngx_close_connection(sc); + ngx_destroy_pool(pool); - return NGX_OK; + return NGX_ERROR; } @@ -1150,6 +1111,8 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } + qs->final_size = f->final_size; + sc = qs->connection; rev = sc->read; @@ -1161,6 +1124,13 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } + if (qs->final_size != (uint64_t) -1 && qs->final_size != f->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->final_size = f->final_size; + rev = qs->connection->read; rev->error = 1; rev->ready = 1; diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h index 1a755e91e..0ee9c37f2 100644 --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -14,8 +14,6 @@ ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); -ngx_int_t ngx_quic_stream_input(ngx_connection_t *c, - ngx_quic_frame_t *frame, void *data); void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f); ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, -- cgit v1.2.3 From 1677503f98ca577745f95508bf73dbbaf36c4b21 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 May 2021 16:41:59 +0300 Subject: QUIC: make sure stream data size is lower than final size. As per quic-transport 34, FINAL_SIZE_ERROR is generated if an endpoint received a STREAM frame or a RESET_STREAM frame containing a final size that was lower than the size of stream data that was already received. --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_streams.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 83e72a6f3..230c2c46f 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -79,6 +79,7 @@ struct ngx_quic_stream_s { uint64_t send_max_data; uint64_t recv_max_data; uint64_t recv_offset; + uint64_t recv_last; uint64_t final_size; ngx_chain_t *in; ngx_uint_t cancelable; /* unsigned cancelable:1; */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 816da61d5..d63938192 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -857,6 +857,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->final_size = last; } + qs->recv_last = last; + if (f->offset == 0) { sc->read->ready = 1; } @@ -884,6 +886,10 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } + if (qs->recv_last < last) { + qs->recv_last = last; + } + if (f->offset < qs->recv_offset) { ngx_quic_trim_bufs(frame->data, qs->recv_offset - f->offset); f->offset = qs->recv_offset; @@ -899,6 +905,11 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } + if (qs->recv_last > last) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + qs->connection->read->pending_eof = 1; qs->final_size = last; } @@ -1129,6 +1140,11 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } + if (qs->recv_last > f->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + qs->final_size = f->final_size; rev = qs->connection->read; -- cgit v1.2.3 From 27a24f4c6ba2defcbe04f447c827b4e6617b3603 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 26 May 2021 13:07:06 +0300 Subject: QUIC: call stream read handler on new data arrival. This was broken in b3f6ad181df4. --- src/event/quic/ngx_event_quic_streams.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index d63938192..192300e09 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -814,6 +814,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, { uint64_t last; ngx_pool_t *pool; + ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -895,9 +896,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, f->offset = qs->recv_offset; } - if (f->offset == qs->recv_offset) { - qs->connection->read->ready = 1; - } + rev = qs->connection->read; if (f->fin) { if (qs->final_size != (uint64_t) -1 && qs->final_size != last) { @@ -910,12 +909,26 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - qs->connection->read->pending_eof = 1; + rev->pending_eof = 1; qs->final_size = last; } - return ngx_quic_order_bufs(c, &qs->in, frame->data, - f->offset - qs->recv_offset); + if (ngx_quic_order_bufs(c, &qs->in, frame->data, + f->offset - qs->recv_offset) + != NGX_OK) + { + return NGX_ERROR; + } + + if (f->offset == qs->recv_offset) { + rev->ready = 1; + + if (rev->active) { + rev->handler(rev); + } + } + + return NGX_OK; cleanup: -- cgit v1.2.3 From 03fcff287db0d6b620f837de95116ad3a3b7e1e9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 27 May 2021 13:29:00 +0300 Subject: HTTP/3: fixed Insert With Name Reference index processing. Based on a patch by Zhiyong Sun. --- src/http/v3/ngx_http_v3_tables.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index e07332c96..8f4e28edd 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -159,12 +159,23 @@ ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) { - ngx_str_t name; + ngx_str_t name; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; if (dynamic) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 ref insert dynamic[%ui] \"%V\"", index, value); + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } -- cgit v1.2.3 From de1ce761999ab294aa346aa08475edcf9522d236 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 28 May 2021 13:45:09 +0300 Subject: README: updated after QUIC RFC publication, nginx 1.21 rebase. --- README | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README b/README index de9df4a44..6cdbd7f1f 100644 --- a/README +++ b/README @@ -15,7 +15,7 @@ Experimental QUIC support for nginx The code is developed in a separate "quic" branch available at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.19.x. We merge new nginx releases into + on nginx mainline 1.21.x. We merge new nginx releases into this branch regularly. The project code base is under the same BSD license as nginx. @@ -34,7 +34,7 @@ Experimental QUIC support for nginx What works now: - Currently we support IETF-QUIC draft-29 through draft-34. + Currently we support IETF-QUIC draft-29 through final RFC documents. Earlier drafts are NOT supported as they have incompatible wire format. nginx should be able to respond to HTTP/3 requests over QUIC and @@ -66,7 +66,7 @@ Experimental QUIC support for nginx - Flow control mechanism is basic and intended to avoid CPU hog and make simple interactions possible - - Not all draft requirements are strictly followed; some of checks are + - Not all protocol requirements are strictly followed; some of checks are omitted for the sake of simplicity of initial implementation 2. Installing @@ -242,10 +242,10 @@ Example configuration: 7. Links - [1] https://tools.ietf.org/html/draft-ietf-quic-transport - [2] https://tools.ietf.org/html/draft-ietf-quic-http + [1] https://datatracker.ietf.org/doc/html/rfc9000 + [2] https://datatracker.ietf.org/doc/html/draft-ietf-quic-http [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel [4] https://boringssl.googlesource.com/boringssl/ - [5] https://tools.ietf.org/html/draft-ietf-quic-recovery + [5] https://datatracker.ietf.org/doc/html/rfc9002 [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html -- cgit v1.2.3 From e8a76252699339b2fa615ec0e3f01ae1afbb36d8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 31 May 2021 11:54:47 +0300 Subject: HTTP/3: removed $http3 that served its purpose. To specify final protocol version by hand: add_header Alt-Svc h3=":443"; --- README | 11 ++++----- src/event/quic/ngx_event_quic.h | 4 ---- src/http/v3/ngx_http_v3_module.c | 51 +--------------------------------------- 3 files changed, 6 insertions(+), 60 deletions(-) diff --git a/README b/README index 6cdbd7f1f..f88cc3cc9 100644 --- a/README +++ b/README @@ -135,17 +135,16 @@ Experimental QUIC support for nginx http3_push http3_push_preload - Two additional variables are available: $quic and $http3. + An additional variable is available: $quic. The value of $quic is "quic" if QUIC connection is used, - and empty string otherwise. The value of $http3 is a string - "h3-xx" where "xx" is the supported draft number. + or an empty string otherwise. Example configuration: http { log_format quic '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$quic" "$http3"'; + '"$http_referer" "$http_user_agent" "$quic"'; access_log logs/access.log quic; @@ -161,7 +160,7 @@ Example configuration: location / { # required for browsers to direct them into quic port - add_header Alt-Svc '$http3=":8443"; ma=86400'; + add_header Alt-Svc 'h3=":8443"; ma=86400'; } } } @@ -202,7 +201,7 @@ Example configuration: If you've got it right, in the access log you should see something like: 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" - "nghttp3/ngtcp2 client" "quic" "h3-29" + "nghttp3/ngtcp2 client" "quic" 5. Troubleshooting diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 230c2c46f..6d4308afa 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -12,10 +12,6 @@ #include -#ifndef NGX_QUIC_DRAFT_VERSION -#define NGX_QUIC_DRAFT_VERSION 29 -#endif - #define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 18da90aab..3b651f044 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,9 +10,6 @@ #include -static ngx_int_t ngx_http_variable_http3(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); @@ -64,7 +61,7 @@ static ngx_command_t ngx_http_v3_commands[] = { static ngx_http_module_t ngx_http_v3_module_ctx = { - ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ @@ -94,52 +91,6 @@ ngx_module_t ngx_http_v3_module = { }; -static ngx_http_variable_t ngx_http_v3_vars[] = { - - { ngx_string("http3"), NULL, ngx_http_variable_http3, 0, 0, 0 }, - - ngx_http_null_variable -}; - - -static ngx_int_t -ngx_http_variable_http3(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data) -{ - v->valid = 1; - v->no_cacheable = 1; - v->not_found = 0; - - v->data = ngx_pnalloc(r->pool, sizeof("h3-xx") - 1); - if (v->data == NULL) { - return NGX_ERROR; - } - - v->len = ngx_sprintf(v->data, "h3-%d", NGX_QUIC_DRAFT_VERSION) - v->data; - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_add_variables(ngx_conf_t *cf) -{ - ngx_http_variable_t *var, *v; - - for (v = ngx_http_v3_vars; v->name.len; v++) { - var = ngx_http_add_variable(cf, &v->name, v->flags); - if (var == NULL) { - return NGX_ERROR; - } - - var->get_handler = v->get_handler; - var->data = v->data; - } - - return NGX_OK; -} - - static void * ngx_http_v3_create_srv_conf(ngx_conf_t *cf) { -- cgit v1.2.3 From 1f85c660cb3e94b60a0f2ec8495f72a43cddb378 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 1 Jun 2021 11:41:38 +0300 Subject: HTTP/3: fixed parsing encoder insertions with empty header value. When starting processing a new encoder instruction, the header state is not memzero'ed because generally it's burdensome. If the header value is empty, this resulted in inserting a stale value left from the previous instruction. Based on a patch by Zhiyong Sun. --- src/http/v3/ngx_http_v3_parse.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index fcb39b209..40366e665 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1366,6 +1366,7 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, st->literal.length = st->pint.value; if (st->literal.length == 0) { + st->value.len = 0; goto done; } @@ -1470,6 +1471,7 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, st->literal.length = st->pint.value; if (st->literal.length == 0) { + st->value.len = 0; goto done; } -- cgit v1.2.3 From dcdf62549f25f030b6cf518b9adb3d2a84313ea5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 1 Jun 2021 12:02:08 +0300 Subject: HTTP/3: undo 5a92523e50d3 after parser refactoring (e1eb7f4ca9f1). This is no longer needed after HTTP/3 request processing has moved into its own function ngx_http_v3_process_header(). --- src/http/v3/ngx_http_v3_parse.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 40366e665..21c73a24d 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -783,7 +783,6 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, st->literal.length = st->pint.value; if (st->literal.length == 0) { - st->value.data = (u_char *) ""; goto done; } -- cgit v1.2.3 From 64586eaa36f1dbc5e21e9007a372c6fb049a6986 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 7 Jun 2021 10:12:46 +0300 Subject: QUIC: stream flow control refactored. - Function ngx_quic_control_flow() is introduced. This functions does both MAX_DATA and MAX_STREAM_DATA flow controls. The function is called from STREAM and RESET_STREAM frame handlers. Previously, flow control was only accounted for STREAM. Also, MAX_DATA flow control was not accounted at all. - Function ngx_quic_update_flow() is introduced. This function advances flow control windows and sends MAX_DATA/MAX_STREAM_DATA. The function is called from RESET_STREAM frame handler, stream cleanup handler and stream recv() handler. --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_connection.h | 4 +- src/event/quic/ngx_event_quic_streams.c | 237 +++++++++++++++++++++-------- 4 files changed, 179 insertions(+), 64 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cc83df0ce..eaaed924d 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -303,6 +303,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ctp->active_connection_id_limit = 2; 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; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 6d4308afa..fe0f7fef3 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -75,6 +75,7 @@ struct ngx_quic_stream_s { uint64_t send_max_data; uint64_t recv_max_data; uint64_t recv_offset; + uint64_t recv_window; uint64_t recv_last; uint64_t final_size; ngx_chain_t *in; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 784378647..ef8c1dacc 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -115,8 +115,10 @@ typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; - uint64_t received; uint64_t sent; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; uint64_t recv_max_data; uint64_t send_max_data; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 192300e09..c6f02a37f 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -25,6 +25,8 @@ static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); +static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last); +static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); ngx_connection_t * @@ -413,6 +415,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) } } + qs->recv_window = qs->recv_max_data; + cln = ngx_pool_cleanup_add(pool, 0); if (cln == NULL) { ngx_close_connection(sc); @@ -432,18 +436,15 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ssize_t len, n; - ngx_buf_t *b; - ngx_chain_t *cl, **ll; - ngx_event_t *rev; - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + ssize_t len, n; + ngx_buf_t *b; + ngx_chain_t *cl, **ll; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; qs = c->quic; pc = qs->parent; - qc = ngx_quic_get_connection(pc); rev = c->read; if (rev->error) { @@ -495,10 +496,6 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_free_bufs(pc, cl); - qc->streams.received += len; - qs->recv_max_data += len; - qs->recv_offset += len; - if (qs->in == NULL) { rev->ready = rev->pending_eof; } @@ -506,39 +503,8 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL recv len:%z", qs->id, len); - if (!rev->pending_eof) { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->recv_max_data; - - ngx_quic_queue_frame(qc, frame); - } - - if ((qc->streams.recv_max_data / 2) < qc->streams.received) { - - frame = ngx_quic_alloc_frame(pc); - - if (frame == NULL) { - return NGX_ERROR; - } - - qc->streams.recv_max_data *= 2; - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_DATA; - frame->u.max_data.max_data = qc->streams.recv_max_data; - - ngx_quic_queue_frame(qc, frame); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv: increased max_data:%uL", - qs->id, qc->streams.recv_max_data); + if (ngx_quic_update_flow(c, qs->recv_offset + len) != NGX_OK) { + return NGX_ERROR; } return len; @@ -729,6 +695,10 @@ ngx_quic_stream_cleanup_handler(void *data) goto done; } + c->read->pending_eof = 1; + + (void) ngx_quic_update_flow(c, qs->recv_last); + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { @@ -848,8 +818,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sc = qs->connection; - if (last > qs->recv_max_data) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + if (ngx_quic_control_flow(sc, last) != NGX_OK) { goto cleanup; } @@ -858,8 +827,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->final_size = last; } - qs->recv_last = last; - if (f->offset == 0) { sc->read->ready = 1; } @@ -873,8 +840,15 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - if (last > qs->recv_max_data) { - qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + sc = qs->connection; + + rev = sc->read; + + if (rev->error) { + return NGX_OK; + } + + if (ngx_quic_control_flow(sc, last) != NGX_OK) { return NGX_ERROR; } @@ -887,17 +861,11 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - if (qs->recv_last < last) { - qs->recv_last = last; - } - if (f->offset < qs->recv_offset) { ngx_quic_trim_bufs(frame->data, qs->recv_offset - f->offset); f->offset = qs->recv_offset; } - rev = qs->connection->read; - if (f->fin) { if (qs->final_size != (uint64_t) -1 && qs->final_size != last) { qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; @@ -1108,6 +1076,7 @@ ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { + ngx_pool_t *pool; ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -1135,19 +1104,37 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } - qs->final_size = f->final_size; - sc = qs->connection; rev = sc->read; rev->error = 1; rev->ready = 1; + if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { + goto cleanup; + } + + qs->final_size = f->final_size; + + if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) { + goto cleanup; + } + sc->listening->handler(sc); return NGX_OK; } + sc = qs->connection; + + rev = sc->read; + rev->error = 1; + rev->ready = 1; + + if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { + return NGX_ERROR; + } + if (qs->final_size != (uint64_t) -1 && qs->final_size != f->final_size) { qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; return NGX_ERROR; @@ -1160,15 +1147,24 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, qs->final_size = f->final_size; - rev = qs->connection->read; - rev->error = 1; - rev->ready = 1; + if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) { + return NGX_ERROR; + } if (rev->active) { rev->handler(rev); } return NGX_OK; + +cleanup: + + pool = sc->pool; + + ngx_close_connection(sc); + ngx_destroy_pool(pool); + + return NGX_ERROR; } @@ -1285,3 +1281,118 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) "quic stream ack len:%uL acked:%uL unacked:%uL", f->u.stream.length, qs->acked, sent - qs->acked); } + + +static ngx_int_t +ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) +{ + uint64_t len; + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + rev = c->read; + qs = c->quic; + qc = ngx_quic_get_connection(qs->parent); + + if (last <= qs->recv_last) { + return NGX_OK; + } + + len = last - qs->recv_last; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow control msd:%uL/%uL md:%uL/%uL", + last, qs->recv_max_data, qc->streams.recv_last + len, + qc->streams.recv_max_data); + + qs->recv_last += len; + + if (!rev->error && qs->recv_last > qs->recv_max_data) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + qc->streams.recv_last += len; + + if (qc->streams.recv_last > qc->streams.recv_max_data) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) +{ + uint64_t len; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + rev = c->read; + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_offset) { + return NGX_OK; + } + + len = last - qs->recv_offset; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update %uL", last); + + qs->recv_offset += len; + + if (!rev->pending_eof && !rev->error + && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) + { + qs->recv_max_data = qs->recv_offset + qs->recv_window; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update msd:%uL", qs->recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->recv_max_data; + + ngx_quic_queue_frame(qc, frame); + } + + qc->streams.recv_offset += len; + + if (qc->streams.recv_max_data + <= qc->streams.recv_offset + qc->streams.recv_window / 2) + { + qc->streams.recv_max_data = qc->streams.recv_offset + + qc->streams.recv_window; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic flow update md:%uL", qc->streams.recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} -- cgit v1.2.3 From 0c77dc9c0bd9ec6c4cc38f460dfc0180a94b9910 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 9 Jun 2021 15:11:43 +0300 Subject: QUIC: persistent congestion calculation. According to RFC 9002 (quic-recovery) 7.6. --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic_ack.c | 123 ++++++++++++++++++++++++++--- src/event/quic/ngx_event_quic_connection.h | 1 + 3 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index eaaed924d..9da43ce5c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -271,6 +271,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->avg_rtt = NGX_QUIC_INITIAL_RTT; qc->rttvar = NGX_QUIC_INITIAL_RTT / 2; qc->min_rtt = NGX_TIMER_INFINITE; + qc->first_rtt = NGX_TIMER_INFINITE; /* * qc->latest_rtt = 0 diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index ecd76f3c3..368e868d8 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -18,19 +18,33 @@ #define NGX_QUIC_TIME_THR 1.125 #define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ +/* quic-recovery, section 7.6.1 Persistent congestion duration */ +#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 + #define ngx_quic_lost_threshold(qc) \ ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ NGX_QUIC_TIME_GRANULARITY) +/* send time of ACK'ed packets */ +typedef struct { + ngx_msec_t max_pn; + ngx_msec_t oldest; + ngx_msec_t newest; +} ngx_quic_ack_stat_t; + + static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, enum ssl_encryption_level_t level, ngx_msec_t send_time); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, - ngx_msec_t *send_time); + ngx_quic_ack_stat_t *st); static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t pn); -static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, + ngx_quic_ack_stat_t *st); +static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c); +static void ngx_quic_persistent_congestion(ngx_connection_t *c); static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *frame); static void ngx_quic_lost_handler(ngx_event_t *ev); @@ -43,8 +57,8 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ssize_t n; u_char *pos, *end; uint64_t min, max, gap, range; - ngx_msec_t send_time; ngx_uint_t i; + ngx_quic_ack_stat_t send_time; ngx_quic_send_ctx_t *ctx; ngx_quic_ack_frame_t *ack; ngx_quic_connection_t *qc; @@ -74,6 +88,9 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, min = ack->largest - ack->first_range; max = ack->largest; + send_time.oldest = NGX_TIMER_INFINITE; + send_time.newest = NGX_TIMER_INFINITE; + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) != NGX_OK) { @@ -94,8 +111,8 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, * - at least one of the newly acknowledged packets was ack-eliciting. */ - if (send_time != NGX_TIMER_INFINITE) { - ngx_quic_rtt_sample(c, ack, pkt->level, send_time); + if (send_time.max_pn != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn); } } @@ -141,7 +158,7 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } } - return ngx_quic_detect_lost(c); + return ngx_quic_detect_lost(c, &send_time); } @@ -161,6 +178,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, qc->min_rtt = latest_rtt; qc->avg_rtt = latest_rtt; qc->rttvar = latest_rtt / 2; + qc->first_rtt = ngx_current_msec; } else { qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); @@ -190,7 +208,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - uint64_t min, uint64_t max, ngx_msec_t *send_time) + uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) { ngx_uint_t found; ngx_queue_t *q; @@ -199,7 +217,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, qc = ngx_quic_get_connection(c); - *send_time = NGX_TIMER_INFINITE; + st->max_pn = NGX_TIMER_INFINITE; found = 0; q = ngx_queue_last(&ctx->sent); @@ -231,7 +249,16 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } if (f->pnum == max) { - *send_time = f->last; + st->max_pn = f->last; + } + + /* save earliest and latest send times of frames ack'ed */ + if (st->oldest == NGX_TIMER_INFINITE || f->last < st->oldest) { + st->oldest = f->last; + } + + if (st->newest == NGX_TIMER_INFINITE || f->last > st->newest) { + st->newest = f->last; } ngx_queue_remove(&f->queue); @@ -377,10 +404,10 @@ ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static ngx_int_t -ngx_quic_detect_lost(ngx_connection_t *c) +ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) { - ngx_uint_t i; - ngx_msec_t now, wait, thr; + ngx_uint_t i, nlost; + ngx_msec_t now, wait, thr, oldest, newest; ngx_queue_t *q; ngx_quic_frame_t *start; ngx_quic_send_ctx_t *ctx; @@ -390,6 +417,12 @@ ngx_quic_detect_lost(ngx_connection_t *c) now = ngx_current_msec; thr = ngx_quic_lost_threshold(qc); + /* send time of lost packets across all send contexts */ + oldest = NGX_TIMER_INFINITE; + newest = NGX_TIMER_INFINITE; + + nlost = 0; + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ctx = &qc->send_ctx[i]; @@ -419,16 +452,80 @@ ngx_quic_detect_lost(ngx_connection_t *c) break; } + if (start->last > qc->first_rtt) { + + if (oldest == NGX_TIMER_INFINITE || start->last < oldest) { + oldest = start->last; + } + + if (newest == NGX_TIMER_INFINITE || start->last > newest) { + newest = start->last; + } + + nlost++; + } + ngx_quic_resend_frames(c, ctx); } } + + /* Establishing Persistent Congestion (7.6.2) */ + + /* + * Once acknowledged, packets are no longer tracked. Thus no send time + * information is available for such packets. This limits persistent + * congestion algorithm to packets mentioned within ACK ranges of the + * latest ACK frame. + */ + + if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { + + if (newest - oldest > ngx_quic_pcg_duration(c)) { + ngx_quic_persistent_congestion(c); + } + } + ngx_quic_set_lost_timer(c); return NGX_OK; } +static ngx_msec_t +ngx_quic_pcg_duration(ngx_connection_t *c) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration += qc->ctp.max_ack_delay; + duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; + + return duration; +} + + +static void +ngx_quic_persistent_congestion(ngx_connection_t *c) +{ + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->recovery_start = ngx_current_msec; + cg->window = qc->tp.max_udp_payload_size * 2; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic persistent congestion win:%uz", cg->window); +} + + void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) { @@ -687,7 +784,7 @@ void ngx_quic_lost_handler(ngx_event_t *ev) c = ev->data; - if (ngx_quic_detect_lost(c) != NGX_OK) { + if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); } diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index ef8c1dacc..e6ef438e4 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -211,6 +211,7 @@ struct ngx_quic_connection_s { ngx_event_t path_validation; ngx_msec_t last_cc; + ngx_msec_t first_rtt; ngx_msec_t latest_rtt; ngx_msec_t avg_rtt; ngx_msec_t min_rtt; -- cgit v1.2.3 From bf7b32e1b60e48db7cf2a938dc4a57dfb13ce0a6 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 10 Jun 2021 23:17:51 +0300 Subject: QUIC: improved errors readability. --- src/event/quic/ngx_event_quic.c | 3 ++- src/event/quic/ngx_event_quic_ssl.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 9da43ce5c..c89a99223 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -912,7 +912,8 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) if (!ngx_quic_keys_available(qc->keys, pkt->level)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic no level %d keys yet, ignoring packet", pkt->level); + "quic no %s keys, ignoring packet", + ngx_quic_level_name(pkt->level)); return NGX_DECLINED; } diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 3ade0b5ac..1ee687b38 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -404,6 +404,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + qc->error_reason = "handshake failed"; return NGX_ERROR; } -- cgit v1.2.3 From 9cf6426f6a517919863d0618e6c514f3f180cb8f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 11 Jun 2021 10:56:51 +0300 Subject: HTTP/3: reordered H3_MISSING_SETTINGS and H3_FRAME_UNEXPECTED. The quic-http-34 is ambiguous as to what error should be generated for the first frame in control stream: Each side MUST initiate a single control stream at the beginning of the connection and send its SETTINGS frame as the first frame on this stream. If the first frame of the control stream is any other frame type, this MUST be treated as a connection error of type H3_MISSING_SETTINGS. If a DATA frame is received on a control stream, the recipient MUST respond with a connection error of type H3_FRAME_UNEXPECTED. If a HEADERS frame is received on a control stream, the recipient MUST respond with a connection error of type H3_FRAME_UNEXPECTED. Previously, H3_FRAME_UNEXPECTED had priority, but now H3_MISSING_SETTINGS has. The arguments in the spec sound more compelling for H3_MISSING_SETTINGS. --- src/http/v3/ngx_http_v3_parse.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 21c73a24d..bd5289631 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1031,6 +1031,12 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse frame type:%ui", st->type); + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } + if (ngx_http_v3_is_v2_frame(st->type) || st->type == NGX_HTTP_V3_FRAME_DATA || st->type == NGX_HTTP_V3_FRAME_HEADERS) @@ -1038,12 +1044,6 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } - if (st->state == sw_first_type - && st->type != NGX_HTTP_V3_FRAME_SETTINGS) - { - return NGX_HTTP_V3_ERR_MISSING_SETTINGS; - } - st->state = sw_length; break; -- cgit v1.2.3 From 80a5227617924f666a39f8debf556ab525f420c9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 11 Jun 2021 12:11:08 +0300 Subject: HTTP/3: generate more H3_FRAME_UNEXPECTED. As per quic-http-34, these are the cases when this error should be generated: If an endpoint receives a second SETTINGS frame on the control stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED SETTINGS frames MUST NOT be sent on any stream other than the control stream. If an endpoint receives a SETTINGS frame on a different stream, the endpoint MUST respond with a connection error of type H3_FRAME_UNEXPECTED. A client MUST NOT send a PUSH_PROMISE frame. A server MUST treat the receipt of a PUSH_PROMISE frame as a connection error of type H3_FRAME_UNEXPECTED; see Section 8. The MAX_PUSH_ID frame is always sent on the control stream. Receipt of a MAX_PUSH_ID frame on any other stream MUST be treated as a connection error of type H3_FRAME_UNEXPECTED. Receipt of an invalid sequence of frames MUST be treated as a connection error of type H3_FRAME_UNEXPECTED; see Section 8. In particular, a DATA frame before any HEADERS frame, or a HEADERS or DATA frame after the trailing HEADERS frame, is considered invalid. A CANCEL_PUSH frame is sent on the control stream. Receiving a CANCEL_PUSH frame on a stream other than the control stream MUST be treated as a connection error of type H3_FRAME_UNEXPECTED. The GOAWAY frame is always sent on the control stream. --- src/http/v3/ngx_http_v3_parse.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bd5289631..7bd9e6327 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -224,7 +224,14 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, st->type = st->vlint.value; - if (ngx_http_v3_is_v2_frame(st->type)) { + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } @@ -1037,9 +1044,16 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, return NGX_HTTP_V3_ERR_MISSING_SETTINGS; } + if (st->state != sw_first_type + && st->type == NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + if (ngx_http_v3_is_v2_frame(st->type) || st->type == NGX_HTTP_V3_FRAME_DATA - || st->type == NGX_HTTP_V3_FRAME_HEADERS) + || st->type == NGX_HTTP_V3_FRAME_HEADERS + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) { return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } @@ -1633,7 +1647,13 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, goto done; } - if (ngx_http_v3_is_v2_frame(st->type)) { + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } -- cgit v1.2.3 From 96e1db1c34a0c206463b86fb3400545f0147f476 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 11 Jun 2021 13:24:24 +0300 Subject: HTTP/3: client GOAWAY support. --- src/http/v3/ngx_http_v3.c | 1 + src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_filter_module.c | 6 ++++++ src/http/v3/ngx_http_v3_parse.c | 25 +++++++++++++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 15 +++++++++++++++ src/http/v3/ngx_http_v3_streams.h | 1 + 6 files changed, 49 insertions(+) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index a1638c504..2c838f4b5 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -37,6 +37,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) } h3c->max_push_id = (uint64_t) -1; + h3c->goaway_push_id = (uint64_t) -1; ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 4c25a806a..e693af7d8 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -129,6 +129,7 @@ struct ngx_http_v3_session_s { ngx_uint_t npushing; uint64_t next_push_id; uint64_t max_push_id; + uint64_t goaway_push_id; ngx_uint_t goaway; /* unsigned goaway:1; */ diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 93f3318cb..db40e3737 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -805,6 +805,12 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, return NGX_ABORT; } + if (h3c->goaway_push_id != (uint64_t) -1) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to goaway"); + return NGX_ABORT; + } + if (h3c->npushing >= h3scf->max_concurrent_pushes) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 abort pushes due to max_concurrent_pushes"); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 7bd9e6327..5951dff10 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1012,6 +1012,7 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, sw_cancel_push, sw_settings, sw_max_push_id, + sw_goaway, sw_skip }; @@ -1091,6 +1092,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, st->state = sw_max_push_id; break; + case NGX_HTTP_V3_FRAME_GOAWAY: + st->state = sw_goaway; + break; + default: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse skip unknown frame"); @@ -1157,6 +1162,26 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, st->state = sw_type; break; + case sw_goaway: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + + if (--st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_goaway(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_type; + break; + case sw_skip: if (--st->length == 0) { diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 0e55dbe0d..8b2047f43 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -703,6 +703,21 @@ ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) } +ngx_int_t +ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); + + h3c->goaway_push_id = push_id; + + return NGX_OK; +} + + ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) { diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h index 75325f5d4..a2126a7f8 100644 --- a/src/http/v3/ngx_http_v3_streams.h +++ b/src/http/v3/ngx_http_v3_streams.h @@ -21,6 +21,7 @@ ngx_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id); +ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); -- cgit v1.2.3 From ae58d87c0151551a269b2b00697c1deb607de11e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Jun 2021 11:55:12 +0300 Subject: QUIC: updated specification references. This includes updating citations and further clarification. --- src/event/quic/ngx_event_quic.c | 30 +++++++++++++--------- src/event/quic/ngx_event_quic_ack.c | 17 ++++++++----- src/event/quic/ngx_event_quic_connection.h | 8 +++--- src/event/quic/ngx_event_quic_connid.c | 12 ++++++--- src/event/quic/ngx_event_quic_migration.c | 41 ++++++++++++++++++++++-------- src/event/quic/ngx_event_quic_output.c | 14 +++++++--- src/event/quic/ngx_event_quic_protection.c | 28 ++++++++++++-------- src/event/quic/ngx_event_quic_ssl.c | 17 ++++++++----- src/event/quic/ngx_event_quic_streams.c | 9 +++---- src/event/quic/ngx_event_quic_transport.c | 10 +++++--- src/event/quic/ngx_event_quic_transport.h | 12 ++++++--- 11 files changed, 128 insertions(+), 70 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index c89a99223..f0dd943a6 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -506,10 +506,11 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) if (rc == NGX_DONE) { /* - * 10.2. Idle Timeout + * RFC 9000, 10.1. Idle Timeout * - * If the idle timeout is enabled by either peer, a connection is - * silently closed and its state is discarded when it remains idle + * If a max_idle_timeout is specified by either endpoint in its + * transport parameters (Section 18.2), the connection is silently + * closed and its state is discarded when it remains idle */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -519,7 +520,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) } else { /* - * 10.3. Immediate Close + * RFC 9000, 10.2. Immediate Close * * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) * to terminate the connection immediately. @@ -708,10 +709,10 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) * Instead of queueing it, we ignore it and rely on the sender's * retransmission: * - * 12.2. Coalescing Packets: + * RFC 9000, 12.2. Coalescing Packets * * For example, if decryption fails (because the keys are - * not available or any other reason), the receiver MAY either + * not available or for any other reason), the receiver MAY either * discard or buffer the packet for later processing and MUST * attempt to process the remaining packets. * @@ -831,7 +832,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, c->log->action = "processing initial packet"; if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { - /* 7.2. Negotiating Connection IDs */ + /* RFC 9000, 7.2. Negotiating Connection IDs */ ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic too short dcid in initial" " packet: len:%i", pkt->dcid.len); @@ -944,7 +945,9 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) if (pkt->level == ssl_encryption_handshake) { /* - * 4.10.1. The successful use of Handshake packets indicates + * RFC 9001, 4.9.1. Discarding Initial Keys + * + * The successful use of Handshake packets indicates * that no more Initial packets need to be exchanged */ ngx_quic_discard_ctx(c, ssl_encryption_initial); @@ -957,12 +960,13 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) if (qc->closing) { /* - * 10.1 Closing and Draining Connection States + * RFC 9000, 10.2. Immediate Close + * * ... delayed or reordered packets are properly discarded. * - * An endpoint retains only enough information to generate - * a packet containing a CONNECTION_CLOSE frame and to identify - * packets as belonging to the connection. + * In the closing state, an endpoint retains only enough information + * to generate a packet containing a CONNECTION_CLOSE frame and to + * identify packets as belonging to the connection. */ qc->error_level = pkt->level; @@ -1331,6 +1335,8 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) if (qsock->path != qc->socket->path && nonprobing) { /* + * RFC 9000, 9.2. Initiating Connection Migration + * * An endpoint can migrate a connection to a new local * address by sending packets containing non-probing frames * from that address. diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 368e868d8..c4e924c8e 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -12,13 +12,13 @@ #define NGX_QUIC_MAX_ACK_GAP 2 -/* quic-recovery, section 6.1.1, Packet Threshold */ +/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ #define NGX_QUIC_PKT_THR 3 /* packets */ -/* quic-recovery, section 6.1.2, Time Threshold */ +/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ #define NGX_QUIC_TIME_THR 1.125 #define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ -/* quic-recovery, section 7.6.1 Persistent congestion duration */ +/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ #define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 #define ngx_quic_lost_threshold(qc) \ @@ -73,9 +73,10 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ack = &f->u.ack; /* + * RFC 9000, 19.3.1. ACK Ranges + * * If any computed packet number is negative, an endpoint MUST * generate a connection error of type FRAME_ENCODING_ERROR. - * (19.3.1) */ if (ack->first_range > ack->largest) { @@ -97,13 +98,15 @@ ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - /* 13.2.3. Receiver Tracking of ACK Frames */ + /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */ if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { ctx->largest_ack = max; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic updated largest received ack:%uL", max); /* + * RFC 9002, 5.1. Generating RTT Samples + * * An endpoint generates an RTT sample on receiving an * ACK frame that meets the following two conditions: * @@ -470,7 +473,7 @@ ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) } - /* Establishing Persistent Congestion (7.6.2) */ + /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ /* * Once acknowledged, packets are no longer tracked. Thus no send time @@ -757,7 +760,7 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) qc = ngx_quic_get_connection(c); - /* PTO calculation: quic-recovery, Appendix 8 */ + /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ duration = qc->avg_rtt; duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index e6ef438e4..79c95a13e 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -38,7 +38,7 @@ typedef struct ngx_quic_keys_s ngx_quic_keys_t; #include -/* quic-recovery, section 6.2.2, kInitialRtt */ +/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ #define NGX_QUIC_UNSET_PN (uint64_t) -1 @@ -143,13 +143,13 @@ typedef struct { /* - * 12.3. Packet Numbers + * RFC 9000, 12.3. Packet Numbers * * Conceptually, a packet number space is the context in which a packet * can be processed and acknowledged. Initial packets can only be sent - * with Initial packet protection keys and acknowledged in packets which + * with Initial packet protection keys and acknowledged in packets that * are also Initial packets. -*/ + */ struct ngx_quic_send_ctx_s { enum ssl_encryption_level_t level; diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index ca9218fcb..273b58c65 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -86,11 +86,13 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, if (f->seqnum < qc->max_retired_seqnum) { /* + * RFC 9000, 19.15. NEW_CONNECTION_ID Frame + * * An endpoint that receives a NEW_CONNECTION_ID frame with * a sequence number smaller than the Retire Prior To field * of a previously received NEW_CONNECTION_ID frame MUST send * a corresponding RETIRE_CONNECTION_ID frame that retires - * the newly received connection ID, unless it has already + * the newly received connection ID, unless it has already * done so for that sequence number. */ @@ -117,8 +119,8 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, if (cid) { /* - * Transmission errors, timeouts and retransmissions might cause the - * same NEW_CONNECTION_ID frame to be received multiple times + * Transmission errors, timeouts, and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times. */ if (cid->len != f->len @@ -126,7 +128,7 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) { /* - * ..a sequence number is used for different connection IDs, + * ..if a sequence number is used for different connection IDs, * the endpoint MAY treat that receipt as a connection error * of type PROTOCOL_VIOLATION. */ @@ -190,6 +192,8 @@ done: if (qc->nclient_ids > qc->tp.active_connection_id_limit) { /* + * RFC 9000, 5.1.1. Issuing Connection IDs + * * After processing a NEW_CONNECTION_ID frame and * adding and retiring active connection IDs, if the number of active * connection IDs exceeds the value advertised in its diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index d6300012d..53e11d9c6 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -38,15 +38,17 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, frame.u.path_response = *f; /* + * RFC 9000, 8.2.2. Path Validation Responses + * * A PATH_RESPONSE frame MUST be sent on the network path where the - * PATH_CHALLENGE was received. + * PATH_CHALLENGE frame was received. */ qsock = ngx_quic_get_socket(c); path = qsock->path; /* - * An endpoint MUST NOT expand the datagram containing the PATH_RESPONSE - * if the resulting data exceeds the anti-amplification limit. + * An endpoint MUST NOT expand the datagram containing the PATH_RESPONSE + * if the resulting data exceeds the anti-amplification limit. */ max = path->received * 3; max = (path->sent >= max) ? 0 : max - path->sent; @@ -61,6 +63,8 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, if (qsock == qc->socket) { /* + * RFC 9000, 9.3.3. Off-Path Packet Forwarding + * * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD * send a non-probing packet in response. */ @@ -91,6 +95,8 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); /* + * RFC 9000, 8.2.3. Successful Path Validation + * * A PATH_RESPONSE frame received on any network path validates the path * on which the PATH_CHALLENGE was sent. */ @@ -120,11 +126,12 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c, valid: /* + * RFC 9000, 9.4. Loss Detection and Congestion Control + * * On confirming a peer's ownership of its new address, * an endpoint MUST immediately reset the congestion controller - * and round-trip time estimator for the new path - * to initial values - * ...unless the only change in the peer's address is its port number. + * and round-trip time estimator for the new path to initial values + * unless the only change in the peer's address is its port number. */ prev = qc->backup->path; @@ -144,6 +151,8 @@ valid: } /* + * RFC 9000, 9.3. Responding to Connection Migration + * * After verifying a new client address, the server SHOULD * send new address validation tokens (Section 8) to the client. */ @@ -474,6 +483,8 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) ctx = ngx_quic_get_send_ctx(qc, pkt->level); /* + * RFC 9000, 9.3. Responding to Connection Migration + * * An endpoint only changes the address to which it sends packets in * response to the highest-numbered non-probing packet. */ @@ -486,6 +497,8 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_set_connection_path(c, next); /* + * RFC 9000, 9.5. Privacy Implications of Connection Migration + * * An endpoint MUST NOT reuse a connection ID when sending to * more than one destination address. */ @@ -578,6 +591,8 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) ngx_memcpy(frame.u.path_challenge.data, path->challenge1, 8); /* + * RFC 9000, 8.2.1. Initiating Path Validation + * * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame * to at least the smallest allowed maximum datagram size of 1200 bytes, * unless the anti-amplification limit for the path does not permit @@ -675,9 +690,11 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) path->state = NGX_QUIC_PATH_NEW; /* + * RFC 9000, 9.4. Loss Detection and Congestion Control + * * If the timer fires before the PATH_RESPONSE is received, the - * endpoint might send a new PATH_CHALLENGE, and restart the timer for - * a longer period of time. This timer SHOULD be set as described in + * endpoint might send a new PATH_CHALLENGE and restart the timer for + * a longer period of time. This timer SHOULD be set as described in * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive. */ @@ -708,9 +725,13 @@ ngx_quic_path_restore(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - /* Failure to validate a path does not cause the connection to end */ - /* + * RFC 9000, 9.1. Probing a New Path + * + * Failure to validate a path does not cause the connection to end + * + * RFC 9000, 9.3.2. On-Path Address Spoofing + * * To protect the connection from failing due to such a spurious * migration, an endpoint MUST revert to using the last validated * peer address when validation of a new peer address fails. diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index d6085db8b..26b046b14 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -23,9 +23,11 @@ /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ /* + * RFC 9000, 10.3. Stateless Reset + * * Endpoints MUST discard packets that are too small to be valid QUIC * packets. With the set of AEAD functions defined in [QUIC-TLS], - * packets that are smaller than 21 bytes are never valid. + * short header packets that are smaller than 21 bytes are never valid. */ #define NGX_QUIC_MIN_PKT_LEN 21 @@ -170,11 +172,11 @@ ngx_quic_get_padding_level(ngx_connection_t *c) ngx_quic_connection_t *qc; /* - * 14.1. Initial Datagram Size + * RFC 9000, 14.1. Initial Datagram Size * * Similarly, a server MUST expand the payload of all UDP datagrams * carrying ack-eliciting Initial packets to at least the smallest - * allowed maximum datagram size of 1200 bytes + * allowed maximum datagram size of 1200 bytes. */ qc = ngx_quic_get_connection(c); @@ -345,6 +347,8 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_create_header(&pkt, NULL, out.len, NULL); /* + * RFC 9000, 8.2.1. Initiating Path Validation + * * An endpoint MUST expand datagrams that contain a * PATH_CHALLENGE frame to at least the smallest allowed * maximum datagram size of 1200 bytes, unless the @@ -777,7 +781,9 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, "quic retry packet sent to %xV", &pkt.dcid); /* - * quic-transport 17.2.5.1: A server MUST NOT send more than one Retry + * RFC 9000, 17.2.5.1. Sending a Retry Packet + * + * A server MUST NOT send more than one Retry * packet in response to a single UDP datagram. * NGX_DONE will stop quic_input() from processing further */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index d1801d05f..82db65921 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -160,7 +160,12 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, client = &keys->secrets[ssl_encryption_initial].client; server = &keys->secrets[ssl_encryption_initial].server; - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + /* + * RFC 9001, section 5. Packet Protection + * + * Initial packets use AEAD_AES_128_GCM. The hash function + * for HKDF when deriving initial secrets and keys is SHA-256. + */ cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); @@ -187,7 +192,6 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, "quic initial secret len:%uz %*xs", is_len, is_len, is); #endif - /* draft-ietf-quic-tls-23#section-5.2 */ client->secret.len = SHA256_DIGEST_LENGTH; server->secret.len = SHA256_DIGEST_LENGTH; @@ -206,7 +210,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_str_t *prk; } seq[] = { - /* draft-ietf-quic-tls-23#section-5.2 */ + /* labels per RFC 9001, 5.1. Packet Protection Keys */ { ngx_string("tls13 client in"), &client->secret, &iss }, { ngx_string("tls13 quic key"), @@ -219,14 +223,12 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, &client->secret, }, { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), &client->hp, &client->secret, }, { ngx_string("tls13 server in"), &server->secret, &iss }, { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ ngx_string("tls13 quic key"), &server->key, &server->secret, @@ -237,7 +239,6 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, &server->secret, }, { - /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ ngx_string("tls13 quic hp"), &server->hp, &server->secret, @@ -894,7 +895,7 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) return NGX_ERROR; } - /* quic-tls: 5.4.1. Header Protection Application */ + /* RFC 9001, 5.4.1. Header Protection Application */ ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); for (i = 0; i < pkt->num_len; i++) { @@ -1095,10 +1096,13 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) p = pkt->raw->pos; len = pkt->data + pkt->len - p; - /* draft-ietf-quic-tls-23#section-5.4.2: + /* + * RFC 9001, 5.4.2. Header Protection Sample + * 5.4.3. AES-Based Header Protection + * 5.4.4. ChaCha20-Based Header Protection + * * the Packet Number field is assumed to be 4 bytes long - * draft-ietf-quic-tls-23#section-5.4.[34]: - * AES-Based and ChaCha20-Based header protections sample 16 bytes + * AES and ChaCha20 algorithms sample 16 bytes */ if (len < EVP_GCM_TLS_TAG_LEN + 4) { @@ -1172,6 +1176,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) if (pkt->payload.len == 0) { /* + * RFC 9000, 12.4. Frames and Frame Types + * * An endpoint MUST treat receipt of a packet containing no * frames as a connection error of type PROTOCOL_VIOLATION. */ @@ -1182,6 +1188,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { /* + * RFC 9000, Reserved Bits + * * An endpoint MUST treat receipt of a packet that has * a non-zero value for these bits, after removing both * packet and header protection, as a connection error diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 1ee687b38..5e2827f23 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -11,8 +11,9 @@ /* - * 7.4. Cryptographic Message Buffering - * Implementations MUST support buffering at least 4096 bytes of data + * RFC 9000, 7.5. Cryptographic Message Buffering + * + * Implementations MUST support buffering at least 4096 bytes of data */ #define NGX_QUIC_MAX_BUFFERED 65535 @@ -198,7 +199,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, " params_len:%ui", client_params_len); if (client_params_len == 0) { - /* quic-tls 8.2 */ + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); qc->error_reason = "missing transport parameters"; @@ -428,7 +429,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) return NGX_ERROR; } - /* 12.4 Frames and frame types, figure 8 */ frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; ngx_quic_queue_frame(qc, frame); @@ -440,8 +440,9 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) } /* + * RFC 9001, 9.5. Header Protection Timing Side Channels + * * Generating next keys before a key update is received. - * See quic-tls 9.4 Header Protection Timing Side-Channels. */ if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) { @@ -449,8 +450,10 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) } /* - * 4.10.2 An endpoint MUST discard its handshake keys - * when the TLS handshake is confirmed + * 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, ssl_encryption_handshake); diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index c6f02a37f..24ccdea03 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -296,12 +296,11 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) } /* - * 2.1. Stream Types and Identifiers + * RFC 9000, 2.1. Stream Types and Identifiers * - * Within each type, streams are created with numerically increasing - * stream IDs. A stream ID that is used out of order results in all - * streams of that type with lower-numbered stream IDs also being - * opened. + * successive streams of each type are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being opened. */ for ( /* void */ ; min_id < id; min_id += 0x04) { diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index c13155c55..894595fbc 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1127,7 +1127,11 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) { uint8_t ptype; - /* frame permissions per packet: 4 bits: IH01: 12.4, Table 3 */ + /* + * RFC 9000, 12.4. Frames and Frame Types: Table 3 + * + * Frame permissions per packet: 4 bits: IH01 + */ static uint8_t ngx_quic_frame_masks[] = { /* PADDING */ 0xF, /* PING */ 0xF, @@ -1242,9 +1246,9 @@ ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) { /* - * QUIC-recovery, section 2: + * RFC 9002, 2. Conventions and Definitions * - * Ack-eliciting Frames: All frames other than ACK, PADDING, and + * Ack-eliciting frames: All frames other than ACK, PADDING, and * CONNECTION_CLOSE are considered ack-eliciting. */ f->need_ack = 1; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 9fb621721..b35ba1839 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -12,8 +12,12 @@ #include -/* QUIC flags in first byte, see quic-transport 17.2 and 17.3 */ - +/* + * RFC 9000, 17.2. Long Header Packets + * 17.3. Short Header Packets + * + * QUIC flags in first byte + */ #define NGX_QUIC_PKT_LONG 0x80 /* header form */ #define NGX_QUIC_PKT_FIXED_BIT 0x40 #define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ @@ -85,7 +89,7 @@ #define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE -/* 22.4. QUIC Transport Error Codes Registry */ +/* 22.5. QUIC Transport Error Codes Registry */ /* Keep in sync with ngx_quic_errors[] */ #define NGX_QUIC_ERR_NO_ERROR 0x00 #define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 @@ -111,7 +115,7 @@ #define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) -/* Transport parameters */ +/* 22.3. QUIC Transport Parameters Registry */ #define NGX_QUIC_TP_ORIGINAL_DCID 0x00 #define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 #define NGX_QUIC_TP_SR_TOKEN 0x02 -- cgit v1.2.3 From b5e4f1f4f0e82848bf2fb2e431365b8376dc5f31 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Jun 2021 17:53:18 +0300 Subject: QUIC: consistent use of 5-byte buffers for header protection. The output buffer is now also of 5 bytes. Header protection uses stream ciphers, which don't produce extra output nor PKCS padding. --- src/event/quic/ngx_event_quic_protection.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 82db65921..8db4a6b0d 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -10,7 +10,10 @@ #include +/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ #define NGX_QUIC_IV_LEN 12 +/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ +#define NGX_QUIC_HP_LEN 5 #define NGX_AES_128_GCM_SHA256 0x1301 #define NGX_AES_256_GCM_SHA384 0x1302 @@ -627,15 +630,15 @@ ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, { int outlen; EVP_CIPHER_CTX *ctx; - u_char zero[5] = {0}; + u_char zero[NGX_QUIC_HP_LEN] = {0}; #ifdef OPENSSL_IS_BORINGSSL - uint32_t counter; + uint32_t cnt; - ngx_memcpy(&counter, in, sizeof(uint32_t)); + ngx_memcpy(&cnt, in, sizeof(uint32_t)); if (cipher == (const EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { - CRYPTO_chacha_20(out, zero, 5, s->hp.data, &in[4], counter); + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); return NGX_OK; } #endif @@ -650,12 +653,12 @@ ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, goto failed; } - if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, 5)) { + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); goto failed; } - if (!EVP_EncryptFinal_ex(ctx, out + 5, &outlen)) { + if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); goto failed; } @@ -857,7 +860,7 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_uint_t i; ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; - u_char nonce[12], mask[16]; + u_char nonce[12], mask[NGX_QUIC_HP_LEN]; out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; @@ -1084,7 +1087,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ngx_str_t in, ad; ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; - uint8_t mask[16], nonce[12]; + uint8_t nonce[12], mask[NGX_QUIC_HP_LEN]; if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) { -- cgit v1.2.3 From 4e741d638f70f07d3e63a6ff34e3058180c9cf56 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Jun 2021 17:54:21 +0300 Subject: QUIC: consistent use of 12-byte buffers in nonce computation. All supported cipher suites produce 96-bit IV (RFC 5116, 5.1, RFC 8439, 2.3). This eliminates a few magic numbers and run-time overhead. --- src/event/quic/ngx_event_quic_protection.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 8db4a6b0d..156bfa3cf 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -204,8 +204,8 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, client->hp.len = EVP_CIPHER_key_length(cipher); server->hp.len = EVP_CIPHER_key_length(cipher); - client->iv.len = EVP_CIPHER_iv_length(cipher); - server->iv.len = EVP_CIPHER_iv_length(cipher); + client->iv.len = NGX_QUIC_IV_LEN; + server->iv.len = NGX_QUIC_IV_LEN; struct { ngx_str_t label; @@ -793,12 +793,12 @@ ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) next->client.secret.len = current->client.secret.len; next->client.key.len = current->client.key.len; - next->client.iv.len = current->client.iv.len; + next->client.iv.len = NGX_QUIC_IV_LEN; next->client.hp = current->client.hp; next->server.secret.len = current->server.secret.len; next->server.key.len = current->server.key.len; - next->server.iv.len = current->server.iv.len; + next->server.iv.len = NGX_QUIC_IV_LEN; next->server.hp = current->server.hp; struct { @@ -860,7 +860,7 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_uint_t i; ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; - u_char nonce[12], mask[NGX_QUIC_HP_LEN]; + u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; @@ -924,9 +924,9 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; static u_char key29[16] = "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; - static u_char nonce[12] = + static u_char nonce[NGX_QUIC_IV_LEN] = "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; - static u_char nonce29[12] = + static u_char nonce29[NGX_QUIC_IV_LEN] = "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; static ngx_str_t in = ngx_string(""); @@ -947,7 +947,7 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) secret.key.len = sizeof(key); secret.key.data = (pkt->version & 0xff000000) ? key29 : key; - secret.iv.len = sizeof(nonce); + secret.iv.len = NGX_QUIC_IV_LEN; if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, (pkt->version & 0xff000000) ? nonce29 : nonce, @@ -1087,7 +1087,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) ngx_str_t in, ad; ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; - uint8_t nonce[12], mask[NGX_QUIC_HP_LEN]; + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR) { -- cgit v1.2.3 From cfbd3c709707f8243440ed316a83120591bb0e85 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Jun 2021 17:55:57 +0300 Subject: QUIC: optimized initial secrets key length computation. AES-128 key length is known in compile time. --- src/event/quic/ngx_event_quic_protection.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 156bfa3cf..5bb81c87c 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -15,6 +15,8 @@ /* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ #define NGX_QUIC_HP_LEN 5 +#define NGX_QUIC_AES_128_KEY_LEN 16 + #define NGX_AES_128_GCM_SHA256 0x1301 #define NGX_AES_256_GCM_SHA384 0x1302 #define NGX_CHACHA20_POLY1305_SHA256 0x1303 @@ -150,7 +152,6 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, uint8_t is[SHA256_DIGEST_LENGTH]; ngx_uint_t i; const EVP_MD *digest; - const EVP_CIPHER *cipher; ngx_quic_secret_t *client, *server; static const uint8_t salt[20] = @@ -170,7 +171,6 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, * for HKDF when deriving initial secrets and keys is SHA-256. */ - cipher = EVP_aes_128_gcm(); digest = EVP_sha256(); is_len = SHA256_DIGEST_LENGTH; @@ -198,11 +198,11 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, client->secret.len = SHA256_DIGEST_LENGTH; server->secret.len = SHA256_DIGEST_LENGTH; - client->key.len = EVP_CIPHER_key_length(cipher); - server->key.len = EVP_CIPHER_key_length(cipher); + client->key.len = NGX_QUIC_AES_128_KEY_LEN; + server->key.len = NGX_QUIC_AES_128_KEY_LEN; - client->hp.len = EVP_CIPHER_key_length(cipher); - server->hp.len = EVP_CIPHER_key_length(cipher); + client->hp.len = NGX_QUIC_AES_128_KEY_LEN; + server->hp.len = NGX_QUIC_AES_128_KEY_LEN; client->iv.len = NGX_QUIC_IV_LEN; server->iv.len = NGX_QUIC_IV_LEN; -- cgit v1.2.3 From f997461f239799de256b0fd44de907cf5d0c3c39 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Jun 2021 18:03:33 +0300 Subject: QUIC: using compile time block/iv length for tokens. Reference values can be found in RFC 3602, 2.1, 2.4. --- src/event/quic/ngx_event_quic_tokens.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index 32026ae51..4387a4495 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -14,6 +14,10 @@ #define NGX_QUIC_MAX_TOKEN_SIZE 64 /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ +/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */ +#define NGX_QUIC_AES_256_CBC_IV_LEN 16 +#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16 + static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, ngx_uint_t no_port, u_char buf[20]); @@ -76,9 +80,9 @@ ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, len = p - in; cipher = EVP_aes_256_cbc(); - iv_len = EVP_CIPHER_iv_length(cipher); + iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; - token->len = iv_len + len + EVP_CIPHER_block_size(cipher); + token->len = iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE; token->data = ngx_pnalloc(c->pool, token->len); if (token->data == NULL) { return NGX_ERROR; @@ -188,11 +192,11 @@ ngx_quic_validate_token(ngx_connection_t *c, u_char *key, cipher = EVP_aes_256_cbc(); iv = pkt->token.data; - iv_len = EVP_CIPHER_iv_length(cipher); + iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; /* sanity checks */ - if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) { goto garbage; } -- cgit v1.2.3 From b0bffa2bbb6f39230dbc2646aa308bef061825fd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 17 Jun 2021 12:35:38 +0300 Subject: QUIC: compact initial secrets table. --- src/event/quic/ngx_event_quic_protection.c | 38 +++++------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 5bb81c87c..317eb094c 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -212,41 +212,15 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_str_t *key; ngx_str_t *prk; } seq[] = { - /* labels per RFC 9001, 5.1. Packet Protection Keys */ { ngx_string("tls13 client in"), &client->secret, &iss }, - { - ngx_string("tls13 quic key"), - &client->key, - &client->secret, - }, - { - ngx_string("tls13 quic iv"), - &client->iv, - &client->secret, - }, - { - ngx_string("tls13 quic hp"), - &client->hp, - &client->secret, - }, + { ngx_string("tls13 quic key"), &client->key, &client->secret }, + { ngx_string("tls13 quic iv"), &client->iv, &client->secret }, + { ngx_string("tls13 quic hp"), &client->hp, &client->secret }, { ngx_string("tls13 server in"), &server->secret, &iss }, - { - ngx_string("tls13 quic key"), - &server->key, - &server->secret, - }, - { - ngx_string("tls13 quic iv"), - &server->iv, - &server->secret, - }, - { - ngx_string("tls13 quic hp"), - &server->hp, - &server->secret, - }, - + { ngx_string("tls13 quic key"), &server->key, &server->secret }, + { ngx_string("tls13 quic iv"), &server->iv, &server->secret }, + { ngx_string("tls13 quic hp"), &server->hp, &server->secret }, }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { -- cgit v1.2.3 From e1c2a97b923ad673364e85d97d717f1bfcf5df24 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 21 Jun 2021 12:47:46 +0300 Subject: QUIC: fixed double memzero of new frames in ngx_quic_alloc_frame(). --- src/event/quic/ngx_event_quic_frames.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 3177fa992..52b5c624e 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -39,7 +39,7 @@ ngx_quic_alloc_frame(ngx_connection_t *c) #endif } else { - frame = ngx_pcalloc(c->pool, sizeof(ngx_quic_frame_t)); + frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return NULL; } -- cgit v1.2.3 From 024df8da684c9d0ce55f9ea8e74bdf61554e3755 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 25 Jun 2021 12:41:58 +0300 Subject: README: updated path after moving QUIC sources. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index f88cc3cc9..e8b5a428b 100644 --- a/README +++ b/README @@ -227,7 +227,7 @@ Example configuration: be easily filtered out. + If you want to investigate deeper, you may want to enable - additional debugging in src/event/ngx_event_quic_connection.h: + additional debugging in src/event/quic/ngx_event_quic_connection.h: #define NGX_QUIC_DEBUG_PACKETS #define NGX_QUIC_DEBUG_FRAMES -- cgit v1.2.3 From 8f8f4840047aae94e3a69afb5a7541e13b3f66bf Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 23 Jun 2021 13:22:00 +0300 Subject: QUIC: fixed client certificates verification in stream. The stream session requires 'ssl' flag to be set in order to perform certificate verification. --- src/stream/ngx_stream_handler.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index c5b2e54a2..f9030335f 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -146,6 +146,10 @@ ngx_stream_init_connection(ngx_connection_t *c) s->ssl = addr_conf->ssl; #endif +#if (NGX_STREAM_QUIC) + s->ssl |= addr_conf->quic; +#endif + if (c->buffer) { s->received += c->buffer->last - c->buffer->pos; } -- cgit v1.2.3 From d54d551e2a880c7a7691a83d212eb707eb0f82ba Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 30 Jun 2021 13:47:38 +0300 Subject: QUIC: consider max_ack_delay=16384 invalid. As per RFC 9000: Values of 2^14 or greater are invalid. --- src/event/quic/ngx_event_quic.c | 2 +- src/http/modules/ngx_http_quic_module.c | 2 +- src/stream/ngx_stream_quic_module.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index f0dd943a6..0d61be837 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -176,7 +176,7 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) return NGX_ERROR; } - if (ctp->max_ack_delay > 16384) { + if (ctp->max_ack_delay >= 16384) { qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; qc->error_reason = "invalid max_ack_delay"; diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 2354dfd8b..d933dd1f9 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -394,7 +394,7 @@ ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) { ngx_msec_t *sp = data; - if (*sp > 16384) { + if (*sp >= 16384) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"quic_max_ack_delay\" must be less than 16384"); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 6567b16cf..01caa9555 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -354,7 +354,7 @@ ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) { ngx_msec_t *sp = data; - if (*sp > 16384) { + if (*sp >= 16384) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"quic_max_ack_delay\" must be less than 16384"); -- cgit v1.2.3 From a85084fea109c019d1ad7466ed063afa7961acba Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Jul 2021 15:37:53 +0300 Subject: HTTP/3: quic-qpack term updates. Renamed header -> field per quic-qpack naming convention, in particular: - Header Field -> Field Line - Header Block -> (Encoded) Field Section - Without Name Reference -> With Literal Name - Header Acknowledgement -> Section Acknowledgment --- src/http/v3/ngx_http_v3_encode.c | 22 ++-- src/http/v3/ngx_http_v3_encode.h | 12 +- src/http/v3/ngx_http_v3_filter_module.c | 134 ++++++++++----------- src/http/v3/ngx_http_v3_parse.c | 200 ++++++++++++++++---------------- src/http/v3/ngx_http_v3_parse.h | 14 +-- src/http/v3/ngx_http_v3_request.c | 4 +- src/http/v3/ngx_http_v3_streams.c | 4 +- src/http/v3/ngx_http_v3_streams.h | 2 +- src/http/v3/ngx_http_v3_tables.c | 66 +++++------ src/http/v3/ngx_http_v3_tables.h | 6 +- 10 files changed, 232 insertions(+), 232 deletions(-) diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c index b7bc3dd7c..2740ccffa 100644 --- a/src/http/v3/ngx_http_v3_encode.c +++ b/src/http/v3/ngx_http_v3_encode.c @@ -100,7 +100,7 @@ ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) uintptr_t -ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count, +ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base) { if (p == NULL) { @@ -119,9 +119,9 @@ ngx_http_v3_encode_header_block_prefix(u_char *p, ngx_uint_t insert_count, uintptr_t -ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) { - /* Indexed Header Field */ + /* Indexed Field Line */ if (p == NULL) { return ngx_http_v3_encode_prefix_int(NULL, index, 6); @@ -134,10 +134,10 @@ ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) uintptr_t -ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, +ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, u_char *data, size_t len) { - /* Literal Header Field With Name Reference */ + /* Literal Field Line With Name Reference */ if (p == NULL) { return ngx_http_v3_encode_prefix_int(NULL, index, 4) @@ -160,9 +160,9 @@ ngx_http_v3_encode_header_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, uintptr_t -ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) { - /* Literal Header Field Without Name Reference */ + /* Literal Field Line With Literal Name */ if (p == NULL) { return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) @@ -187,9 +187,9 @@ ngx_http_v3_encode_header_l(u_char *p, ngx_str_t *name, ngx_str_t *value) uintptr_t -ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index) +ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index) { - /* Indexed Header Field With Post-Base Index */ + /* Indexed Field Line With Post-Base Index */ if (p == NULL) { return ngx_http_v3_encode_prefix_int(NULL, index, 4); @@ -202,10 +202,10 @@ ngx_http_v3_encode_header_pbi(u_char *p, ngx_uint_t index) uintptr_t -ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, u_char *data, +ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, size_t len) { - /* Literal Header Field With Post-Base Name Reference */ + /* Literal Field Line With Post-Base Name Reference */ if (p == NULL) { return ngx_http_v3_encode_prefix_int(NULL, index, 3) diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h index 583c5675b..fca376da5 100644 --- a/src/http/v3/ngx_http_v3_encode.h +++ b/src/http/v3/ngx_http_v3_encode.h @@ -18,16 +18,16 @@ uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix); -uintptr_t ngx_http_v3_encode_header_block_prefix(u_char *p, +uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); -uintptr_t ngx_http_v3_encode_header_ri(u_char *p, ngx_uint_t dynamic, +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_header_lri(u_char *p, ngx_uint_t dynamic, +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_header_l(u_char *p, ngx_str_t *name, +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_header_pbi(u_char *p, ngx_uint_t index); -uintptr_t ngx_http_v3_encode_header_lpbi(u_char *p, ngx_uint_t index, +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 db40e3737..52db65736 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -159,16 +159,16 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) } } - len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200); + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200, - NULL, 3); + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -184,14 +184,14 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SERVER, - NULL, n); + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); } if (r->headers_out.date == NULL) { - len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, - NULL, ngx_cached_http_time.len); + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { @@ -203,19 +203,19 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, NULL, n); } if (r->headers_out.content_length == NULL) { if (r->headers_out.content_length_n > 0) { - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, NULL, NGX_OFF_T_LEN); } else if (r->headers_out.content_length_n == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, + len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); } } @@ -223,7 +223,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } @@ -267,7 +267,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n += sizeof(":65535") - 1; } - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_LOCATION, NULL, n); } else { @@ -278,7 +278,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) #if (NGX_HTTP_GZIP) if (r->gzip_vary) { if (clcf->gzip_vary) { - len += ngx_http_v3_encode_header_ri(NULL, 0, + len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } else { @@ -306,8 +306,8 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) continue; } - len += ngx_http_v3_encode_header_l(NULL, &header[i].key, - &header[i].value); + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); @@ -317,15 +317,15 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) return NGX_ERROR; } - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_STATUS_200); } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_STATUS_200, NULL, 3); b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); @@ -345,13 +345,13 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_SERVER, p, n); } if (r->headers_out.date == NULL) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_DATE, ngx_cached_http_time.data, ngx_cached_http_time.len); @@ -366,7 +366,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, NULL, n); @@ -394,7 +394,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); n = p - b->last; - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, NULL, n); @@ -402,7 +402,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) r->headers_out.content_length_n); } else if (r->headers_out.content_length_n == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); } } @@ -410,7 +410,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); @@ -425,7 +425,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n += ngx_sprintf(b->last, ":%ui", port) - b->last; } - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LOCATION, NULL, n); @@ -449,7 +449,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } #endif @@ -473,9 +473,9 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) continue; } - b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, - &header[i].key, - &header[i].value); + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); } if (r->header_only) { @@ -1065,55 +1065,55 @@ ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, len = ngx_http_v3_encode_varlen_int(NULL, push_id); - len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - NULL, r->headers_in.server.len); + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, r->headers_in.server.len); if (path->len == 1 && path->data[0] == '/') { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - NULL, path->len); + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, path->len); } if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); } else if (r->schema.len == 4 && ngx_strncmp(r->schema.data, "http", 4) == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - NULL, r->schema.len); + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + NULL, r->schema.len); } if (r->headers_in.accept_encoding) { - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, r->headers_in.accept_encoding->value.len); } if (r->headers_in.accept_language) { - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, r->headers_in.accept_language->value.len); } if (r->headers_in.user_agent) { - len += ngx_http_v3_encode_header_lri(NULL, 0, + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_USER_AGENT, NULL, r->headers_in.user_agent->value.len); } @@ -1125,59 +1125,59 @@ ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_METHOD_GET); - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_AUTHORITY, r->headers_in.server.data, r->headers_in.server.len); if (path->len == 1 && path->data[0] == '/') { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_PATH_ROOT); } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_PATH_ROOT, path->data, path->len); } if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_SCHEME_HTTPS); } else if (r->schema.len == 4 && ngx_strncmp(r->schema.data, "http", 4) == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_SCHEME_HTTP); } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_SCHEME_HTTP, r->schema.data, r->schema.len); } if (r->headers_in.accept_encoding) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, r->headers_in.accept_encoding->value.data, r->headers_in.accept_encoding->value.len); } if (r->headers_in.accept_language) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, r->headers_in.accept_language->value.data, r->headers_in.accept_language->value.len); } if (r->headers_in.user_agent) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_USER_AGENT, r->headers_in.user_agent->value.data, r->headers_in.user_agent->value.len); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 5951dff10..c5fc24482 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -19,22 +19,22 @@ static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, 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, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, - ngx_http_v3_parse_header_block_prefix_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_rep(ngx_connection_t *c, - ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, u_char ch); static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_ri(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_lri(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_l(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_pbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, u_char ch); @@ -43,10 +43,10 @@ static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_inr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); -static ngx_int_t ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); +static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch); static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, u_char ch); @@ -201,7 +201,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, sw_skip, sw_prefix, sw_verify, - sw_header_rep, + sw_field_rep, sw_done }; @@ -277,7 +277,7 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return NGX_HTTP_V3_ERR_FRAME_ERROR; } - rc = ngx_http_v3_parse_header_block_prefix(c, &st->prefix, ch); + rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, ch); if (rc != NGX_DONE) { return rc; } @@ -292,14 +292,14 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, return rc; } - st->state = sw_header_rep; + st->state = sw_field_rep; /* fall through */ - case sw_header_rep: + case sw_field_rep: - rc = ngx_http_v3_parse_header_rep(c, &st->header_rep, st->prefix.base, - ch); + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, + ch); if (--st->length == 0 && rc == NGX_AGAIN) { return NGX_HTTP_V3_ERR_FRAME_ERROR; @@ -323,7 +323,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_header(c, c->quic->id) != NGX_OK) { + if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { return NGX_ERROR; } } @@ -334,8 +334,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, - ngx_http_v3_parse_header_block_prefix_t *st, u_char ch) +ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, u_char ch) { ngx_int_t rc; enum { @@ -350,7 +350,7 @@ ngx_http_v3_parse_header_block_prefix(ngx_connection_t *c, case sw_start: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header block prefix"); + "http3 parse field section prefix"); st->state = sw_req_insert_count; @@ -401,7 +401,7 @@ done: } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header block prefix done " + "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); @@ -411,75 +411,75 @@ done: static ngx_int_t -ngx_http_v3_parse_header_rep(ngx_connection_t *c, - ngx_http_v3_parse_header_rep_t *st, ngx_uint_t base, u_char ch) +ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, u_char ch) { ngx_int_t rc; enum { sw_start = 0, - sw_header_ri, - sw_header_lri, - sw_header_l, - sw_header_pbi, - sw_header_lpbi + sw_field_ri, + sw_field_lri, + sw_field_l, + sw_field_pbi, + sw_field_lpbi }; if (st->state == sw_start) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header representation"); + "http3 parse field representation"); - ngx_memzero(&st->header, sizeof(ngx_http_v3_parse_header_t)); + ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); - st->header.base = base; + st->field.base = base; if (ch & 0x80) { - /* Indexed Header Field */ + /* Indexed Field Line */ - st->state = sw_header_ri; + st->state = sw_field_ri; } else if (ch & 0x40) { - /* Literal Header Field With Name Reference */ + /* Literal Field Line With Name Reference */ - st->state = sw_header_lri; + st->state = sw_field_lri; } else if (ch & 0x20) { - /* Literal Header Field Without Name Reference */ + /* Literal Field Line With Literal Name */ - st->state = sw_header_l; + st->state = sw_field_l; } else if (ch & 0x10) { - /* Indexed Header Field With Post-Base Index */ + /* Indexed Field Line With Post-Base Index */ - st->state = sw_header_pbi; + st->state = sw_field_pbi; } else { - /* Literal Header Field With Post-Base Name Reference */ + /* Literal Field Line With Post-Base Name Reference */ - st->state = sw_header_lpbi; + st->state = sw_field_lpbi; } } switch (st->state) { - case sw_header_ri: - rc = ngx_http_v3_parse_header_ri(c, &st->header, ch); + case sw_field_ri: + rc = ngx_http_v3_parse_field_ri(c, &st->field, ch); break; - case sw_header_lri: - rc = ngx_http_v3_parse_header_lri(c, &st->header, ch); + case sw_field_lri: + rc = ngx_http_v3_parse_field_lri(c, &st->field, ch); break; - case sw_header_l: - rc = ngx_http_v3_parse_header_l(c, &st->header, ch); + case sw_field_l: + rc = ngx_http_v3_parse_field_l(c, &st->field, ch); break; - case sw_header_pbi: - rc = ngx_http_v3_parse_header_pbi(c, &st->header, ch); + case sw_field_pbi: + rc = ngx_http_v3_parse_field_pbi(c, &st->field, ch); break; - case sw_header_lpbi: - rc = ngx_http_v3_parse_header_lpbi(c, &st->header, ch); + case sw_field_lpbi: + rc = ngx_http_v3_parse_field_lpbi(c, &st->field, ch); break; default: @@ -491,7 +491,7 @@ ngx_http_v3_parse_header_rep(ngx_connection_t *c, } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header representation done"); + "http3 parse field representation done"); st->state = sw_start; return NGX_DONE; @@ -523,7 +523,7 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, if (n > cscf->large_client_header_buffers.size) { ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent too large header field"); + "client sent too large field line"); return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; } @@ -578,7 +578,7 @@ done: static ngx_int_t -ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, +ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; @@ -591,7 +591,7 @@ ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header ri"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field ri"); st->dynamic = (ch & 0x40) ? 0 : 1; st->state = sw_index; @@ -614,7 +614,7 @@ ngx_http_v3_parse_header_ri(ngx_connection_t *c, ngx_http_v3_parse_header_t *st, done: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header ri done %s%ui]", + "http3 parse field ri done %s%ui]", st->dynamic ? "dynamic[-" : "static[", st->index); if (st->dynamic) { @@ -633,8 +633,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_lri(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -649,7 +649,7 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header lri"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field lri"); st->dynamic = (ch & 0x10) ? 0 : 1; st->state = sw_index; @@ -705,7 +705,7 @@ ngx_http_v3_parse_header_lri(ngx_connection_t *c, done: ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header lri done %s%ui] \"%V\"", + "http3 parse field lri done %s%ui] \"%V\"", st->dynamic ? "dynamic[-" : "static[", st->index, &st->value); @@ -724,8 +724,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_l(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -741,7 +741,7 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header l"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); st->literal.huffman = (ch & 0x08) ? 1 : 0; st->state = sw_name_len; @@ -812,7 +812,7 @@ ngx_http_v3_parse_header_l(ngx_connection_t *c, done: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header l done \"%V\" \"%V\"", + "http3 parse field l done \"%V\" \"%V\"", &st->name, &st->value); st->state = sw_start; @@ -821,8 +821,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_pbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -834,7 +834,7 @@ ngx_http_v3_parse_header_pbi(ngx_connection_t *c, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header pbi"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field pbi"); st->state = sw_index; @@ -856,7 +856,7 @@ ngx_http_v3_parse_header_pbi(ngx_connection_t *c, done: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header pbi done dynamic[+%ui]", st->index); + "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); @@ -870,8 +870,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -887,7 +887,7 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, case sw_start: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header lpbi"); + "http3 parse field lpbi"); st->state = sw_index; @@ -942,7 +942,7 @@ ngx_http_v3_parse_header_lpbi(ngx_connection_t *c, done: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header lpbi done dynamic[+%ui] \"%V\"", + "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); @@ -1260,7 +1260,7 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, enum { sw_start = 0, sw_inr, - sw_iwnr, + sw_iln, sw_capacity, sw_duplicate }; @@ -1276,12 +1276,12 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, st->state = sw_inr; } else if (ch & 0x40) { - /* Insert Without Name Reference */ + /* Insert With Literal Name */ - st->state = sw_iwnr; + st->state = sw_iln; } else if (ch & 0x20) { - /* Set Dynamic Table Capacity */ + /* Set Dynamic Table Capacity */ st->state = sw_capacity; @@ -1296,16 +1296,16 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, case sw_inr: - rc = ngx_http_v3_parse_header_inr(c, &st->header, ch); + rc = ngx_http_v3_parse_field_inr(c, &st->field, ch); if (rc != NGX_DONE) { return rc; } goto done; - case sw_iwnr: + case sw_iln: - rc = ngx_http_v3_parse_header_iwnr(c, &st->header, ch); + rc = ngx_http_v3_parse_field_iln(c, &st->field, ch); if (rc != NGX_DONE) { return rc; } @@ -1354,8 +1354,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_inr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -1370,7 +1370,7 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header inr"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field inr"); st->dynamic = (ch & 0x40) ? 0 : 1; st->state = sw_name_index; @@ -1427,7 +1427,7 @@ ngx_http_v3_parse_header_inr(ngx_connection_t *c, done: ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header inr done %s[%ui] \"%V\"", + "http3 parse field inr done %s[%ui] \"%V\"", st->dynamic ? "dynamic" : "static", st->index, &st->value); @@ -1442,8 +1442,8 @@ done: static ngx_int_t -ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, - ngx_http_v3_parse_header_t *st, u_char ch) +ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, u_char ch) { ngx_int_t rc; enum { @@ -1460,7 +1460,7 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, case sw_start: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header iwnr"); + "http3 parse field iln"); st->literal.huffman = (ch & 0x20) ? 1 : 0; st->state = sw_name_len; @@ -1532,7 +1532,7 @@ ngx_http_v3_parse_header_iwnr(ngx_connection_t *c, done: ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header iwnr done \"%V\":\"%V\"", + "http3 parse field iln done \"%V\":\"%V\"", &st->name, &st->value); rc = ngx_http_v3_insert(c, &st->name, &st->value); @@ -1552,7 +1552,7 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, ngx_int_t rc; enum { sw_start = 0, - sw_ack_header, + sw_ack_section, sw_cancel_stream, sw_inc_insert_count }; @@ -1563,9 +1563,9 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, "http3 parse decoder instruction"); if (ch & 0x80) { - /* Header Acknowledgement */ + /* Section Acknowledgment */ - st->state = sw_ack_header; + st->state = sw_ack_section; } else if (ch & 0x40) { /* Stream Cancellation */ @@ -1581,14 +1581,14 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, switch (st->state) { - case sw_ack_header: + case sw_ack_section: rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); if (rc != NGX_DONE) { return rc; } - rc = ngx_http_v3_ack_header(c, st->pint.value); + rc = ngx_http_v3_ack_section(c, st->pint.value); if (rc != NGX_OK) { return rc; } diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index c187b7bc2..5ca3a410f 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -41,7 +41,7 @@ typedef struct { ngx_uint_t sign; ngx_uint_t base; ngx_http_v3_parse_prefix_int_t pint; -} ngx_http_v3_parse_header_block_prefix_t; +} ngx_http_v3_parse_field_section_prefix_t; typedef struct { @@ -65,13 +65,13 @@ typedef struct { ngx_http_v3_parse_prefix_int_t pint; ngx_http_v3_parse_literal_t literal; -} ngx_http_v3_parse_header_t; +} ngx_http_v3_parse_field_t; typedef struct { ngx_uint_t state; - ngx_http_v3_parse_header_t header; -} ngx_http_v3_parse_header_rep_t; + ngx_http_v3_parse_field_t field; +} ngx_http_v3_parse_field_rep_t; typedef struct { @@ -79,14 +79,14 @@ typedef struct { ngx_uint_t type; ngx_uint_t length; ngx_http_v3_parse_varlen_int_t vlint; - ngx_http_v3_parse_header_block_prefix_t prefix; - ngx_http_v3_parse_header_rep_t header_rep; + ngx_http_v3_parse_field_section_prefix_t prefix; + ngx_http_v3_parse_field_rep_t field_rep; } ngx_http_v3_parse_headers_t; typedef struct { ngx_uint_t state; - ngx_http_v3_parse_header_t header; + ngx_http_v3_parse_field_t field; ngx_http_v3_parse_prefix_int_t pint; } ngx_http_v3_parse_encoder_t; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 9d7ca952d..5fc6e233b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -304,8 +304,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) /* rc == NGX_OK || rc == NGX_DONE */ - if (ngx_http_v3_process_header(r, &st->header_rep.header.name, - &st->header_rep.header.value) + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) != NGX_OK) { break; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 8b2047f43..fa39be78a 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -603,14 +603,14 @@ ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_int_t -ngx_http_v3_send_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) +ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) { u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; size_t n; ngx_connection_t *dc; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client ack header %ui", stream_id); + "http3 client ack section %ui", stream_id); dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h index a2126a7f8..4f7290e4c 100644 --- a/src/http/v3/ngx_http_v3_streams.h +++ b/src/http/v3/ngx_http_v3_streams.h @@ -34,7 +34,7 @@ ngx_int_t ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, ngx_int_t ngx_http_v3_send_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); ngx_int_t ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_send_ack_header(ngx_connection_t *c, +ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 8f4e28edd..348088774 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -15,7 +15,7 @@ static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); static void ngx_http_v3_unblock(void *data); -static ngx_int_t ngx_http_v3_new_header(ngx_connection_t *c); +static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); typedef struct { @@ -25,7 +25,7 @@ typedef struct { } ngx_http_v3_block_t; -static ngx_http_v3_header_t ngx_http_v3_static_table[] = { +static ngx_http_v3_field_t ngx_http_v3_static_table[] = { { ngx_string(":authority"), ngx_string("") }, { ngx_string(":path"), ngx_string("/") }, @@ -198,7 +198,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) { u_char *p; size_t size; - ngx_http_v3_header_t *h; + ngx_http_v3_field_t *field; ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -215,21 +215,21 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) "http3 insert [%ui] \"%V\":\"%V\", size:%uz", dt->base + dt->nelts, name, value, size); - p = ngx_alloc(sizeof(ngx_http_v3_header_t) + name->len + value->len, + p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, c->log); if (p == NULL) { return NGX_ERROR; } - h = (ngx_http_v3_header_t *) p; + field = (ngx_http_v3_field_t *) p; - h->name.data = p + sizeof(ngx_http_v3_header_t); - h->name.len = name->len; - h->value.data = ngx_cpymem(h->name.data, name->data, name->len); - h->value.len = value->len; - ngx_memcpy(h->value.data, value->data, value->len); + field->name.data = p + sizeof(ngx_http_v3_field_t); + field->name.len = name->len; + field->value.data = ngx_cpymem(field->name.data, name->data, name->len); + field->value.len = value->len; + ngx_memcpy(field->value.data, value->data, value->len); - dt->elts[dt->nelts++] = h; + dt->elts[dt->nelts++] = field; dt->size += size; /* TODO increment can be sent less often */ @@ -238,7 +238,7 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) return NGX_ERROR; } - if (ngx_http_v3_new_header(c) != NGX_OK) { + if (ngx_http_v3_new_entry(c) != NGX_OK) { return NGX_ERROR; } @@ -250,7 +250,7 @@ ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) { ngx_uint_t max, prev_max; - ngx_http_v3_header_t **elts; + 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; @@ -323,7 +323,7 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) { size_t size, target; ngx_uint_t n; - ngx_http_v3_header_t *h; + ngx_http_v3_field_t *field; ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -340,14 +340,14 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) n = 0; while (dt->size > target) { - h = dt->elts[n++]; - size = ngx_http_v3_table_entry_size(&h->name, &h->value); + field = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&field->name, &field->value); ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 evict [%ui] \"%V\":\"%V\" size:%uz", - dt->base, &h->name, &h->value, size); + dt->base, &field->name, &field->value, size); - ngx_free(h); + ngx_free(field); dt->size -= size; } @@ -388,10 +388,10 @@ ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) ngx_int_t -ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id) +ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 ack header %ui", stream_id); + "http3 ack section %ui", stream_id); /* we do not use dynamic tables */ @@ -415,8 +415,8 @@ ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) { - ngx_uint_t nelts; - ngx_http_v3_header_t *h; + ngx_uint_t nelts; + ngx_http_v3_field_t *field; nelts = sizeof(ngx_http_v3_static_table) / sizeof(ngx_http_v3_static_table[0]); @@ -428,18 +428,18 @@ ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, return NGX_ERROR; } - h = &ngx_http_v3_static_table[index]; + field = &ngx_http_v3_static_table[index]; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 static[%ui] lookup \"%V\":\"%V\"", - index, &h->name, &h->value); + index, &field->name, &field->value); if (name) { - *name = h->name; + *name = field->name; } if (value) { - *value = h->value; + *value = field->value; } return NGX_OK; @@ -450,7 +450,7 @@ ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) { - ngx_http_v3_header_t *h; + ngx_http_v3_field_t *field; ngx_http_v3_session_t *h3c; ngx_http_v3_dynamic_table_t *dt; @@ -464,18 +464,18 @@ ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, return NGX_ERROR; } - h = dt->elts[index - dt->base]; + field = dt->elts[index - dt->base]; ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dynamic[%ui] lookup \"%V\":\"%V\"", - index, &h->name, &h->value); + index, &field->name, &field->value); if (name) { - *name = h->name; + *name = field->name; } if (value) { - *value = h->value; + *value = field->value; } return NGX_OK; @@ -617,7 +617,7 @@ ngx_http_v3_unblock(void *data) static ngx_int_t -ngx_http_v3_new_header(ngx_connection_t *c) +ngx_http_v3_new_entry(ngx_connection_t *c) { ngx_queue_t *q; ngx_connection_t *bc; @@ -627,7 +627,7 @@ ngx_http_v3_new_header(ngx_connection_t *c) h3c = ngx_http_v3_get_session(c); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 new dynamic header, blocked:%ui", h3c->nblocked); + "http3 new dynamic entry, blocked:%ui", h3c->nblocked); while (!ngx_queue_empty(&h3c->blocked)) { q = ngx_queue_head(&h3c->blocked); diff --git a/src/http/v3/ngx_http_v3_tables.h b/src/http/v3/ngx_http_v3_tables.h index 36589f171..991cc12c9 100644 --- a/src/http/v3/ngx_http_v3_tables.h +++ b/src/http/v3/ngx_http_v3_tables.h @@ -17,11 +17,11 @@ typedef struct { ngx_str_t name; ngx_str_t value; -} ngx_http_v3_header_t; +} ngx_http_v3_field_t; typedef struct { - ngx_http_v3_header_t **elts; + ngx_http_v3_field_t **elts; ngx_uint_t nelts; ngx_uint_t base; size_t size; @@ -36,7 +36,7 @@ ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value); ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_ack_header(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); -- cgit v1.2.3 From 1860eda336fce3fa6a6b1d7c81a904668c7af598 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 5 Jul 2021 13:17:10 +0300 Subject: QUIC: fixed padding calculation. Sometimes, QUIC packets need to be of certain (or minimal) size. This is achieved by adding PADDING frames. It is possible, that adding padding will affect header size, thus forcing us to recalculate padding size once more. --- src/event/quic/ngx_event_quic_output.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 26b046b14..688ae67fe 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -368,6 +368,9 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } if (out.len < pad_len) { + /* compensate for potentially enlarged header in Length bytes */ + pad_len -= ngx_quic_create_header(&pkt, NULL, pad_len, NULL) + - ngx_quic_create_header(&pkt, NULL, out.len, NULL); ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); out.len = pad_len; } -- cgit v1.2.3 From 46aa440c66d51eebc2fd66bfb4a0d1f722982823 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 12 Jul 2021 16:40:57 +0300 Subject: Core: the ngx_event_udp.h header file. --- auto/sources | 3 ++- src/event/ngx_event.h | 28 +--------------------------- src/event/ngx_event_udp.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 src/event/ngx_event_udp.h diff --git a/auto/sources b/auto/sources index 37276aa94..3c7a37e6d 100644 --- a/auto/sources +++ b/auto/sources @@ -89,7 +89,8 @@ EVENT_DEPS="src/event/ngx_event.h \ src/event/ngx_event_timer.h \ src/event/ngx_event_posted.h \ src/event/ngx_event_connect.h \ - src/event/ngx_event_pipe.h" + src/event/ngx_event_pipe.h \ + src/event/ngx_event_udp.h" EVENT_SRCS="src/event/ngx_event.c \ src/event/ngx_event_timer.c \ diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index e023af027..a4a273390 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -167,25 +167,6 @@ struct ngx_event_aio_s { #endif -#if !(NGX_WIN32) - -typedef struct { - ngx_buf_t *buffer; - struct sockaddr *sockaddr; - socklen_t socklen; -} ngx_udp_dgram_t; - - -struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_str_t key; - ngx_udp_dgram_t *dgram; -}; - -#endif - - typedef struct { ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags); @@ -516,14 +497,6 @@ extern ngx_module_t ngx_event_core_module; void ngx_event_accept(ngx_event_t *ev); -#if !(NGX_WIN32) -void ngx_event_recvmsg(ngx_event_t *ev); -void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, - ngx_str_t *key); -#endif -void ngx_delete_udp_connection(void *data); ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle); ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle); u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -553,6 +526,7 @@ ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat); #include #include +#include #if (NGX_WIN32) #include diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h new file mode 100644 index 000000000..fca6bac0f --- /dev/null +++ b/src/event/ngx_event_udp.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_UDP_H_INCLUDED_ +#define _NGX_EVENT_UDP_H_INCLUDED_ + + +#include +#include + + +#if !(NGX_WIN32) + +typedef struct { + ngx_buf_t *buffer; + struct sockaddr *sockaddr; + socklen_t socklen; +} ngx_udp_dgram_t; + + +struct ngx_udp_connection_s { + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_str_t key; + ngx_udp_dgram_t *dgram; +}; + + +void ngx_event_recvmsg(ngx_event_t *ev); +void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, + ngx_str_t *key); + +#endif + +void ngx_delete_udp_connection(void *data); + + +#endif /* _NGX_EVENT_UDP_H_INCLUDED_ */ -- cgit v1.2.3 From 105de9762f840bf97d939a931b47bba9a9bfe226 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 15 Jul 2021 14:21:39 +0300 Subject: Core: made the ngx_sendmsg() function non-static. Additionally, the ngx_init_srcaddr_cmsg() function is introduced which initializes control message with connection local address. The NGX_HAVE_ADDRINFO_CMSG macro is defined when at least one of methods to deal with corresponding control message is available. --- src/event/ngx_event_udp.h | 31 +++++++ src/os/unix/ngx_udp_sendmsg_chain.c | 161 +++++++++++++++++++++--------------- 2 files changed, 124 insertions(+), 68 deletions(-) diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h index fca6bac0f..bd7c973a2 100644 --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -14,6 +14,14 @@ #if !(NGX_WIN32) +#if ((NGX_HAVE_MSGHDR_MSG_CONTROL) \ + && (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR \ + || NGX_HAVE_IP_PKTINFO \ + || (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO))) +#define NGX_HAVE_ADDRINFO_CMSG 1 +#endif + + typedef struct { ngx_buf_t *buffer; struct sockaddr *sockaddr; @@ -29,7 +37,30 @@ struct ngx_udp_connection_s { }; +#if (NGX_HAVE_ADDRINFO_CMSG) + +typedef union { +#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR) + struct in_addr addr; +#endif + +#if (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo pkt; +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo pkt6; +#endif +} ngx_addrinfo_t; + +size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, + struct sockaddr *local_sockaddr); + +#endif + + void ngx_event_recvmsg(ngx_event_t *ev); +ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags); void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index 3d1d6dde4..f13bed9a9 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -12,7 +12,7 @@ static ngx_chain_t *ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log); -static ssize_t ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec); +static ssize_t ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec); ngx_chain_t * @@ -88,7 +88,7 @@ ngx_udp_unix_sendmsg_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) send += vec.size; - n = ngx_sendmsg(c, &vec); + n = ngx_sendmsg_vec(c, &vec); if (n == NGX_ERROR) { return NGX_CHAIN_ERROR; @@ -204,24 +204,13 @@ ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log) static ssize_t -ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec) +ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) { - ssize_t n; - ngx_err_t err; - struct msghdr msg; - -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - -#if (NGX_HAVE_IP_SENDSRCADDR) - u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; -#elif (NGX_HAVE_IP_PKTINFO) - u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif + struct msghdr msg; +#if defined(NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif ngx_memzero(&msg, sizeof(struct msghdr)); @@ -234,88 +223,124 @@ ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec) msg.msg_iov = vec->iovs; msg.msg_iovlen = vec->count; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - +#if defined(NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { -#if (NGX_HAVE_IP_SENDSRCADDR) + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); - if (c->local_sockaddr->sa_family == AF_INET) { - struct cmsghdr *cmsg; - struct in_addr *addr; - struct sockaddr_in *sin; + cmsg = CMSG_FIRSTHDR(&msg); - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_SENDSRCADDR; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + return ngx_sendmsg(c, &msg, 0); +} - sin = (struct sockaddr_in *) c->local_sockaddr; - addr = (struct in_addr *) CMSG_DATA(cmsg); - *addr = sin->sin_addr; - } +#if defined(NGX_HAVE_ADDRINFO_CMSG) +size_t +ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) +{ + size_t len; +#if (NGX_HAVE_IP_SENDSRCADDR) + struct in_addr *addr; + struct sockaddr_in *sin; #elif (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo *pkt; + struct sockaddr_in *sin; +#endif - if (c->local_sockaddr->sa_family == AF_INET) { - struct cmsghdr *cmsg; - struct in_pktinfo *pkt; - struct sockaddr_in *sin; +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo *pkt6; + struct sockaddr_in6 *sin6; +#endif - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); +#if (NGX_HAVE_IP_SENDSRCADDR) || (NGX_HAVE_IP_PKTINFO) - sin = (struct sockaddr_in *) c->local_sockaddr; + if (local_sockaddr->sa_family == AF_INET) { - pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); - ngx_memzero(pkt, sizeof(struct in_pktinfo)); - pkt->ipi_spec_dst = sin->sin_addr; - } + cmsg->cmsg_level = IPPROTO_IP; + +#if (NGX_HAVE_IP_SENDSRCADDR) + + cmsg->cmsg_type = IP_SENDSRCADDR; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + len = CMSG_SPACE(sizeof(struct in_addr)); + + sin = (struct sockaddr_in *) local_sockaddr; + + addr = (struct in_addr *) CMSG_DATA(cmsg); + *addr = sin->sin_addr; + +#elif (NGX_HAVE_IP_PKTINFO) + + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + len = CMSG_SPACE(sizeof(struct in_pktinfo)); + + sin = (struct sockaddr_in *) local_sockaddr; + + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + ngx_memzero(pkt, sizeof(struct in_pktinfo)); + pkt->ipi_spec_dst = sin->sin_addr; + +#endif + return len; + } #endif #if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + if (local_sockaddr->sa_family == AF_INET6) { - if (c->local_sockaddr->sa_family == AF_INET6) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pkt6; - struct sockaddr_in6 *sin6; + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + len = CMSG_SPACE(sizeof(struct in6_pktinfo)); - msg.msg_control = &msg_control6; - msg.msg_controllen = sizeof(msg_control6); + sin6 = (struct sockaddr_in6 *) local_sockaddr; - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); + ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); + pkt6->ipi6_addr = sin6->sin6_addr; - sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + return len; + } +#endif - pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); - ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); - pkt6->ipi6_addr = sin6->sin6_addr; - } + return 0; +} #endif - } + +ssize_t +ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags) +{ + ssize_t n; + ngx_err_t err; +#if (NGX_DEBUG) + size_t size; + ngx_uint_t i; #endif eintr: - n = sendmsg(c->fd, &msg, 0); + n = sendmsg(c->fd, msg, flags); + +#if (NGX_DEBUG) + for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) { + size += msg->msg_iov[i].iov_len; + } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sendmsg: %z of %uz", n, vec->size); + "sendmsg: %z of %uz", n, size); +#endif if (n == -1) { err = ngx_errno; -- cgit v1.2.3 From c0764bc3e91713806875904f9c46bc68815b7bba Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 15 Jul 2021 14:22:00 +0300 Subject: QUIC: added support for segmentation offloading. To improve output performance, UDP segmentation offloading is used if available. If there is a significant amount of data in an output queue and path is verified, QUIC packets are not sent one-by-one, but instead are collected in a buffer, which is then passed to kernel in a single sendmsg call, using UDP GSO. Such method greatly decreases number of system calls and thus system load. --- auto/os/linux | 21 +++ src/event/quic/ngx_event_quic_output.c | 246 +++++++++++++++++++++++++++++++-- src/os/unix/ngx_linux_config.h | 4 + 3 files changed, 261 insertions(+), 10 deletions(-) diff --git a/auto/os/linux b/auto/os/linux index 3f1738c74..3b9c28419 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -281,6 +281,27 @@ if [ $ngx_found = yes ]; then fi +# UDP_SEGMENT socket option is used for segmentation offloading + +ngx_feature="UDP_SEGMENT" +ngx_feature_name="NGX_HAVE_UDP_SEGMENT" +ngx_feature_run=no +ngx_feature_incs="#include + #include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(int); + int val; + getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)" +. auto/feature + +if [ $ngx_found = yes ]; then + UDP_SEGMENT_FOUND=YES + have=NGX_HAVE_UDP_SEGMENT . auto/have +fi + + # ngx_quic_bpf module uses sockhash to select socket from reuseport group, # support appeared in Linux-5.7: # diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 688ae67fe..ce3805c8f 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -17,6 +17,9 @@ #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 +#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */ +#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */ + #define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ #define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ #define NGX_QUIC_RETRY_BUFFER_SIZE 256 @@ -39,6 +42,16 @@ static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock); +static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c, + ngx_quic_socket_t *qsock); +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) +static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c, + ngx_quic_socket_t *qsock); +static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c, + ngx_quic_socket_t *qsock); +static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, + size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); +#endif static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock); @@ -83,24 +96,55 @@ ngx_quic_output(ngx_connection_t *c) static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + size_t in_flight; + ngx_int_t rc; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + if (ngx_quic_allow_segmentation(c, qsock)) { + rc = ngx_quic_create_segments(c, qsock); + } else +#endif + { + rc = ngx_quic_create_datagrams(c, qsock); + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) { off_t max; - size_t len, min, in_flight; + size_t len, min; ssize_t n; u_char *p; ngx_uint_t i, pad; ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; - ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; - c->log->action = "sending frames"; - qc = ngx_quic_get_connection(c); - cg = &qc->congestion; - - in_flight = cg->in_flight; path = qsock->path; @@ -153,16 +197,198 @@ ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) path->sent += len; } - if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { - qc->send_timer_set = 1; - ngx_add_timer(c->read, qc->tp.max_idle_timeout); + return NGX_OK; +} + + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + +static ngx_uint_t +ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + size_t bytes, len; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + if (qsock->path->state != NGX_QUIC_PATH_VALIDATED) { + /* don't even try to be faster on non-validated paths */ + return 0; } + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + bytes = 0; + + len = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_SEGMENT_BUF); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + bytes += f->len; + + if (bytes > len * 3) { + /* require at least ~3 full packets to batch */ + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + size_t len, segsize; + ssize_t n; + u_char *p, *end; + ngx_uint_t nseg; + ngx_quic_send_ctx_t *ctx; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; + + qc = ngx_quic_get_connection(c); + path = qsock->path; + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + segsize = ngx_min(qc->ctp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_SEGMENT_BUF); + p = dst; + end = dst + sizeof(dst); + + nseg = 0; + + for ( ;; ) { + + len = ngx_min(segsize, (size_t) (end - p)); + + if (len) { + + n = ngx_quic_output_packet(c, ctx, p, len, len, qsock); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + nseg++; + + } else { + n = 0; + } + + if (p == dst) { + break; + } + + if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) { + n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr, + path->socklen, segsize); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + path->sent += n; + + p = dst; + nseg = 0; + } + } return NGX_OK; } +static ssize_t +ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen, size_t segment) +{ + size_t clen; + ssize_t n; + uint16_t *valp; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + +#if defined(NGX_HAVE_ADDRINFO_CMSG) + char msg_control[CMSG_SPACE(sizeof(uint16_t)) + + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#else + char msg_control[CMSG_SPACE(sizeof(uint16_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + ngx_memzero(msg_control, sizeof(msg_control)); + + iov.iov_len = len; + iov.iov_base = buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + + cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_level = SOL_UDP; + cmsg->cmsg_type = UDP_SEGMENT; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + + clen = CMSG_SPACE(sizeof(uint16_t)); + + valp = (void *) CMSG_DATA(cmsg); + *valp = segment; + +#if defined(NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + cmsg = CMSG_NXTHDR(&msg, cmsg); + clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + msg.msg_controllen = clen; + + n = ngx_sendmsg(c, &msg, 0); + if (n == -1) { + return NGX_ERROR; + } + + c->sent += n; + + return n; +} + +#endif + + + static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c) { diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h index 3036caebf..88fef47ce 100644 --- a/src/os/unix/ngx_linux_config.h +++ b/src/os/unix/ngx_linux_config.h @@ -103,6 +103,10 @@ typedef struct iocb ngx_aiocb_t; #include #endif +#if (NGX_HAVE_UDP_SEGMENT) +#include +#endif + #define NGX_LISTEN_BACKLOG 511 -- cgit v1.2.3 From 169b27a50b5e91184853bab96d9377b6f82d871c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 15 Jul 2021 14:22:54 +0300 Subject: Core: added separate function for local source address cmsg. --- src/event/ngx_event_udp.c | 92 ++++--------------------------------- src/event/ngx_event_udp.h | 2 + src/os/unix/ngx_udp_sendmsg_chain.c | 65 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 82 deletions(-) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index f5d9638dc..64db5c17f 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -41,18 +41,8 @@ ngx_event_recvmsg(ngx_event_t *ev) ngx_connection_t *c, *lc; static u_char buffer[65535]; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - -#if (NGX_HAVE_IP_RECVDSTADDR) - u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; -#elif (NGX_HAVE_IP_PKTINFO) - u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif - +#if (NGX_HAVE_ADDRINFO_CMSG) + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif if (ev->timedout) { @@ -87,25 +77,13 @@ ngx_event_recvmsg(ngx_event_t *ev) msg.msg_iov = iov; msg.msg_iovlen = 1; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - +#if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); -#if (NGX_HAVE_IP_RECVDSTADDR || NGX_HAVE_IP_PKTINFO) - if (ls->sockaddr->sa_family == AF_INET) { - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); - } -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - if (ls->sockaddr->sa_family == AF_INET6) { - msg.msg_control = &msg_control6; - msg.msg_controllen = sizeof(msg_control6); - } -#endif - } - + ngx_memzero(&msg_control, sizeof(msg_control)); + } #endif n = recvmsg(lc->fd, &msg, 0); @@ -124,7 +102,7 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +#if (NGX_HAVE_ADDRINFO_CMSG) if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { ngx_log_error(NGX_LOG_ALERT, ev->log, 0, "recvmsg() truncated data"); @@ -154,7 +132,7 @@ ngx_event_recvmsg(ngx_event_t *ev) local_sockaddr = ls->sockaddr; local_socklen = ls->socklen; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +#if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { struct cmsghdr *cmsg; @@ -166,59 +144,9 @@ ngx_event_recvmsg(ngx_event_t *ev) cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - -#if (NGX_HAVE_IP_RECVDSTADDR) - - if (cmsg->cmsg_level == IPPROTO_IP - && cmsg->cmsg_type == IP_RECVDSTADDR - && local_sockaddr->sa_family == AF_INET) - { - struct in_addr *addr; - struct sockaddr_in *sin; - - addr = (struct in_addr *) CMSG_DATA(cmsg); - sin = (struct sockaddr_in *) local_sockaddr; - sin->sin_addr = *addr; - + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { break; } - -#elif (NGX_HAVE_IP_PKTINFO) - - if (cmsg->cmsg_level == IPPROTO_IP - && cmsg->cmsg_type == IP_PKTINFO - && local_sockaddr->sa_family == AF_INET) - { - struct in_pktinfo *pkt; - struct sockaddr_in *sin; - - pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); - sin = (struct sockaddr_in *) local_sockaddr; - sin->sin_addr = pkt->ipi_addr; - - break; - } - -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - - if (cmsg->cmsg_level == IPPROTO_IPV6 - && cmsg->cmsg_type == IPV6_PKTINFO - && local_sockaddr->sa_family == AF_INET6) - { - struct in6_pktinfo *pkt6; - struct sockaddr_in6 *sin6; - - pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); - sin6 = (struct sockaddr_in6 *) local_sockaddr; - sin6->sin6_addr = pkt6->ipi6_addr; - - break; - } - -#endif - } } diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h index bd7c973a2..31d46b7df 100644 --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -55,6 +55,8 @@ typedef union { size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr); +ngx_int_t ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, + struct sockaddr *local_sockaddr); #endif diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index f13bed9a9..78b497ea0 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -316,6 +316,71 @@ ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) return 0; } + +ngx_int_t +ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) +{ + +#if (NGX_HAVE_IP_RECVDSTADDR) + struct in_addr *addr; + struct sockaddr_in *sin; +#elif (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo *pkt; + struct sockaddr_in *sin; +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo *pkt6; + struct sockaddr_in6 *sin6; +#endif + + + #if (NGX_HAVE_IP_RECVDSTADDR) + + if (cmsg->cmsg_level == IPPROTO_IP + && cmsg->cmsg_type == IP_RECVDSTADDR + && local_sockaddr->sa_family == AF_INET) + { + addr = (struct in_addr *) CMSG_DATA(cmsg); + sin = (struct sockaddr_in *) local_sockaddr; + sin->sin_addr = *addr; + + return NGX_OK; + } + +#elif (NGX_HAVE_IP_PKTINFO) + + if (cmsg->cmsg_level == IPPROTO_IP + && cmsg->cmsg_type == IP_PKTINFO + && local_sockaddr->sa_family == AF_INET) + { + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + sin = (struct sockaddr_in *) local_sockaddr; + sin->sin_addr = pkt->ipi_addr; + + return NGX_OK; + } + +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + + if (cmsg->cmsg_level == IPPROTO_IPV6 + && cmsg->cmsg_type == IPV6_PKTINFO + && local_sockaddr->sa_family == AF_INET6) + { + pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); + sin6 = (struct sockaddr_in6 *) local_sockaddr; + sin6->sin6_addr = pkt6->ipi6_addr; + + return NGX_OK; + } + +#endif + + return NGX_DECLINED; +} + #endif -- cgit v1.2.3 From 31fe966e719c8fb0273119476b0c9a86d3f8e1b2 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 20 Jul 2021 12:04:58 +0300 Subject: Core: fixed errno clobbering in ngx_sendmsg(). This was broken by 2dfd313f22f2. --- src/os/unix/ngx_udp_sendmsg_chain.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index 78b497ea0..b29b8d318 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -398,15 +398,6 @@ eintr: n = sendmsg(c->fd, msg, flags); -#if (NGX_DEBUG) - for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) { - size += msg->msg_iov[i].iov_len; - } - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sendmsg: %z of %uz", n, size); -#endif - if (n == -1) { err = ngx_errno; @@ -428,5 +419,14 @@ eintr: } } +#if (NGX_DEBUG) + for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) { + size += msg->msg_iov[i].iov_len; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sendmsg: %z of %uz", n, size); +#endif + return n; } -- cgit v1.2.3 From 6157d0b5c1b3a6be7928748df2cda19838889f4f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 20 Jul 2021 12:37:12 +0300 Subject: QUIC: the "quic_gso" directive. The directive enables usage of UDP segmentation offloading by quic. By default, gso is disabled since it is not always operational when detected (depends on interface configuration). --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_output.c | 8 ++++++-- src/http/modules/ngx_http_quic_module.c | 9 +++++++++ src/stream/ngx_stream_quic_module.c | 9 +++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index fe0f7fef3..d3429cbe4 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -59,6 +59,7 @@ typedef struct { ngx_ssl_t *ssl; ngx_quic_tp_t tp; ngx_flag_t retry; + ngx_flag_t gso_enabled; ngx_flag_t require_alpn; ngx_str_t host_key; u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index ce3805c8f..dc4cf59be 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -212,13 +212,17 @@ ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; + qc = ngx_quic_get_connection(c); + + if (!qc->conf->gso_enabled) { + return 0; + } + if (qsock->path->state != NGX_QUIC_PATH_VALIDATED) { /* don't even try to be faster on non-validated paths */ return 0; } - qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); if (!ngx_queue_empty(&ctx->frames)) { return 0; diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index d933dd1f9..ab84583f2 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -126,6 +126,13 @@ static ngx_command_t ngx_http_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, + { ngx_string("quic_gso"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, gso_enabled), + NULL }, + { ngx_string("quic_host_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_quic_host_key, @@ -290,6 +297,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; + conf->gso_enabled = NGX_CONF_UNSET; conf->require_alpn = 1; return conf; @@ -348,6 +356,7 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->tp.active_connection_id_limit, 2); ngx_conf_merge_value(conf->retry, prev->retry, 0); + ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 01caa9555..2cd811ad4 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -128,6 +128,13 @@ static ngx_command_t ngx_stream_quic_commands[] = { offsetof(ngx_quic_conf_t, retry), NULL }, + { ngx_string("quic_gso"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, gso_enabled), + NULL }, + { ngx_string("quic_host_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_stream_quic_host_key, @@ -251,6 +258,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; + conf->gso_enabled = NGX_CONF_UNSET; return conf; } @@ -308,6 +316,7 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->tp.active_connection_id_limit, 2); ngx_conf_merge_value(conf->retry, prev->retry, 0); + ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); -- cgit v1.2.3 From 2b5659f350974dc1659c512d5681971c857c2deb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 22 Jul 2021 15:00:37 +0300 Subject: QUIC: avoid processing 1-RTT with incomplete handshake in OpenSSL. OpenSSL is known to provide read keys for an encryption level before the level is active in TLS, following the old BoringSSL API. In BoringSSL, it was then fixed to defer releasing read keys until QUIC may use them. --- src/event/quic/ngx_event_quic.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 0d61be837..8f57b9f5d 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -918,6 +918,20 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DECLINED; } +#if !defined (OPENSSL_IS_BORINGSSL) + /* OpenSSL provides read keys for an application level before it's ready */ + + if (pkt->level == ssl_encryption_application + && SSL_quic_read_level(c->ssl->connection) + < ssl_encryption_application) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys ready, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } +#endif + pkt->keys = qc->keys; pkt->key_phase = qc->key_phase; pkt->plaintext = buf; -- cgit v1.2.3 From fc2311137fdb61f4a9c4cfdd8654ac0155ff85c9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 13 Jul 2021 22:44:03 +0300 Subject: HTTP/3: response trailers support. --- src/http/v3/ngx_http_v3_filter_module.c | 134 ++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 16 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 52db65736..8f2e3ff43 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -49,7 +49,8 @@ static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, uint64_t push_id); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); -static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r); +static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx); static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); @@ -515,7 +516,9 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) *ll = hl; ll = &cl->next; - if (r->headers_out.content_length_n >= 0 && !r->header_only) { + if (r->headers_out.content_length_n >= 0 + && !r->header_only && !r->expect_trailers) + { len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); @@ -1303,7 +1306,7 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } if (cl->buf->last_buf) { - tl = ngx_http_v3_create_trailers(r); + tl = ngx_http_v3_create_trailers(r, ctx); if (tl == NULL) { return NGX_ERROR; } @@ -1326,32 +1329,131 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) static ngx_chain_t * -ngx_http_v3_create_trailers(ngx_http_request_t *r) +ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx) { - ngx_buf_t *b; - ngx_chain_t *cl; + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create trailers"); + len = 0; - /* XXX */ + part = &r->headers_out.trailers.part; + header = part->elts; - b = ngx_calloc_buf(r->pool); - if (b == NULL) { + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { return NULL; } + b = cl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; b->last_buf = 1; - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { + if (len == 0) { + b->temporary = 0; + b->pos = b->last = NULL; + return cl; + } + + b->temporary = 1; + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + b->pos = ngx_palloc(r->pool, len); + if (b->pos == NULL) { return NULL; } - cl->buf = b; - cl->next = NULL; + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, + 0, 0, 0); + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { - return cl; + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + n = b->last - b->pos; + + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (hl == NULL) { + return NULL; + } + + b = hl->buf; + p = b->start; + + if (p == NULL) { + p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (p == NULL) { + return NULL; + } + + b->start = p; + b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = p; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl->next = cl; + + return hl; } -- cgit v1.2.3 From 245a15ed2738ad5e7ca971f2ae3c34d2288dc4f9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 16 Jul 2021 15:43:01 +0300 Subject: HTTP/3: use request pool instead of connection pool. In several parts of ngx_http_v3_header_filter() connection pool was used for request-related data. --- src/http/v3/ngx_http_v3_filter_module.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 8f2e3ff43..71aa839e1 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -483,7 +483,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) b->last_buf = 1; } - cl = ngx_alloc_chain_link(c->pool); + cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } @@ -496,7 +496,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + ngx_http_v3_encode_varlen_int(NULL, n); - b = ngx_create_temp_buf(c->pool, len); + b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } @@ -505,7 +505,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) NGX_HTTP_V3_FRAME_HEADERS); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - hl = ngx_alloc_chain_link(c->pool); + hl = ngx_alloc_chain_link(r->pool); if (hl == NULL) { return NGX_ERROR; } @@ -523,7 +523,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); - b = ngx_create_temp_buf(c->pool, len); + b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } @@ -533,7 +533,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); - cl = ngx_alloc_chain_link(c->pool); + cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } -- cgit v1.2.3 From 00ca66455b986d63576e653c9892c6eea91bf312 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 23 Jul 2021 11:25:16 +0300 Subject: QUIC: updated README with GSO details. --- README | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README b/README index e8b5a428b..d4b8bf5a8 100644 --- a/README +++ b/README @@ -127,6 +127,13 @@ Experimental QUIC support for nginx ssl_protocols TLSv1.3; + To enable GSO (Generic Segmentation Offloading): + + quic_gso on; + + By default this Linux-specific optimization [8] is disabled. + Enable if your network interface is configured to support GSO. + A number of directives were added that configure HTTP/3: http3_max_table_capacity @@ -248,3 +255,4 @@ Example configuration: [5] https://datatracker.ietf.org/doc/html/rfc9002 [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html + [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf -- cgit v1.2.3 From b7a5224bd8b2976c2978b0be2569a44de730c000 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 28 Jul 2021 13:21:47 +0300 Subject: QUIC: eliminated stream type from ngx_quic_stream_frame_t. The information about the type is contained in off/len/fin bits. Also, where possible, only the first stream type (0x08) is used for simplicity. --- src/event/quic/ngx_event_quic.c | 9 +--- src/event/quic/ngx_event_quic_ack.c | 18 +------- src/event/quic/ngx_event_quic_frames.c | 19 +------- src/event/quic/ngx_event_quic_streams.c | 6 +-- src/event/quic/ngx_event_quic_transport.c | 72 ++++++++++++++++++------------- src/event/quic/ngx_event_quic_transport.h | 3 +- 6 files changed, 50 insertions(+), 77 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 8f57b9f5d..302101cf4 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1191,14 +1191,7 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PING: break; - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: + case NGX_QUIC_FT_STREAM: if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index c4e924c8e..06205c1ba 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -239,14 +239,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); break; - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: + case NGX_QUIC_FT_STREAM: ngx_quic_handle_stream_ack(c, f); break; } @@ -599,14 +592,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) ngx_quic_queue_frame(qc, f); break; - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: + case NGX_QUIC_FT_STREAM: qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); if (qs && qs->connection->write->error) { diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 52b5c624e..438565858 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -197,14 +197,7 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) switch (f->type) { case NGX_QUIC_FT_CRYPTO: - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: + case NGX_QUIC_FT_STREAM: break; default: @@ -663,15 +656,7 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) break; - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: - + case NGX_QUIC_FT_STREAM: p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); if (f->u.stream.off) { diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 24ccdea03..58639a6f9 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -584,12 +584,11 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) } frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM6; /* OFF=1 LEN=1 FIN=0 */ + frame->type = NGX_QUIC_FT_STREAM; frame->u.stream.off = 1; frame->u.stream.len = 1; frame->u.stream.fin = 0; - frame->u.stream.type = frame->type; frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; frame->u.stream.length = n; @@ -755,12 +754,11 @@ ngx_quic_stream_cleanup_handler(void *data) } frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM7; /* OFF=1 LEN=1 FIN=1 */ + frame->type = NGX_QUIC_FT_STREAM; frame->u.stream.off = 1; frame->u.stream.len = 1; frame->u.stream.fin = 1; - frame->u.stream.type = frame->type; frame->u.stream.stream_id = qs->id; frame->u.stream.offset = c->sent; frame->u.stream.length = 0; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 894595fbc..94d516fc7 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -14,6 +14,10 @@ #define NGX_QUIC_LONG_DCID_OFFSET 6 #define NGX_QUIC_SHORT_DCID_OFFSET 1 +#define NGX_QUIC_STREAM_FRAME_FIN 0x01 +#define NGX_QUIC_STREAM_FRAME_LEN 0x02 +#define NGX_QUIC_STREAM_FRAME_OFF 0x04 + #if (NGX_HAVE_NONALIGNED) @@ -736,10 +740,6 @@ ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, } -#define ngx_quic_stream_bit_off(val) (((val) & 0x04) ? 1 : 0) -#define ngx_quic_stream_bit_len(val) (((val) & 0x02) ? 1 : 0) -#define ngx_quic_stream_bit_fin(val) (((val) & 0x01) ? 1 : 0) - ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, ngx_quic_frame_t *f) @@ -931,7 +931,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, break; - case NGX_QUIC_FT_STREAM0: + case NGX_QUIC_FT_STREAM: case NGX_QUIC_FT_STREAM1: case NGX_QUIC_FT_STREAM2: case NGX_QUIC_FT_STREAM3: @@ -940,34 +940,36 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_STREAM6: case NGX_QUIC_FT_STREAM7: - f->u.stream.type = f->type; - - f->u.stream.off = ngx_quic_stream_bit_off(f->type); - f->u.stream.len = ngx_quic_stream_bit_len(f->type); - f->u.stream.fin = ngx_quic_stream_bit_fin(f->type); + f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); if (p == NULL) { goto error; } - if (f->type & 0x04) { + if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { + f->u.stream.off = 1; + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); if (p == NULL) { goto error; } } else { + f->u.stream.off = 0; f->u.stream.offset = 0; } - if (f->type & 0x02) { + if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { + f->u.stream.len = 1; + p = ngx_quic_parse_int(p, end, &f->u.stream.length); if (p == NULL) { goto error; } } else { + f->u.stream.len = 0; f->u.stream.length = end - p; /* up to packet end */ } @@ -977,6 +979,8 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, } b->last = p; + + f->type = NGX_QUIC_FT_STREAM; break; case NGX_QUIC_FT_MAX_DATA: @@ -1141,7 +1145,7 @@ ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) /* STOP_SENDING */ 0x3, /* CRYPTO */ 0xD, /* NEW_TOKEN */ 0x0, /* only sent by server */ - /* STREAM0 */ 0x3, + /* STREAM */ 0x3, /* STREAM1 */ 0x3, /* STREAM2 */ 0x3, /* STREAM3 */ 0x3, @@ -1276,14 +1280,7 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) case NGX_QUIC_FT_NEW_TOKEN: return ngx_quic_create_new_token(p, &f->u.token); - case NGX_QUIC_FT_STREAM0: - case NGX_QUIC_FT_STREAM1: - case NGX_QUIC_FT_STREAM2: - case NGX_QUIC_FT_STREAM3: - case NGX_QUIC_FT_STREAM4: - case NGX_QUIC_FT_STREAM5: - case NGX_QUIC_FT_STREAM6: - case NGX_QUIC_FT_STREAM7: + case NGX_QUIC_FT_STREAM: return ngx_quic_create_stream(p, &f->u.stream, f->data); case NGX_QUIC_FT_CONNECTION_CLOSE: @@ -1499,20 +1496,34 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ngx_chain_t *data) { size_t len; - u_char *start; + u_char *start, type; ngx_buf_t *b; + type = NGX_QUIC_FT_STREAM; + + if (sf->off) { + type |= NGX_QUIC_STREAM_FRAME_OFF; + } + + if (sf->len) { + type |= NGX_QUIC_STREAM_FRAME_LEN; + } + + if (sf->fin) { + type |= NGX_QUIC_STREAM_FRAME_FIN; + } + if (p == NULL) { - len = ngx_quic_varint_len(sf->type); + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(sf->stream_id); if (sf->off) { len += ngx_quic_varint_len(sf->offset); } - len += ngx_quic_varint_len(sf->stream_id); - - /* length is always present in generated frames */ - len += ngx_quic_varint_len(sf->length); + if (sf->len) { + len += ngx_quic_varint_len(sf->length); + } len += sf->length; @@ -1521,15 +1532,16 @@ ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, start = p; - ngx_quic_build_int(&p, sf->type); + ngx_quic_build_int(&p, type); ngx_quic_build_int(&p, sf->stream_id); if (sf->off) { ngx_quic_build_int(&p, sf->offset); } - /* length is always present in generated frames */ - ngx_quic_build_int(&p, sf->length); + if (sf->len) { + ngx_quic_build_int(&p, sf->length); + } while (data) { b = data->buf; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index b35ba1839..81a41b1ea 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -63,7 +63,7 @@ #define NGX_QUIC_FT_STOP_SENDING 0x05 #define NGX_QUIC_FT_CRYPTO 0x06 #define NGX_QUIC_FT_NEW_TOKEN 0x07 -#define NGX_QUIC_FT_STREAM0 0x08 +#define NGX_QUIC_FT_STREAM 0x08 #define NGX_QUIC_FT_STREAM1 0x09 #define NGX_QUIC_FT_STREAM2 0x0A #define NGX_QUIC_FT_STREAM3 0x0B @@ -190,7 +190,6 @@ typedef struct { uint64_t offset; uint64_t length; - uint8_t type; uint64_t stream_id; unsigned off:1; unsigned len:1; -- cgit v1.2.3 From 5bb45c98a73044ce2b7f389c7764cc10cc869bed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Jul 2021 10:03:36 +0300 Subject: HTTP/3: require mandatory uni streams before additional ones. As per quic-http-34: Endpoints SHOULD create the HTTP control stream as well as the unidirectional streams required by mandatory extensions (such as the QPACK encoder and decoder streams) first, and then create additional streams as allowed by their peer. Previously, client could create and destroy additional uni streams unlimited number of times before creating mandatory streams. --- src/http/v3/ngx_http_v3_streams.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index fa39be78a..693225b89 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -91,6 +91,8 @@ ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; + h3c = ngx_http_v3_get_session(c); + switch (type) { case NGX_HTTP_V3_STREAM_ENCODER: @@ -119,12 +121,19 @@ ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 stream 0x%02xL", type); + + if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + index = -1; } if (index >= 0) { - h3c = ngx_http_v3_get_session(c); - if (h3c->known_streams[index]) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; -- cgit v1.2.3 From cc3752ce8e7d2ff93df3da054cec2cccbbcfe260 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 28 Jul 2021 17:23:18 +0300 Subject: QUIC: handle EAGAIN properly on UDP sockets. Previously, the error was ignored leading to unnecessary retransmits. Now, unsent frames are returned into output queue, state is reset, and timer is started for the next send attempt. --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic_connection.h | 5 +- src/event/quic/ngx_event_quic_migration.c | 6 +- src/event/quic/ngx_event_quic_output.c | 177 +++++++++++++++++++++-------- src/event/quic/ngx_event_quic_transport.h | 1 + 5 files changed, 135 insertions(+), 55 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 302101cf4..515fb9b55 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -255,6 +255,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sending); ngx_queue_init(&qc->send_ctx[i].sent); qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 79c95a13e..8e6cea5b6 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -161,8 +161,9 @@ struct ngx_quic_send_ctx_s { uint64_t largest_ack; /* received from peer */ uint64_t largest_pn; /* received from peer */ - ngx_queue_t frames; - ngx_queue_t sent; + ngx_queue_t frames; /* generated frames */ + ngx_queue_t sending; /* frames assigned to pkt */ + ngx_queue_t sent; /* frames waiting ACK */ uint64_t pending_ack; /* non sent ack-eliciting */ uint64_t largest_range; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 53e11d9c6..050b785a6 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -55,7 +55,7 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, pad = ngx_min(1200, max); sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent == -1) { + if (sent < 0) { return NGX_ERROR; } @@ -606,7 +606,7 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) pad = ngx_min(1200, max); sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent == -1) { + if (sent < 0) { return NGX_ERROR; } @@ -618,7 +618,7 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) pad = ngx_min(1200, max); sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent == -1) { + if (sent < 0) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index dc4cf59be..a8cf7c5f6 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -39,11 +39,16 @@ #define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ +#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ + static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock); static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock); +static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum); #if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock); @@ -138,6 +143,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) size_t len, min; ssize_t n; u_char *p; + uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST]; ngx_uint_t i, pad; ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; @@ -145,7 +151,6 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); - path = qsock->path; for ( ;; ) { @@ -167,6 +172,8 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) ctx = &qc->send_ctx[i]; + preserved_pnum[i] = ctx->pnum; + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { return NGX_ERROR; } @@ -194,6 +201,19 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) return NGX_ERROR; } + if (n == NGX_AGAIN) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]); + } + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_commit_send(c, &qc->send_ctx[i]); + } + path->sent += len; } @@ -201,6 +221,57 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) } +static void +ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cg = &qc->congestion; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_head(&ctx->sending); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + + if (f->pkt_need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, q); + + cg->in_flight += f->plen; + + } else { + ngx_quic_free_frame(c, f); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); +} + + +static void +ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum) +{ + ngx_queue_t *q; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_last(&ctx->sending); + ngx_queue_remove(q); + ngx_queue_insert_head(&ctx->frames, q); + } + + ctx->pnum = pnum; +} + + #if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) static ngx_uint_t @@ -264,9 +335,10 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) size_t len, segsize; ssize_t n; u_char *p, *end; + uint64_t preserved_pnum; ngx_uint_t nseg; - ngx_quic_send_ctx_t *ctx; ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; @@ -286,6 +358,8 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) nseg = 0; + preserved_pnum = ctx->pnum; + for ( ;; ) { len = ngx_min(segsize, (size_t) (end - p)); @@ -315,10 +389,20 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) return NGX_ERROR; } + if (n == NGX_AGAIN) { + ngx_quic_revert_send(c, ctx, preserved_pnum); + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + ngx_quic_commit_send(c, ctx); + path->sent += n; p = dst; nseg = 0; + preserved_pnum = ctx->pnum; } } @@ -380,8 +464,8 @@ ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, msg.msg_controllen = clen; n = ngx_sendmsg(c, &msg, 0); - if (n == -1) { - return NGX_ERROR; + if (n < 0) { + return n; } c->sent += n; @@ -622,32 +706,20 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ctx->pnum++; if (pkt.need_ack) { - /* move frames into the sent queue to wait for ack */ - - if (!qc->closing) { - q = ngx_queue_head(&ctx->frames); - f = ngx_queue_data(q, ngx_quic_frame_t, queue); - f->plen = res.len; - - do { - q = ngx_queue_head(&ctx->frames); - ngx_queue_remove(q); - ngx_queue_insert_tail(&ctx->sent, q); - } while (--nframes); - } - - cg->in_flight += res.len; + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic congestion send if:%uz", cg->in_flight); + f->plen = res.len; } while (nframes--) { q = ngx_queue_head(&ctx->frames); f = ngx_queue_data(q, ngx_quic_frame_t, queue); + f->pkt_need_ack = pkt.need_ack; + ngx_queue_remove(q); - ngx_quic_free_frame(c, f); + ngx_queue_insert_tail(&ctx->sending, q); } return res.len; @@ -658,37 +730,46 @@ static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen) { - ngx_buf_t b; - socklen_t orig_socklen; - ngx_chain_t cl, *res; - struct sockaddr *orig_sockaddr; + ssize_t n; + struct iovec iov; + struct msghdr msg; +#if defined(NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif - ngx_memzero(&b, sizeof(ngx_buf_t)); + ngx_memzero(&msg, sizeof(struct msghdr)); - b.pos = b.start = buf; - b.last = b.end = buf + len; - b.last_buf = 1; - b.temporary = 1; + iov.iov_len = len; + iov.iov_base = buf; - cl.buf = &b; - cl.next= NULL; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; - orig_socklen = c->socklen; - orig_sockaddr = c->sockaddr; + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; - c->sockaddr = sockaddr; - c->socklen = socklen; +#if defined(NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { - res = c->send_chain(c, &cl, 0); + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); - c->sockaddr = orig_sockaddr; - c->socklen = orig_socklen; + cmsg = CMSG_FIRSTHDR(&msg); - if (res == NGX_CHAIN_ERROR) { - return NGX_ERROR; + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; } - return len; + c->sent += n; + + return n; } @@ -945,9 +1026,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) - == NGX_ERROR) - { + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) { return NGX_ERROR; } @@ -1006,7 +1085,7 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, #endif len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); - if (len == NGX_ERROR) { + if (len < 0) { return NGX_ERROR; } @@ -1221,7 +1300,5 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ctx->pnum++; - len = ngx_quic_send(c, res.data, res.len, sockaddr, socklen); - - return len; + return ngx_quic_send(c, res.data, res.len, sockaddr, socklen); } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 81a41b1ea..493882308 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -273,6 +273,7 @@ struct ngx_quic_frame_s { ngx_msec_t last; ssize_t len; unsigned need_ack:1; + unsigned pkt_need_ack:1; unsigned flush:1; ngx_chain_t *data; -- cgit v1.2.3 From 7a8fa1182812cd2408f2fe404b3ec6de35b9299c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Jul 2021 12:49:16 +0300 Subject: QUIC: limit in-flight bytes by congestion window. Previously, in-flight byte counter and congestion window were properly maintained, but the limit was not properly implemented. Now a new datagram is sent only if in-flight byte counter is less than window. The limit is datagram-based, which means that a single datagram may lead to exceeding the limit, but the next one will not be sent. --- src/event/quic/ngx_event_quic_ack.c | 22 ++++++++++++++++++++-- src/event/quic/ngx_event_quic_output.c | 15 +++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 06205c1ba..3e4bb6d4f 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -293,6 +293,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, void ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { + ngx_uint_t blocked; ngx_msec_t timer; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; @@ -304,6 +305,8 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) qc = ngx_quic_get_connection(c); cg = &qc->congestion; + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + cg->in_flight -= f->plen; timer = f->last - cg->recovery_start; @@ -313,7 +316,7 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) "quic congestion ack recovery win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); - return; + goto done; } if (cg->window < cg->ssthresh) { @@ -338,6 +341,12 @@ ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) if ((ngx_msec_int_t) timer < 0) { cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; } + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } } @@ -620,6 +629,7 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) static void ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) { + ngx_uint_t blocked; ngx_msec_t timer; ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; @@ -631,6 +641,8 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) qc = ngx_quic_get_connection(c); cg = &qc->congestion; + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + cg->in_flight -= f->plen; f->plen = 0; @@ -641,7 +653,7 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) "quic congestion lost recovery win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); - return; + goto done; } cg->recovery_start = ngx_current_msec; @@ -656,6 +668,12 @@ ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic congestion lost win:%uz ss:%z if:%uz", cg->window, cg->ssthresh, cg->in_flight); + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index a8cf7c5f6..dbcdb70d5 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -147,13 +147,16 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_uint_t i, pad; ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); + cg = &qc->congestion; path = qsock->path; - for ( ;; ) { + while (cg->in_flight < cg->window) { + p = dst; len = ngx_min(qc->ctp.max_udp_payload_size, @@ -339,10 +342,12 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_uint_t nseg; ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; qc = ngx_quic_get_connection(c); + cg = &qc->congestion; path = qsock->path; ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); @@ -364,7 +369,7 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) len = ngx_min(segsize, (size_t) (end - p)); - if (len) { + if (len && cg->in_flight < cg->window) { n = ngx_quic_output_packet(c, ctx, p, len, len, qsock); if (n == NGX_ERROR) { @@ -531,7 +536,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_queue_t *q; ngx_quic_frame_t *f; ngx_quic_header_t pkt; - ngx_quic_congestion_t *cg; ngx_quic_connection_t *qc; static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; @@ -545,7 +549,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, max, min); qc = ngx_quic_get_connection(c); - cg = &qc->congestion; hlen = (ctx->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER @@ -568,10 +571,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (!pkt.need_ack && f->need_ack && max > cg->window) { - max = cg->window; - } - if (f->type == NGX_QUIC_FT_PATH_RESPONSE || f->type == NGX_QUIC_FT_PATH_CHALLENGE) { -- cgit v1.2.3 From 2f833198b8229842dbbc57f7e86b00b19ed3b294 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Jul 2021 12:17:56 +0300 Subject: HTTP/3: http3_max_uni_streams directive. The directive limits the number of uni streams client is allowed to create. --- src/http/v3/ngx_http_v3.h | 2 ++ src/http/v3/ngx_http_v3_module.c | 12 ++++++++++++ src/http/v3/ngx_http_v3_streams.c | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index e693af7d8..bea560864 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -53,6 +53,7 @@ #define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 #define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 #define NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES 10 +#define NGX_HTTP_V3_DEFAULT_MAX_UNI_STREAMS 3 /* HTTP/3 errors */ #define NGX_HTTP_V3_ERR_NO_ERROR 0x100 @@ -99,6 +100,7 @@ typedef struct { size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; + ngx_uint_t max_uni_streams; } ngx_http_v3_srv_conf_t; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 3b651f044..873ebb2f3 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -42,6 +42,13 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), NULL }, + { ngx_string("http3_max_uni_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_uni_streams), + NULL }, + { ngx_string("http3_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_v3_push, @@ -104,6 +111,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; + h3scf->max_uni_streams = NGX_CONF_UNSET_UINT; return h3scf; } @@ -127,6 +135,10 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) prev->max_concurrent_pushes, NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES); + ngx_conf_merge_uint_value(conf->max_uni_streams, + prev->max_uni_streams, + NGX_HTTP_V3_DEFAULT_MAX_UNI_STREAMS); + return NGX_CONF_OK; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 693225b89..1b0f91454 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -35,10 +35,24 @@ static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { + uint64_t n; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_uni_stream_t *us; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + n = c->quic->id >> 2; + + if (n >= h3scf->max_uni_streams) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "reached maximum number of uni streams"); + ngx_http_close_connection(c); + return; + } + c->quic->cancelable = 1; us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); -- cgit v1.2.3 From b93ae5d0670f265bec60cd616dd42b0ce96d8e2d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 2 Aug 2021 15:48:21 +0300 Subject: QUIC: stream limits in "hq" mode. The "hq" mode is HTTP/0.9-1.1 over QUIC. The following limits are introduced: - uni streams are not allowed - keepalive_requests is enforced - keepalive_time is enforced In case of error, QUIC connection is finalized with 0x101 code. This code corresponds to HTTP/3 General Protocol Error. --- src/http/modules/ngx_http_quic_module.c | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index ab84583f2..b41c069b6 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -188,6 +188,7 @@ static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); ngx_int_t ngx_http_quic_init(ngx_connection_t *c) { + uint64_t n; ngx_quic_conf_t *qcf; ngx_http_connection_t *hc, *phc; ngx_http_core_loc_conf_t *clcf; @@ -208,6 +209,40 @@ ngx_http_quic_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init quic stream"); +#if (NGX_HTTP_V3) + if (!hc->addr_conf->http3) +#endif + { + /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_quic_finalize_connection(c->quic->parent, 0x101, + "unexpected uni stream"); + ngx_http_close_connection(c); + return NGX_DONE; + } + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests) { + ngx_quic_finalize_connection(c->quic->parent, 0x101, + "reached maximum number of requests"); + ngx_http_close_connection(c); + return NGX_DONE; + } + + if (ngx_current_msec - c->quic->parent->start_time + > clcf->keepalive_time) + { + ngx_quic_finalize_connection(c->quic->parent, 0x101, + "reached maximum time for requests"); + ngx_http_close_connection(c); + return NGX_DONE; + } + } + phc = ngx_http_quic_get_connection(c); if (phc->ssl_servername) { -- cgit v1.2.3 From e1ad576f960ab2b455b4d12869f69cb648feba42 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Jul 2021 16:01:37 +0300 Subject: HTTP/3: close connection on keepalive_requests * 2. After receiving GOAWAY, client is not supposed to create new streams. However, until client reads this frame, we allow it to create new streams, which are gracefully rejected. To prevent client from abusing this algorithm, a new limit is introduced. Upon reaching keepalive_requests * 2, server now closes the entire QUIC connection claiming excessive load. --- src/http/v3/ngx_http_v3_request.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5fc6e233b..f45a7b95e 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,6 +81,15 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests * 2) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "too many requests per connection"); + ngx_http_close_connection(c); + return; + } + h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { @@ -89,8 +98,6 @@ ngx_http_v3_init(ngx_connection_t *c) return; } - n = c->quic->id >> 2; - if (n + 1 == clcf->keepalive_requests || ngx_current_msec - c->quic->parent->start_time > clcf->keepalive_time) -- cgit v1.2.3 From 0b179efeb0bc18e92a31748685cc74240ddb5667 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 4 Aug 2021 15:49:18 +0300 Subject: QUIC: client certificate validation with OCSP. --- src/event/quic/ngx_event_quic.c | 2 + src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_connection.h | 4 ++ src/event/quic/ngx_event_quic_ssl.c | 14 ++++ src/event/quic/ngx_event_quic_streams.c | 101 +++++++++++++++++++++++++---- src/event/quic/ngx_event_quic_streams.h | 1 + 6 files changed, 109 insertions(+), 14 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 515fb9b55..e79a24e8a 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -304,6 +304,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; ctp->active_connection_id_limit = 2; + ngx_queue_init(&qc->streams.uninitialized); + qc->streams.recv_max_data = qc->tp.initial_max_data; qc->streams.recv_window = qc->streams.recv_max_data; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index d3429cbe4..d425cee31 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -69,6 +69,7 @@ typedef struct { struct ngx_quic_stream_s { ngx_rbtree_node_t node; + ngx_queue_t queue; ngx_connection_t *parent; ngx_connection_t *connection; uint64_t id; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 8e6cea5b6..f3de5b136 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -114,6 +114,7 @@ struct ngx_quic_socket_s { typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; + ngx_queue_t uninitialized; uint64_t sent; uint64_t recv_offset; @@ -131,6 +132,9 @@ typedef struct { uint64_t client_max_streams_bidi; uint64_t client_streams_uni; uint64_t client_streams_bidi; + + ngx_uint_t initialized; + /* unsigned initialized:1; */ } ngx_quic_streams_t; diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 5e2827f23..9083ad1e3 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -361,6 +361,7 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) { int n, sslerr; + ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl; ngx_ssl_conn_t *ssl_conn; @@ -462,6 +463,19 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) return NGX_ERROR; } + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_quic_init_streams; + return NGX_OK; + } + + ngx_quic_init_streams(c); + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 58639a6f9..c4fd4eb3e 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -15,8 +15,10 @@ static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id); +static ngx_int_t ngx_quic_init_stream(ngx_quic_stream_t *qs); 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); 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, @@ -146,6 +148,8 @@ 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_pool_t *pool; + ngx_queue_t *q; ngx_event_t *rev, *wev; ngx_rbtree_t *tree; ngx_rbtree_node_t *node; @@ -155,6 +159,17 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ngx_uint_t ns; #endif + while (!ngx_queue_empty(&qc->streams.uninitialized)) { + q = ngx_queue_head(&qc->streams.uninitialized); + ngx_queue_remove(q); + + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + pool = qs->connection->pool; + + ngx_close_connection(qs->connection); + ngx_destroy_pool(pool); + } + tree = &qc->streams.tree; if (tree->root == tree->sentinel) { @@ -310,7 +325,9 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) return NULL; } - qs->connection->listening->handler(qs->connection); + if (ngx_quic_init_stream(qs) != NGX_OK) { + return NULL; + } if (qc->shutdown) { return NGX_QUIC_STREAM_GONE; @@ -321,6 +338,59 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) } +static ngx_int_t +ngx_quic_init_stream(ngx_quic_stream_t *qs) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(qs->parent); + + c = qs->connection; + + if (!qc->streams.initialized) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic postpone stream init"); + + ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); + + c->listening->handler(c); + + return NGX_OK; +} + + +void +ngx_quic_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"); + + qc = ngx_quic_get_connection(c); + + while (!ngx_queue_empty(&qc->streams.uninitialized)) { + q = ngx_queue_head(&qc->streams.uninitialized); + ngx_queue_remove(q); + + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0, + "quic init postponed stream"); + + qs->connection->listening->handler(qs->connection); + } + + qc->streams.initialized = 1; +} + + static ngx_quic_stream_t * ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) { @@ -387,6 +457,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->read->log = log; sc->write->log = log; + sc->read->handler = ngx_quic_empty_handler; + sc->write->handler = ngx_quic_empty_handler; + log->connection = sc->number; if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 @@ -432,6 +505,12 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) } +static void +ngx_quic_empty_handler(ngx_event_t *ev) +{ +} + + static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { @@ -832,9 +911,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, goto cleanup; } - sc->listening->handler(sc); - - return NGX_OK; + return ngx_quic_init_stream(qs); } sc = qs->connection; @@ -986,7 +1063,9 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, limit = qs->recv_max_data; - qs->connection->listening->handler(qs->connection); + if (ngx_quic_init_stream(qs) != NGX_OK) { + return NGX_ERROR; + } } else { limit = qs->recv_max_data; @@ -1043,9 +1122,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, qs->send_max_data = f->limit; } - qs->connection->listening->handler(qs->connection); - - return NGX_OK; + return ngx_quic_init_stream(qs); } if (f->limit <= qs->send_max_data) { @@ -1117,9 +1194,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, goto cleanup; } - sc->listening->handler(sc); - - return NGX_OK; + return ngx_quic_init_stream(qs); } sc = qs->connection; @@ -1202,9 +1277,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, wev->error = 1; wev->ready = 1; - sc->listening->handler(sc); - - return NGX_OK; + return ngx_quic_init_stream(qs); } wev = qs->connection->write; diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h index 0ee9c37f2..95cdfca1c 100644 --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -31,6 +31,7 @@ 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); +void 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, -- cgit v1.2.3 From dab9163a95cb9c1c00ee9a3644c58474528b0f2b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 5 Aug 2021 09:20:32 +0300 Subject: QUIC: asynchronous shutdown. Previously, when cleaning up a QUIC stream in shutdown mode, ngx_quic_shutdown_quic() was called, which could close the QUIC connection right away. This could be a problem if the connection was referenced up the stack. For example, this could happen in ngx_quic_init_streams(), ngx_quic_close_streams(), ngx_quic_create_client_stream() etc. With a typical HTTP/3 client the issue is unlikely because of HTTP/3 uni streams which need a posted event to close. In this case QUIC connection cannot be closed right away. Now QUIC connection read event is posted and it will shut down the connection asynchronously. --- src/event/quic/ngx_event_quic.c | 4 ++++ src/event/quic/ngx_event_quic_streams.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index e79a24e8a..076e19422 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -421,7 +421,11 @@ ngx_quic_input_handler(ngx_event_t *rev) if (!rev->ready) { if (qc->closing) { ngx_quic_close_connection(c, NGX_OK); + + } else if (qc->shutdown) { + ngx_quic_shutdown_quic(c); } + return; } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index c4fd4eb3e..bff41b20c 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -849,7 +849,7 @@ done: (void) ngx_quic_output(pc); if (qc->shutdown) { - ngx_quic_shutdown_quic(pc); + ngx_post_event(pc->read, &ngx_posted_events); } } -- cgit v1.2.3 From af83b3c32c26646f2b3bf1b8f097eb175f6b5bb5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 4 Aug 2021 17:35:11 +0300 Subject: HTTP/3: replaced macros with values. --- src/http/v3/ngx_http_v3.h | 5 ----- src/http/v3/ngx_http_v3_module.c | 12 ++++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index bea560864..9076b6ff5 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -50,11 +50,6 @@ #define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 #define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 -#define NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY 16384 -#define NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS 16 -#define NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES 10 -#define NGX_HTTP_V3_DEFAULT_MAX_UNI_STREAMS 3 - /* HTTP/3 errors */ #define NGX_HTTP_V3_ERR_NO_ERROR 0x100 #define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 873ebb2f3..4b59b097c 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -124,20 +124,16 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_v3_srv_conf_t *conf = child; ngx_conf_merge_size_value(conf->max_table_capacity, - prev->max_table_capacity, - NGX_HTTP_V3_DEFAULT_MAX_TABLE_CAPACITY); + prev->max_table_capacity, 16384); ngx_conf_merge_uint_value(conf->max_blocked_streams, - prev->max_blocked_streams, - NGX_HTTP_V3_DEFAULT_MAX_BLOCKED_STREAMS); + prev->max_blocked_streams, 16); ngx_conf_merge_uint_value(conf->max_concurrent_pushes, - prev->max_concurrent_pushes, - NGX_HTTP_V3_DEFAULT_MAX_CONCURRENT_PUSHES); + prev->max_concurrent_pushes, 10); ngx_conf_merge_uint_value(conf->max_uni_streams, - prev->max_uni_streams, - NGX_HTTP_V3_DEFAULT_MAX_UNI_STREAMS); + prev->max_uni_streams, 3); return NGX_CONF_OK; } -- cgit v1.2.3 From d895a831ae76d97999de777d9289e5e6e29c5741 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Aug 2021 11:09:13 +0300 Subject: HTTP/3: got rid of HTTP/2 module dependency. The Huffman encoder/decoder now can be built separately from HTTP/2 module. --- auto/modules | 9 ++++++--- src/http/ngx_http.h | 6 ++++++ src/http/v2/ngx_http_v2.h | 6 ------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/auto/modules b/auto/modules index d454466af..7430bfe6b 100644 --- a/auto/modules +++ b/auto/modules @@ -421,9 +421,6 @@ if [ $HTTP = YES ]; then have=NGX_HTTP_HEADERS . auto/have HTTP_QUIC=YES - # XXX for Huffman - HTTP_V2=YES - ngx_module_name=ngx_http_v3_module ngx_module_incs=src/http/v3 ngx_module_deps="src/http/v3/ngx_http_v3.h \ @@ -441,6 +438,12 @@ if [ $HTTP = YES ]; then ngx_module_libs= ngx_module_link=$HTTP_V3 + if [ $HTTP_V2 = NO ]; then + ngx_module_srcs="$ngx_module_srcs \ + src/http/v2/ngx_http_v2_huff_decode.c \ + src/http/v2/ngx_http_v2_huff_encode.c" + fi + . auto/module fi diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 70109adc2..fb4157715 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -180,6 +180,12 @@ ngx_int_t ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types, ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3) +ngx_int_t ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, + u_char **dst, ngx_uint_t last, ngx_log_t *log); +size_t ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, + ngx_uint_t lower); +#endif extern ngx_module_t ngx_http_module; diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 349229711..65fc65812 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -312,12 +312,6 @@ ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); -ngx_int_t ngx_http_v2_huff_decode(u_char *state, u_char *src, size_t len, - u_char **dst, ngx_uint_t last, ngx_log_t *log); -size_t ngx_http_v2_huff_encode(u_char *src, size_t len, u_char *dst, - ngx_uint_t lower); - - #define ngx_http_v2_prefix(bits) ((1 << (bits)) - 1) -- cgit v1.2.3 From e1fbbfaba6951ace275d64677c49d160fe5ba3ab Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Aug 2021 11:13:29 +0300 Subject: QUIC: better ordering in auto/modules. --- auto/modules | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/auto/modules b/auto/modules index 7430bfe6b..21cecc276 100644 --- a/auto/modules +++ b/auto/modules @@ -416,6 +416,26 @@ if [ $HTTP = YES ]; then ngx_module_type=HTTP + if [ $HTTP_V2 = YES ]; then + have=NGX_HTTP_V2 . auto/have + have=NGX_HTTP_HEADERS . auto/have + + ngx_module_name=ngx_http_v2_module + ngx_module_incs=src/http/v2 + ngx_module_deps="src/http/v2/ngx_http_v2.h \ + src/http/v2/ngx_http_v2_module.h" + ngx_module_srcs="src/http/v2/ngx_http_v2.c \ + src/http/v2/ngx_http_v2_table.c \ + src/http/v2/ngx_http_v2_encode.c \ + src/http/v2/ngx_http_v2_huff_decode.c \ + src/http/v2/ngx_http_v2_huff_encode.c \ + src/http/v2/ngx_http_v2_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V2 + + . auto/module + fi + if [ $HTTP_V3 = YES ]; then have=NGX_HTTP_V3 . auto/have have=NGX_HTTP_HEADERS . auto/have @@ -447,26 +467,6 @@ if [ $HTTP = YES ]; then . auto/module fi - if [ $HTTP_V2 = YES ]; then - have=NGX_HTTP_V2 . auto/have - have=NGX_HTTP_HEADERS . auto/have - - ngx_module_name=ngx_http_v2_module - ngx_module_incs=src/http/v2 - ngx_module_deps="src/http/v2/ngx_http_v2.h \ - src/http/v2/ngx_http_v2_module.h" - ngx_module_srcs="src/http/v2/ngx_http_v2.c \ - src/http/v2/ngx_http_v2_table.c \ - src/http/v2/ngx_http_v2_encode.c \ - src/http/v2/ngx_http_v2_huff_decode.c \ - src/http/v2/ngx_http_v2_huff_encode.c \ - src/http/v2/ngx_http_v2_module.c" - ngx_module_libs= - ngx_module_link=$HTTP_V2 - - . auto/module - fi - if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= -- cgit v1.2.3 From 6fb9bdad6a811f00388044a89d322fbdfd072606 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Aug 2021 12:35:12 +0300 Subject: HTTP/3: disabled control characters and space in header names. This is a follow up to 41f4bd4c51f1. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f45a7b95e..1fcbad1de 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -428,7 +428,7 @@ ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, continue; } - if (ch == '\0' || ch == LF || ch == CR || ch == ':' + if (ch <= 0x20 || ch == 0x7f || ch == ':' || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, -- cgit v1.2.3 From d6507778008ebaef59af24a74420393ac7265aa9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 17 Aug 2021 11:41:11 +0300 Subject: QUIC: fixed format specifiers in ngx_quic_bpf module. --- src/event/quic/ngx_event_quic_bpf.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c index 4d5c3adae..848a64d59 100644 --- a/src/event/quic/ngx_event_quic_bpf.c +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -222,7 +222,7 @@ ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) } ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, - "quic bpf close %s fd:%i failed", name, fd); + "quic bpf close %s fd:%d failed", name, fd); } @@ -345,7 +345,7 @@ ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, - "quic bpf sockmap created fd:%i", grp->map_fd); + "quic bpf sockmap created fd:%d", grp->map_fd); return grp; failed: @@ -400,7 +400,7 @@ ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, - "quic bpf sockmap fd duplicated old:%i new:%i", + "quic bpf sockmap fd duplicated old:%d new:%d", ogrp->map_fd, grp->map_fd); return grp; @@ -441,8 +441,8 @@ ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, - "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%d", - grp->map_fd, ls->fd, cookie, ls->worker); + "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui", + grp->map_fd, ls->fd, cookie, ls->worker); /* do not inherit this socket */ ls->ignore = 1; @@ -635,7 +635,7 @@ ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "quic bpf sockmap inherited with " - "fd:%i address:%*s", + "fd:%d address:%*s", grp->map_fd, p - v, v); v = p + 1; break; -- cgit v1.2.3 From ee13d5f93d4dddefcaf3a2ce44a1ab4489bee369 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Aug 2021 13:03:46 +0300 Subject: QUIC: fixed dead store assignment. Found by Clang Static Analyzer. --- src/event/quic/ngx_event_quic_tokens.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index 4387a4495..7b7f03e3f 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -259,7 +259,6 @@ ngx_quic_validate_token(ngx_connection_t *c, u_char *key, } odcid.data = p; - p += odcid.len; } now = ngx_time(); -- cgit v1.2.3 From 2ff0af368d08ade41cb164eb54f621361c9f1bec Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Aug 2021 13:03:48 +0300 Subject: HTTP/3: fixed dead store assignment. Found by Clang Static Analyzer. --- src/http/v3/ngx_http_v3_request.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 1fcbad1de..0bd585317 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1217,7 +1217,6 @@ done: b->last_buf = 1; *ll = tl; - ll = &tl->next; } else { -- cgit v1.2.3 From 6a74c07ea0e1dbd3cf5ec562e21aa1d5f414f195 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Aug 2021 14:40:33 +0300 Subject: QUIC: removed duplicate logging of Stateless Reset Token. --- src/event/quic/ngx_event_quic_ssl.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 9083ad1e3..669bae9bb 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -525,10 +525,6 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, qc->tp.sr_token); - len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); /* always succeeds */ -- cgit v1.2.3 From 3749805864fb2f7bb0eae7b82cfda383db970d8b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Aug 2021 14:41:31 +0300 Subject: QUIC: Stateless Reset Token debug logging cleanup. --- src/event/quic/ngx_event_quic_tokens.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index 7b7f03e3f..5b1a9df46 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -39,11 +39,9 @@ ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, return NGX_ERROR; } -#if (NGX_DEBUG) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stateless reset token %*xs", (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif return NGX_OK; } -- cgit v1.2.3 From 68d4325de08053f4cb0db590dc72ef9494c33bd6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 8 Jul 2021 21:52:47 +0300 Subject: HTTP/3: bulk parse functions. Previously HTTP/3 streams were parsed by one character. Now all parse functions receive buffers. This should optimize parsing time and CPU load. --- src/http/v3/ngx_http_v3_parse.c | 1882 ++++++++++++++++++++----------------- src/http/v3/ngx_http_v3_parse.h | 6 +- src/http/v3/ngx_http_v3_request.c | 15 +- src/http/v3/ngx_http_v3_streams.c | 35 +- 4 files changed, 1068 insertions(+), 870 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index c5fc24482..3f676b4bb 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -14,51 +14,91 @@ ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) +static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t n); +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, u_char ch); + 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, u_char ch); + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, - ngx_http_v3_parse_field_section_prefix_t *st, u_char ch); + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, - ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, u_char ch); + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, - ngx_http_v3_parse_literal_t *st, u_char ch); + ngx_http_v3_parse_literal_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch); + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch); + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch); + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch); + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch); + 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, u_char ch); + 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, u_char ch); + 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, u_char ch); + 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, u_char ch); + 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, u_char ch); + 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, u_char ch); + 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) +{ + *loc = *b; + + if ((size_t) (loc->last - loc->pos) > n) { + loc->last = loc->pos + n; + } +} + + +static void +ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn) +{ + *pn -= loc->pos - b->pos; + b->pos = loc->pos; +} + + +static ngx_int_t +ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) +{ + if ((size_t) (b->last - b->pos) < *length) { + *length -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += *length; + return NGX_DONE; +} + + static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, - ngx_http_v3_parse_varlen_int_t *st, u_char ch) + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) { + u_char ch; enum { sw_start = 0, sw_length_2, @@ -70,57 +110,64 @@ ngx_http_v3_parse_varlen_int(ngx_connection_t *c, sw_length_8 }; - switch (st->state) { - - case sw_start: + for ( ;; ) { - st->value = ch; - if (st->value & 0xc0) { - st->state = sw_length_2; - break; + if (b->pos == b->last) { + return NGX_AGAIN; } - goto done; + ch = *b->pos++; + + switch (st->state) { + + case sw_start: - case sw_length_2: + st->value = ch; + if (st->value & 0xc0) { + st->state = sw_length_2; + break; + } - st->value = (st->value << 8) + ch; - if ((st->value & 0xc000) == 0x4000) { - st->value &= 0x3fff; goto done; - } - st->state = sw_length_3; - break; + case sw_length_2: - case sw_length_4: + st->value = (st->value << 8) + ch; + if ((st->value & 0xc000) == 0x4000) { + st->value &= 0x3fff; + goto done; + } - st->value = (st->value << 8) + ch; - if ((st->value & 0xc0000000) == 0x80000000) { - st->value &= 0x3fffffff; - goto done; - } + st->state = sw_length_3; + break; - st->state = sw_length_5; - break; + case sw_length_4: - case sw_length_3: - case sw_length_5: - case sw_length_6: - case sw_length_7: + st->value = (st->value << 8) + ch; + if ((st->value & 0xc0000000) == 0x80000000) { + st->value &= 0x3fffffff; + goto done; + } - st->value = (st->value << 8) + ch; - st->state++; - break; + st->state = sw_length_5; + break; - case sw_length_8: + case sw_length_3: + case sw_length_5: + case sw_length_6: + case sw_length_7: - st->value = (st->value << 8) + ch; - st->value &= 0x3fffffffffffffff; - goto done; - } + st->value = (st->value << 8) + ch; + st->state++; + break; + + case sw_length_8: - return NGX_AGAIN; + st->value = (st->value << 8) + ch; + st->value &= 0x3fffffffffffffff; + goto done; + } + } done: @@ -134,50 +181,58 @@ done: 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, u_char ch) + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b) { + u_char ch; ngx_uint_t mask; enum { sw_start = 0, sw_value }; - switch (st->state) { - - case sw_start: - - mask = (1 << prefix) - 1; - st->value = ch & mask; + for ( ;; ) { - if (st->value != mask) { - goto done; + if (b->pos == b->last) { + return NGX_AGAIN; } - st->shift = 0; - st->state = sw_value; - break; + ch = *b->pos++; - case sw_value: + switch (st->state) { - st->value += (uint64_t) (ch & 0x7f) << st->shift; + case sw_start: - 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; - } + mask = (1 << prefix) - 1; + st->value = ch & mask; - if (ch & 0x80) { - st->shift += 7; + if (st->value != mask) { + goto done; + } + + st->shift = 0; + st->state = sw_value; break; - } - goto done; - } + case sw_value: + + st->value += (uint64_t) (ch & 0x7f) << st->shift; + + 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; + } - return NGX_AGAIN; + if (ch & 0x80) { + st->shift += 7; + break; + } + + goto done; + } + } done: @@ -191,8 +246,9 @@ done: ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, - u_char ch) + ngx_buf_t *b) { + ngx_buf_t loc; ngx_int_t rc; enum { sw_start = 0, @@ -205,118 +261,131 @@ ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, sw_done }; - switch (st->state) { + for ( ;; ) { + + switch (st->state) { + + case sw_start: - case sw_start: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers"); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers"); + st->state = sw_type; - st->state = sw_type; + /* fall through */ - /* fall through */ + case sw_type: - case sw_type: + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + st->type = st->vlint.value; - st->type = st->vlint.value; - - if (ngx_http_v3_is_v2_frame(st->type) - || st->type == NGX_HTTP_V3_FRAME_DATA - || st->type == NGX_HTTP_V3_FRAME_GOAWAY - || st->type == NGX_HTTP_V3_FRAME_SETTINGS - || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID - || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH - || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) - { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; - } + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } - st->state = sw_length; - break; + st->state = sw_length; + break; - case sw_length: + case sw_length: - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - st->length = st->vlint.value; + st->length = st->vlint.value; - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse headers type:%ui, len:%ui", - st->type, st->length); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers type:%ui, len:%ui", + st->type, st->length); - if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { - st->state = st->length > 0 ? sw_skip : sw_type; - break; - } + if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { + st->state = st->length > 0 ? sw_skip : sw_type; + break; + } - if (st->length == 0) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } - st->state = sw_prefix; - break; + st->state = sw_prefix; + break; - case sw_skip: + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } - if (--st->length == 0) { st->state = sw_type; - } + break; - break; + case sw_prefix: - case sw_prefix: + ngx_http_v3_parse_start_local(b, &loc, st->length); - if (--st->length == 0) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc); - rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, ch); - if (rc != NGX_DONE) { - return rc; - } + ngx_http_v3_parse_end_local(b, &loc, &st->length); - st->state = sw_verify; - break; + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } - case sw_verify: + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); - if (rc != NGX_OK) { - return rc; - } + st->state = sw_verify; + break; - st->state = sw_field_rep; + case sw_verify: - /* fall through */ + rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } - case sw_field_rep: + st->state = sw_field_rep; - rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, - ch); + /* fall through */ - if (--st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + case sw_field_rep: - if (rc != NGX_DONE) { - return rc; - } + ngx_http_v3_parse_start_local(b, &loc, st->length); - if (st->length == 0) { - goto done; - } + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, + &loc); - return NGX_OK; - } + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + goto done; + } - return NGX_AGAIN; + return NGX_OK; + } + } done: @@ -335,8 +404,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, - ngx_http_v3_parse_field_section_prefix_t *st, u_char ch) + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -345,47 +415,54 @@ ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, sw_read_delta_base }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse field section prefix"); + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix"); - st->state = sw_req_insert_count; + st->state = sw_req_insert_count; - /* fall through */ + /* fall through */ - case sw_req_insert_count: + case sw_req_insert_count: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, ch); - if (rc != NGX_DONE) { - return rc; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b); + if (rc != NGX_DONE) { + return rc; + } - st->insert_count = st->pint.value; - st->state = sw_delta_base; - break; + st->insert_count = st->pint.value; + st->state = sw_delta_base; + break; - case sw_delta_base: + case sw_delta_base: - st->sign = (ch & 0x80) ? 1 : 0; - st->state = sw_read_delta_base; + if (b->pos == b->last) { + return NGX_AGAIN; + } - /* fall through */ + ch = *b->pos; - case sw_read_delta_base: + st->sign = (ch & 0x80) ? 1 : 0; + st->state = sw_read_delta_base; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + /* fall through */ - st->delta_base = st->pint.value; - goto done; - } + case sw_read_delta_base: - return NGX_AGAIN; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->delta_base = st->pint.value; + goto done; + } + } done: @@ -412,8 +489,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, - ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, u_char ch) + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -429,6 +507,12 @@ ngx_http_v3_parse_field_rep(ngx_connection_t *c, ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field representation"); + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); st->field.base = base; @@ -463,23 +547,23 @@ ngx_http_v3_parse_field_rep(ngx_connection_t *c, switch (st->state) { case sw_field_ri: - rc = ngx_http_v3_parse_field_ri(c, &st->field, ch); + rc = ngx_http_v3_parse_field_ri(c, &st->field, b); break; case sw_field_lri: - rc = ngx_http_v3_parse_field_lri(c, &st->field, ch); + rc = ngx_http_v3_parse_field_lri(c, &st->field, b); break; case sw_field_l: - rc = ngx_http_v3_parse_field_l(c, &st->field, ch); + rc = ngx_http_v3_parse_field_l(c, &st->field, b); break; case sw_field_pbi: - rc = ngx_http_v3_parse_field_pbi(c, &st->field, ch); + rc = ngx_http_v3_parse_field_pbi(c, &st->field, b); break; case sw_field_lpbi: - rc = ngx_http_v3_parse_field_lpbi(c, &st->field, ch); + rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b); break; default: @@ -500,8 +584,9 @@ ngx_http_v3_parse_field_rep(ngx_connection_t *c, static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, - u_char ch) + ngx_buf_t *b) { + u_char ch; ngx_uint_t n; ngx_http_core_srv_conf_t *cscf; enum { @@ -509,63 +594,70 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse literal huff:%ui, len:%ui", - st->huffman, st->length); + case sw_start: - n = st->length; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal huff:%ui, len:%ui", + st->huffman, st->length); - cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); + n = st->length; - 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; - } + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); - if (st->huffman) { - n = n * 8 / 5; - st->huffstate = 0; - } + 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; + } - st->last = ngx_pnalloc(c->pool, n + 1); - if (st->last == NULL) { - return NGX_ERROR; - } + if (st->huffman) { + n = n * 8 / 5; + st->huffstate = 0; + } + + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } - st->value.data = st->last; - st->state = sw_value; + st->value.data = st->last; + st->state = sw_value; - /* fall through */ + /* fall through */ - case sw_value: + case sw_value: - if (st->huffman) { - if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last, - st->length == 1, c->log) - != NGX_OK) - { - return NGX_ERROR; + if (b->pos == b->last) { + return NGX_AGAIN; } - } else { - *st->last++ = ch; - } + ch = *b->pos++; - if (--st->length) { - break; - } + if (st->huffman) { + if (ngx_http_v2_huff_decode(&st->huffstate, &ch, 1, &st->last, + st->length == 1, c->log) + != NGX_OK) + { + return NGX_ERROR; + } - st->value.len = st->last - st->value.data; - *st->last = '\0'; - goto done; - } + } else { + *st->last++ = ch; + } + + if (--st->length) { + break; + } - return NGX_AGAIN; + st->value.len = st->last - st->value.data; + *st->last = '\0'; + goto done; + } + } done: @@ -579,37 +671,46 @@ done: static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, - u_char ch) + ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, sw_index }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field ri"); + case sw_start: - st->dynamic = (ch & 0x40) ? 0 : 1; - st->state = sw_index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri"); - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_index: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); - if (rc != NGX_DONE) { - return rc; - } + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_index; - st->index = st->pint.value; - goto done; - } + /* fall through */ - return NGX_AGAIN; + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } done: @@ -634,8 +735,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -645,62 +747,76 @@ ngx_http_v3_parse_field_lri(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field lri"); + case sw_start: - st->dynamic = (ch & 0x10) ? 0 : 1; - st->state = sw_index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri"); - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_index: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch); - if (rc != NGX_DONE) { - return rc; - } + st->dynamic = (ch & 0x10) ? 0 : 1; + st->state = sw_index; - st->index = st->pint.value; - st->state = sw_value_len; - break; + /* fall through */ - case sw_value_len: + case sw_index: - st->literal.huffman = (ch & 0x80) ? 1 : 0; - st->state = sw_read_value_len; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } - /* fall through */ + st->index = st->pint.value; + st->state = sw_value_len; + break; - case sw_read_value_len: + case sw_value_len: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + if (b->pos == b->last) { + return NGX_AGAIN; + } - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - goto done; - } + ch = *b->pos; - st->state = sw_value; - break; + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; - case sw_value: + /* fall through */ - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_read_value_len: - st->value = st->literal.value; - goto done; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } - return NGX_AGAIN; + st->value = st->literal.value; + goto done; + } + } done: @@ -725,8 +841,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -737,77 +854,90 @@ ngx_http_v3_parse_field_l(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); + case sw_start: - st->literal.huffman = (ch & 0x08) ? 1 : 0; - st->state = sw_name_len; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_name_len: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch); - if (rc != NGX_DONE) { - return rc; - } + st->literal.huffman = (ch & 0x08) ? 1 : 0; + st->state = sw_name_len; - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - return NGX_ERROR; - } + /* fall through */ - st->state = sw_name; - break; + case sw_name_len: - case sw_name: + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } - st->name = st->literal.value; - st->state = sw_value_len; - break; + st->state = sw_name; + break; - case sw_value_len: + case sw_name: - st->literal.huffman = (ch & 0x80) ? 1 : 0; - st->state = sw_read_value_len; + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } - /* fall through */ + st->name = st->literal.value; + st->state = sw_value_len; + break; - case sw_read_value_len: + case sw_value_len: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + if (b->pos == b->last) { + return NGX_AGAIN; + } - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - goto done; - } + ch = *b->pos; - st->state = sw_value; - break; + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; - case sw_value: + /* fall through */ - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_read_value_len: - st->value = st->literal.value; - goto done; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } - return NGX_AGAIN; + st->value = st->literal.value; + goto done; + } + } done: @@ -822,7 +952,7 @@ done: static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { ngx_int_t rc; enum { @@ -830,28 +960,30 @@ ngx_http_v3_parse_field_pbi(ngx_connection_t *c, sw_index }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field pbi"); + case sw_start: - st->state = sw_index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi"); - /* fall through */ + st->state = sw_index; - case sw_index: + /* fall through */ - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_index: - st->index = st->pint.value; - goto done; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } - return NGX_AGAIN; + st->index = st->pint.value; + goto done; + } + } done: @@ -871,8 +1003,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -882,62 +1015,69 @@ ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse field lpbi"); + case sw_start: - st->state = sw_index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi"); - /* fall through */ + st->state = sw_index; - case sw_index: + /* fall through */ - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_index: - st->index = st->pint.value; - st->state = sw_value_len; - break; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } - case sw_value_len: + st->index = st->pint.value; + st->state = sw_value_len; + break; - st->literal.huffman = (ch & 0x80) ? 1 : 0; - st->state = sw_read_value_len; + case sw_value_len: - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_read_value_len: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - goto done; - } + /* fall through */ - st->state = sw_value; - break; + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } - case sw_value: + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + goto done; + } - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + st->state = sw_value; + break; - st->value = st->literal.value; - goto done; - } + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } - return NGX_AGAIN; + st->value = st->literal.value; + goto done; + } + } done: @@ -1001,8 +1141,9 @@ ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, - u_char ch) + ngx_buf_t *b) { + ngx_buf_t loc; ngx_int_t rc; enum { sw_start = 0, @@ -1016,188 +1157,208 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, sw_skip }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse control"); + case sw_start: - st->state = sw_first_type; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse control"); - /* fall through */ + st->state = sw_first_type; - case sw_first_type: - case sw_type: + /* fall through */ - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_first_type: + case sw_type: - st->type = st->vlint.value; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse frame type:%ui", st->type); + st->type = st->vlint.value; - if (st->state == sw_first_type - && st->type != NGX_HTTP_V3_FRAME_SETTINGS) - { - return NGX_HTTP_V3_ERR_MISSING_SETTINGS; - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame type:%ui", st->type); - if (st->state != sw_first_type - && st->type == NGX_HTTP_V3_FRAME_SETTINGS) - { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; - } + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } - if (ngx_http_v3_is_v2_frame(st->type) - || st->type == NGX_HTTP_V3_FRAME_DATA - || st->type == NGX_HTTP_V3_FRAME_HEADERS - || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) - { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; - } + if (st->state != sw_first_type + && st->type == NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } - st->state = sw_length; - break; + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_HEADERS + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } - case sw_length: + st->state = sw_length; + break; - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_length: - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse frame len:%uL", st->vlint.value); + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - st->length = st->vlint.value; - if (st->length == 0) { - st->state = sw_type; - break; - } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame len:%uL", st->vlint.value); - switch (st->type) { + st->length = st->vlint.value; + if (st->length == 0) { + st->state = sw_type; + break; + } - case NGX_HTTP_V3_FRAME_CANCEL_PUSH: - st->state = sw_cancel_push; - break; + switch (st->type) { - case NGX_HTTP_V3_FRAME_SETTINGS: - st->state = sw_settings; - break; + case NGX_HTTP_V3_FRAME_CANCEL_PUSH: + st->state = sw_cancel_push; + break; - case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: - st->state = sw_max_push_id; - break; + case NGX_HTTP_V3_FRAME_SETTINGS: + st->state = sw_settings; + break; + + case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: + st->state = sw_max_push_id; + break; + + case NGX_HTTP_V3_FRAME_GOAWAY: + st->state = sw_goaway; + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse skip unknown frame"); + st->state = sw_skip; + } - case NGX_HTTP_V3_FRAME_GOAWAY: - st->state = sw_goaway; break; - default: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse skip unknown frame"); - st->state = sw_skip; - } + case sw_cancel_push: - break; + ngx_http_v3_parse_start_local(b, &loc, st->length); - case sw_cancel_push: + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + ngx_http_v3_parse_end_local(b, &loc, &st->length); - if (--st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } - if (rc != NGX_DONE) { - return rc; - } + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_cancel_push(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_cancel_push(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } - st->state = sw_type; - break; + st->state = sw_type; + break; - case sw_settings: + case sw_settings: - rc = ngx_http_v3_parse_settings(c, &st->settings, ch); + ngx_http_v3_parse_start_local(b, &loc, st->length); - if (--st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_SETTINGS_ERROR; - } + rc = ngx_http_v3_parse_settings(c, &st->settings, &loc); - if (rc != NGX_DONE) { - return rc; - } + ngx_http_v3_parse_end_local(b, &loc, &st->length); - if (st->length == 0) { - st->state = sw_type; - } + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } - break; + if (rc != NGX_DONE) { + return rc; + } - case sw_max_push_id: + if (st->length == 0) { + st->state = sw_type; + } - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + break; - if (--st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + case sw_max_push_id: - if (rc != NGX_DONE) { - return rc; - } + ngx_http_v3_parse_start_local(b, &loc, st->length); - rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - st->state = sw_type; - break; + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } - case sw_goaway: + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); + rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } - if (--st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } + st->state = sw_type; + break; - if (rc != NGX_DONE) { - return rc; - } + case sw_goaway: - rc = ngx_http_v3_goaway(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } + ngx_http_v3_parse_start_local(b, &loc, st->length); - st->state = sw_type; - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - case sw_skip: + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_goaway(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } - if (--st->length == 0) { st->state = sw_type; - } + break; - break; - } + case sw_skip: - return NGX_AGAIN; + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } } static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, - ngx_http_v3_parse_settings_t *st, u_char ch) + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b) { ngx_int_t rc; enum { @@ -1206,42 +1367,44 @@ ngx_http_v3_parse_settings(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings"); + case sw_start: - st->state = sw_id; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse settings"); - /* fall through */ + st->state = sw_id; - case sw_id: + /* fall through */ - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_id: - st->id = st->vlint.value; - st->state = sw_value; - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - case sw_value: + st->id = st->vlint.value; + st->state = sw_value; + break; - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_value: - if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { - return NGX_HTTP_V3_ERR_SETTINGS_ERROR; - } + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - goto done; - } + if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } - return NGX_AGAIN; + goto done; + } + } done: @@ -1254,8 +1417,9 @@ done: static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, - u_char ch) + ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -1265,98 +1429,102 @@ ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, sw_duplicate }; - if (st->state == sw_start) { + for ( ;; ) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse encoder instruction"); + if (st->state == sw_start) { - if (ch & 0x80) { - /* Insert With Name Reference */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction"); - st->state = sw_inr; + if (b->pos == b->last) { + return NGX_AGAIN; + } - } else if (ch & 0x40) { - /* Insert With Literal Name */ + ch = *b->pos; - st->state = sw_iln; + if (ch & 0x80) { + /* Insert With Name Reference */ - } else if (ch & 0x20) { - /* Set Dynamic Table Capacity */ + st->state = sw_inr; - st->state = sw_capacity; + } else if (ch & 0x40) { + /* Insert With Literal Name */ - } else { - /* Duplicate */ + st->state = sw_iln; - st->state = sw_duplicate; - } - } + } else if (ch & 0x20) { + /* Set Dynamic Table Capacity */ - switch (st->state) { + st->state = sw_capacity; - case sw_inr: + } else { + /* Duplicate */ - rc = ngx_http_v3_parse_field_inr(c, &st->field, ch); - if (rc != NGX_DONE) { - return rc; + st->state = sw_duplicate; + } } - goto done; - - case sw_iln: + switch (st->state) { - rc = ngx_http_v3_parse_field_iln(c, &st->field, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_inr: - goto done; + rc = ngx_http_v3_parse_field_inr(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } - case sw_capacity: + st->state = sw_start; + break; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_iln: - rc = ngx_http_v3_set_capacity(c, st->pint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_parse_field_iln(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } - goto done; + st->state = sw_start; + break; - case sw_duplicate: + case sw_capacity: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); - if (rc != NGX_DONE) { - return rc; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_duplicate(c, st->pint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_set_capacity(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } - goto done; - } + st->state = sw_start; + break; - return NGX_AGAIN; + default: /* sw_duplicate */ -done: + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse encoder instruction done"); + rc = ngx_http_v3_duplicate(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } - st->state = sw_start; - return NGX_AGAIN; + st->state = sw_start; + break; + } + } } static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -1366,63 +1534,77 @@ ngx_http_v3_parse_field_inr(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field inr"); + case sw_start: - st->dynamic = (ch & 0x40) ? 0 : 1; - st->state = sw_name_index; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr"); - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_name_index: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); - if (rc != NGX_DONE) { - return rc; - } + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_name_index; - st->index = st->pint.value; - st->state = sw_value_len; - break; + /* fall through */ - case sw_value_len: + case sw_name_index: - st->literal.huffman = (ch & 0x80) ? 1 : 0; - st->state = sw_read_value_len; + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } - /* fall through */ + st->index = st->pint.value; + st->state = sw_value_len; + break; - case sw_read_value_len: + case sw_value_len: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + if (b->pos == b->last) { + return NGX_AGAIN; + } - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - st->value.len = 0; - goto done; - } + ch = *b->pos; - st->state = sw_value; - break; + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; - case sw_value: + /* fall through */ - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_read_value_len: - st->value = st->literal.value; - goto done; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } - return NGX_AGAIN; + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } done: @@ -1443,8 +1625,9 @@ done: static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, - ngx_http_v3_parse_field_t *st, u_char ch) + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -1455,79 +1638,92 @@ ngx_http_v3_parse_field_iln(ngx_connection_t *c, sw_value }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse field iln"); + case sw_start: - st->literal.huffman = (ch & 0x20) ? 1 : 0; - st->state = sw_name_len; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln"); - /* fall through */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - case sw_name_len: + ch = *b->pos; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, ch); - if (rc != NGX_DONE) { - return rc; - } + st->literal.huffman = (ch & 0x20) ? 1 : 0; + st->state = sw_name_len; - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - return NGX_ERROR; - } + /* fall through */ - st->state = sw_name; - break; + case sw_name_len: - case sw_name: + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } - st->name = st->literal.value; - st->state = sw_value_len; - break; + st->state = sw_name; + break; - case sw_value_len: + case sw_name: - st->literal.huffman = (ch & 0x80) ? 1 : 0; - st->state = sw_read_value_len; + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } - /* fall through */ + st->name = st->literal.value; + st->state = sw_value_len; + break; - case sw_read_value_len: + case sw_value_len: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + if (b->pos == b->last) { + return NGX_AGAIN; + } - st->literal.length = st->pint.value; - if (st->literal.length == 0) { - st->value.len = 0; - goto done; - } + ch = *b->pos; - st->state = sw_value; - break; + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; - case sw_value: + /* fall through */ - rc = ngx_http_v3_parse_literal(c, &st->literal, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_read_value_len: - st->value = st->literal.value; - goto done; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: - return NGX_AGAIN; + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } done: @@ -1547,8 +1743,9 @@ done: static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, - u_char ch) + ngx_buf_t *b) { + u_char ch; ngx_int_t rc; enum { sw_start = 0, @@ -1557,88 +1754,90 @@ ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, sw_inc_insert_count }; - if (st->state == sw_start) { - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse decoder instruction"); + for ( ;; ) { - if (ch & 0x80) { - /* Section Acknowledgment */ + if (st->state == sw_start) { - st->state = sw_ack_section; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction"); - } else if (ch & 0x40) { - /* Stream Cancellation */ + if (b->pos == b->last) { + return NGX_AGAIN; + } - st->state = sw_cancel_stream; + ch = *b->pos; - } else { - /* Insert Count Increment */ + if (ch & 0x80) { + /* Section Acknowledgment */ - st->state = sw_inc_insert_count; - } - } + st->state = sw_ack_section; - switch (st->state) { + } else if (ch & 0x40) { + /* Stream Cancellation */ - case sw_ack_section: + st->state = sw_cancel_stream; - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, ch); - if (rc != NGX_DONE) { - return rc; - } + } else { + /* Insert Count Increment */ - rc = ngx_http_v3_ack_section(c, st->pint.value); - if (rc != NGX_OK) { - return rc; + st->state = sw_inc_insert_count; + } } - goto done; + switch (st->state) { - case sw_cancel_stream: + case sw_ack_section: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); - if (rc != NGX_DONE) { - return rc; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_cancel_stream(c, st->pint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_ack_section(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } - goto done; + st->state = sw_start; + break; - case sw_inc_insert_count: + case sw_cancel_stream: - rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, ch); - if (rc != NGX_DONE) { - return rc; - } + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } - rc = ngx_http_v3_inc_insert_count(c, st->pint.value); - if (rc != NGX_OK) { - return rc; - } + rc = ngx_http_v3_cancel_stream(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } - goto done; - } + st->state = sw_start; + break; - return NGX_AGAIN; + case sw_inc_insert_count: -done: + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse decoder instruction done"); + rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } - st->state = sw_start; - return NGX_AGAIN; + st->state = sw_start; + break; + } + } } ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, - u_char ch) + ngx_buf_t *b) { ngx_int_t rc; enum { @@ -1648,75 +1847,78 @@ ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, sw_skip }; - switch (st->state) { + for ( ;; ) { - case sw_start: + switch (st->state) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); + case sw_start: - st->state = sw_type; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); - /* fall through */ + st->state = sw_type; - case sw_type: + /* fall through */ - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_type: - st->type = st->vlint.value; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { - /* trailers */ - goto done; - } + st->type = st->vlint.value; - if (ngx_http_v3_is_v2_frame(st->type) - || st->type == NGX_HTTP_V3_FRAME_GOAWAY - || st->type == NGX_HTTP_V3_FRAME_SETTINGS - || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID - || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH - || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) - { - return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; - } + if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { + /* trailers */ + goto done; + } - st->state = sw_length; - break; + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } - case sw_length: + st->state = sw_length; + break; - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + case sw_length: - st->length = st->vlint.value; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse data type:%ui, len:%ui", - st->type, st->length); + st->length = st->vlint.value; - if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { - st->state = sw_skip; - break; - } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data type:%ui, len:%ui", + st->type, st->length); - st->state = sw_type; - return NGX_OK; + if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { + st->state = sw_skip; + break; + } + + st->state = sw_type; + return NGX_OK; + + case sw_skip: - case sw_skip: + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } - if (--st->length == 0) { st->state = sw_type; + break; } - - break; } - return NGX_AGAIN; - done: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); @@ -1728,7 +1930,7 @@ done: ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, - u_char ch) + ngx_buf_t *b) { ngx_int_t rc; enum { @@ -1740,76 +1942,64 @@ ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, sw_unknown }; - switch (st->state) { - case sw_start: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); + for ( ;; ) { - st->state = sw_type; + switch (st->state) { + case sw_start: - /* fall through */ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); - case sw_type: + st->state = sw_type; - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, ch); - if (rc != NGX_DONE) { - return rc; - } + /* fall through */ - rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } + case sw_type: - switch (st->vlint.value) { - case NGX_HTTP_V3_STREAM_CONTROL: - st->state = sw_control; - break; + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } - case NGX_HTTP_V3_STREAM_ENCODER: - st->state = sw_encoder; - break; + rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } - case NGX_HTTP_V3_STREAM_DECODER: - st->state = sw_decoder; - break; + switch (st->vlint.value) { + case NGX_HTTP_V3_STREAM_CONTROL: + st->state = sw_control; + break; - default: - st->state = sw_unknown; - } + case NGX_HTTP_V3_STREAM_ENCODER: + st->state = sw_encoder; + break; - break; + case NGX_HTTP_V3_STREAM_DECODER: + st->state = sw_decoder; + break; - case sw_control: + default: + st->state = sw_unknown; + } - rc = ngx_http_v3_parse_control(c, &st->u.control, ch); - if (rc != NGX_OK) { - return rc; - } + break; - break; + case sw_control: - case sw_encoder: + return ngx_http_v3_parse_control(c, &st->u.control, b); - rc = ngx_http_v3_parse_encoder(c, &st->u.encoder, ch); - if (rc != NGX_OK) { - return rc; - } + case sw_encoder: - break; + return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); - case sw_decoder: + case sw_decoder: - rc = ngx_http_v3_parse_decoder(c, &st->u.decoder, ch); - if (rc != NGX_OK) { - return rc; - } + return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); - break; + case sw_unknown: - case sw_unknown: - break; + b->pos = b->last; + return NGX_AGAIN; + } } - - return NGX_AGAIN; } diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index 5ca3a410f..ba004db5d 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -136,11 +136,11 @@ typedef struct { */ ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, - ngx_http_v3_parse_headers_t *st, u_char ch); + 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, u_char ch); + 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, u_char ch); + ngx_http_v3_parse_uni_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 0bd585317..fb9ea8ff1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -207,6 +207,7 @@ ngx_http_v3_cleanup_request(void *data) static void ngx_http_v3_process_request(ngx_event_t *rev) { + u_char *p; ssize_t n; ngx_buf_t *b; ngx_int_t rc; @@ -273,7 +274,9 @@ ngx_http_v3_process_request(ngx_event_t *rev) b->last = b->start + n; } - rc = ngx_http_v3_parse_headers(c, st, *b->pos); + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); if (rc > 0) { ngx_http_v3_finalize_connection(c, rc, @@ -302,8 +305,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } - b->pos++; - r->request_length++; + r->request_length += b->pos - p; if (rc == NGX_AGAIN) { continue; @@ -1024,6 +1026,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { off_t max; size_t size; + u_char *p; ngx_int_t rc; ngx_buf_t *b; ngx_uint_t last; @@ -1078,9 +1081,11 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) while (cl->buf->pos < cl->buf->last) { if (st->length == 0) { - r->request_length++; + p = cl->buf->pos; + + rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); - rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++); + r->request_length += cl->buf->pos - p; if (rc == NGX_AGAIN) { continue; diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 1b0f91454..1cff29319 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -168,7 +168,8 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) { u_char buf[128]; ssize_t n; - ngx_int_t rc, i; + ngx_buf_t b; + ngx_int_t rc; ngx_connection_t *c; ngx_http_v3_uni_stream_t *us; @@ -177,6 +178,8 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + ngx_memzero(&b, sizeof(ngx_buf_t)); + while (rev->ready) { n = c->recv(c, buf, sizeof(buf)); @@ -201,25 +204,25 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) break; } - for (i = 0; i < n; i++) { + b.pos = buf; + b.last = buf + n; - rc = ngx_http_v3_parse_uni(c, &us->parse, buf[i]); + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); - if (rc == NGX_DONE) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 read done"); - ngx_http_v3_close_uni_stream(c); - return; - } + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; + } - if (rc > 0) { - goto failed; - } + if (rc > 0) { + goto failed; + } - if (rc != NGX_AGAIN) { - rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; - goto failed; - } + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; } } -- cgit v1.2.3 From 47c993da63a1351193207588d7f9ef1327b1744b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 1 Sep 2021 11:12:23 +0300 Subject: README: HTTP/3 trailers are now supported. --- README | 1 - 1 file changed, 1 deletion(-) diff --git a/README b/README index d4b8bf5a8..cdbfde7af 100644 --- a/README +++ b/README @@ -58,7 +58,6 @@ Experimental QUIC support for nginx - Explicit Congestion Notification (ECN) as specified in quic-recovery [5] - A connection with the spin bit succeeds and the bit is spinning - Structured Logging - - HTTP/3 trailers Since the code is experimental and still under development, a lot of things may not work as expected, for example: -- cgit v1.2.3 From 9985ab86bf0eb3a58f26d0396c1828d4a70faf03 Mon Sep 17 00:00:00 2001 From: Mariano Di Martino Date: Fri, 3 Sep 2021 14:23:50 +0300 Subject: QUIC: fixed null pointer dereference in MAX_DATA handler. If a MAX_DATA frame was received before any stream was created, then the worker process would crash in nginx_quic_handle_max_data_frame() while traversing the stream tree. The issue is solved by adding a check that makes sure the tree is not empty. --- src/event/quic/ngx_event_quic_streams.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index bff41b20c..ef8a9df47 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1000,7 +1000,9 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, return NGX_OK; } - if (qc->streams.sent >= qc->streams.send_max_data) { + if (tree->root != tree->sentinel + && qc->streams.sent >= qc->streams.send_max_data) + { for (node = ngx_rbtree_min(tree->root, tree->sentinel); node; -- cgit v1.2.3 From 465362e0664a4fe31cb5df8e757bc99b3c68f5fa Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 6 Sep 2021 16:59:00 +0300 Subject: QUIC: store QUIC connection fd in stream fake connection. Previously it had -1 as fd. This fixes proxying, which relies on downstream connection having a real fd. Also, this reduces diff to the default branch for ngx_close_connection(). --- src/core/ngx_connection.c | 12 ++++++------ src/event/quic/ngx_event_quic_streams.c | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 974f48c9a..4a7d2fe79 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1185,6 +1185,11 @@ ngx_close_connection(ngx_connection_t *c) ngx_uint_t log_error, level; ngx_socket_t fd; + if (c->fd == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed"); + return; + } + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1193,7 +1198,7 @@ ngx_close_connection(ngx_connection_t *c) ngx_del_timer(c->write); } - if (!c->shared && c->fd != (ngx_socket_t) -1) { + if (!c->shared) { if (ngx_del_conn) { ngx_del_conn(c, NGX_CLOSE_EVENT); @@ -1225,11 +1230,6 @@ ngx_close_connection(ngx_connection_t *c) ngx_free_connection(c); - if (c->fd == (ngx_socket_t) -1) { - ngx_log_debug0(NGX_LOG_DEBUG_CORE, c->log, 0, "connection has no fd"); - return; - } - fd = c->fd; c->fd = (ngx_socket_t) -1; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index ef8a9df47..a4f4cb57c 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -440,6 +440,8 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qs->connection = sc; sc->quic = qs; + sc->fd = c->fd; + sc->shared = 1; sc->type = SOCK_STREAM; sc->pool = pool; sc->ssl = c->ssl; -- cgit v1.2.3 From 7a45071cb6a460c999a3ad5f978f8cfd5d792cdb Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Thu, 9 Sep 2021 15:34:00 +0300 Subject: Changed the OpenSSL QUIC support detection. As was changed in 253cf267f95a. --- auto/lib/openssl/conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 94ccd5795..f0dd1e1e8 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -150,7 +150,7 @@ if [ $USE_OPENSSL_QUIC = YES ]; then ngx_feature_incs="#include " ngx_feature_path= ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" - ngx_feature_test="SSL_CTX_set_quic_method(NULL, NULL)" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" . auto/feature if [ $ngx_found = no ]; then -- cgit v1.2.3 From ef94770e163f1b8e58d18ee1f1bbd45c4a754d33 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Thu, 9 Sep 2021 15:40:08 +0300 Subject: QUIC: macro style. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 669bae9bb..f589d74ac 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -549,7 +549,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } -#if NGX_OPENSSL_QUIC_ZRTT_CTX +#if (NGX_OPENSSL_QUIC_ZRTT_CTX) if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic SSL_set_quic_early_data_context() failed"); -- cgit v1.2.3 From 4208e67e9808e2d458c0b705184308c41dc72bdf Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 9 Sep 2021 19:12:27 +0300 Subject: QUIC: removed Firefox workaround for trailing zeroes in datagrams. This became unnecessary after discarding invalid packets since a6784cf32c13. --- src/event/quic/ngx_event_quic.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 076e19422..478fc8461 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -730,11 +730,6 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) /* b->pos is at header end, adjust by actual packet length */ b->pos = pkt.data + pkt.len; - /* firefox workaround: skip zero padding at the end of quic packet */ - while (b->pos < b->last && *(b->pos) == 0) { - b->pos++; - } - p = b->pos; } -- cgit v1.2.3 From 590996466ca568dbc7de5d40b7062dc254d211c2 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 9 Sep 2021 15:47:29 +0300 Subject: HTTP/3: reading body buffering in filters. This change follows similar changes in HTTP/1 and HTTP/2 in 9cf043a5d9ca. --- src/http/v3/ngx_http_v3_request.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fb9ea8ff1..f11c32da9 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -822,7 +822,7 @@ ngx_http_v3_read_request_body(ngx_http_request_t *r) return rc; } - if (rb->rest == 0) { + if (rb->rest == 0 && rb->last_saved) { /* the whole request body was pre-read */ r->request_body_no_buffering = 0; rb->post_handler(r); @@ -895,6 +895,7 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) size_t size; ssize_t n; ngx_int_t rc; + ngx_uint_t flush; ngx_chain_t out; ngx_connection_t *c; ngx_http_request_body_t *rb; @@ -902,12 +903,17 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) c = r->connection; rb = r->request_body; + flush = 1; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read client request body"); for ( ;; ) { for ( ;; ) { + if (rb->rest == 0) { + break; + } + if (rb->buf->last == rb->buf->end) { /* update chains */ @@ -931,12 +937,25 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) return NGX_AGAIN; } + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "busy buffers after request body flush"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } + flush = 0; rb->buf->pos = rb->buf->start; rb->buf->last = rb->buf->start; } @@ -948,6 +967,10 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) size = (size_t) rest; } + if (size == 0) { + break; + } + n = c->recv(c, rb->buf->last, size); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -970,6 +993,7 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) /* pass buffer to request body filter chain */ + flush = 0; out.buf = rb->buf; out.next = NULL; @@ -991,11 +1015,19 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client request body rest %O", rb->rest); - if (rb->rest == 0) { + if (flush) { + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + } + + if (rb->rest == 0 && rb->last_saved) { break; } - if (!c->read->ready) { + if (!c->read->ready || rb->rest == 0) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(c->read, clcf->client_body_timeout); -- cgit v1.2.3 From 0ac1f6fd47b59bd59abb4c8465e622e60f666ee0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Sep 2021 16:25:08 +0300 Subject: HTTP/3: implemented QPACK Huffman encoding for response fields. --- src/http/v3/ngx_http_v3_encode.c | 87 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c index 2740ccffa..a62f5d30f 100644 --- a/src/http/v3/ngx_http_v3_encode.c +++ b/src/http/v3/ngx_http_v3_encode.c @@ -137,6 +137,9 @@ 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) { + size_t hlen; + u_char *p1, *p2; + /* Literal Field Line With Name Reference */ if (p == NULL) { @@ -148,11 +151,28 @@ ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, *p = dynamic ? 0x40 : 0x50; p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + p1 = p; *p = 0; p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); if (data) { - p = ngx_cpymem(p, data, len); + p2 = p; + hlen = ngx_http_v2_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } } return (uintptr_t) p; @@ -162,6 +182,9 @@ 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) { + size_t hlen; + u_char *p1, *p2; + /* Literal Field Line With Literal Name */ if (p == NULL) { @@ -171,16 +194,50 @@ ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) + value->len; } + p1 = p; *p = 0x20; p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); - ngx_strlow(p, name->data, name->len); - p += name->len; + p2 = p; + hlen = ngx_http_v2_huff_encode(name->data, name->len, p, 1); + + if (hlen) { + p = p1; + *p = 0x28; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + ngx_strlow(p, name->data, name->len); + p += name->len; + } + p1 = p; *p = 0; p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); - p = ngx_cpymem(p, value->data, value->len); + p2 = p; + hlen = ngx_http_v2_huff_encode(value->data, value->len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, value->data, value->len); + } return (uintptr_t) p; } @@ -205,6 +262,9 @@ uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, size_t len) { + size_t hlen; + u_char *p1, *p2; + /* Literal Field Line With Post-Base Name Reference */ if (p == NULL) { @@ -216,11 +276,28 @@ ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, *p = 0; p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + p1 = p; *p = 0; p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); if (data) { - p = ngx_cpymem(p, data, len); + p2 = p; + hlen = ngx_http_v2_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } } return (uintptr_t) p; -- cgit v1.2.3 From ee5d9279288beec74e563c5fd3e1d8dd0171fb70 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Sep 2021 16:25:23 +0300 Subject: HTTP/3: Huffman encoding for the Content-Type response field. --- src/http/v3/ngx_http_v3_filter_module.c | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 71aa839e1..45063524b 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -359,35 +359,35 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) } if (r->headers_out.content_type.len) { - n = r->headers_out.content_type.len; - if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { - n += sizeof("; charset=") - 1 + r->headers_out.charset.len; - } + n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, - NULL, n); + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } - p = b->last; - b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, - r->headers_out.content_type.len); + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { - b->last = ngx_cpymem(b->last, "; charset=", - sizeof("; charset=") - 1); - b->last = ngx_cpymem(b->last, r->headers_out.charset.data, - r->headers_out.charset.len); + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); - /* update r->headers_out.content_type for possible logging */ + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); - r->headers_out.content_type.len = b->last - p; - r->headers_out.content_type.data = p; + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = n; + r->headers_out.content_type.data = p - n; } + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + r->headers_out.content_type.data, + r->headers_out.content_type.len); } if (r->headers_out.content_length == NULL) { -- cgit v1.2.3 From 12cf623bc2f77ffd0a0425c8f1d07cf912160ef6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Sep 2021 16:25:31 +0300 Subject: HTTP/3: Huffman encoding for the Last-Modified response field. --- src/http/v3/ngx_http_v3_filter_module.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 45063524b..0fcbed01f 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -411,11 +411,18 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, - sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; - b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_http_time(p, r->headers_out.last_modified_time); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, + p, n); } if (host.data) { -- cgit v1.2.3 From bacd7ef0bed22b1b447a0de765fbd13721e80afb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Sep 2021 16:25:32 +0300 Subject: HTTP/3: Huffman encoding for the Location response field. --- src/http/v3/ngx_http_v3_filter_module.c | 114 +++++++++++++++----------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 0fcbed01f..0ab31b5d6 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -95,7 +95,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) u_char *p; size_t len, n; ngx_buf_t *b; - ngx_str_t host; + ngx_str_t host, location; ngx_uint_t i, port; ngx_chain_t *out, *hl, *cl, **ll; ngx_list_part_t *part; @@ -229,51 +229,70 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } - if (r->headers_out.location - && r->headers_out.location->value.len - && r->headers_out.location->value.data[0] == '/' - && clcf->absolute_redirect) - { - r->headers_out.location->hash = 0; + if (r->headers_out.location && r->headers_out.location->value.len) { - if (clcf->server_name_in_redirect) { - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - host = cscf->server_name; + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; - } else if (r->headers_in.server.len) { - host = r->headers_in.server; + } else if (r->headers_in.server.len) { + host = r->headers_in.server; - } else { - host.len = NGX_SOCKADDR_STRLEN; - host.data = addr; + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); - if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { return NGX_ERROR; } - } - port = ngx_inet_get_port(c->local_sockaddr); + p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); + p = ngx_cpymem(p, host.data, host.len); - n = sizeof("https://") - 1 + host.len - + r->headers_out.location->value.len; + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } - if (clcf->port_in_redirect) { - port = (port == 443) ? 0 : port; + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); - } else { - port = 0; - } + /* update r->headers_out.location->value for possible logging */ - if (port) { - n += sizeof(":65535") - 1; + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); } - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_LOCATION, NULL, n); + r->headers_out.location->hash = 0; - } else { - ngx_str_null(&host); - port = 0; + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, + r->headers_out.location->value.len); } #if (NGX_HTTP_GZIP) @@ -425,34 +444,11 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) p, n); } - if (host.data) { - n = sizeof("https://") - 1 + host.len - + r->headers_out.location->value.len; - - if (port) { - n += ngx_sprintf(b->last, ":%ui", port) - b->last; - } - + if (r->headers_out.location && r->headers_out.location->value.len) { b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LOCATION, - NULL, n); - - p = b->last; - b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); - b->last = ngx_cpymem(b->last, host.data, host.len); - - if (port) { - b->last = ngx_sprintf(b->last, ":%ui", port); - } - - b->last = ngx_cpymem(b->last, r->headers_out.location->value.data, - r->headers_out.location->value.len); - - /* update r->headers_out.location->value for possible logging */ - - r->headers_out.location->value.len = b->last - p; - r->headers_out.location->value.data = p; - ngx_str_set(&r->headers_out.location->key, "Location"); + NGX_HTTP_V3_HEADER_LOCATION, + r->headers_out.location->value.data, + r->headers_out.location->value.len); } #if (NGX_HTTP_GZIP) -- cgit v1.2.3 From 10cafa75a4c3ccc1df52e55225c28c6b4110b229 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Sep 2021 16:25:37 +0300 Subject: HTTP/3: added debug logging of response fields. Because of QPACK compression it's hard to see what fields are actually sent by the server. --- src/http/v3/ngx_http_v3_filter_module.c | 41 ++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 0ab31b5d6..764d97957 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -340,6 +340,10 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, 0, 0, 0); + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \":status: %03ui\"", + r->headers_out.status); + if (r->headers_out.status == NGX_HTTP_OK) { b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_STATUS_200); @@ -365,12 +369,19 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n = sizeof("nginx") - 1; } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"server: %*s\"", n, p); + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_SERVER, p, n); } if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_DATE, ngx_cached_http_time.data, @@ -403,13 +414,23 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) r->headers_out.content_type.data = p - n; } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, r->headers_out.content_type.data, r->headers_out.content_type.len); } - if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + if (r->headers_out.content_length_n > 0) { p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); n = p - b->last; @@ -421,7 +442,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); - } else if (r->headers_out.content_length_n == 0) { + } else { b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); } @@ -439,12 +460,19 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) ngx_http_time(p, r->headers_out.last_modified_time); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"last-modified: %*s\"", n, p); + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LAST_MODIFIED, p, n); } if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LOCATION, r->headers_out.location->value.data, @@ -453,6 +481,9 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) #if (NGX_HTTP_GZIP) if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } @@ -477,6 +508,10 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) continue; } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, &header[i].key, &header[i].value); @@ -1417,7 +1452,7 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r, } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 trailer: \"%V: %V\"", + "http3 output trailer: \"%V: %V\"", &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, -- cgit v1.2.3 From bd89c448b7e7beb15409e2abe2f174a36a7a0823 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Tue, 14 Sep 2021 12:09:13 +0300 Subject: Removed NGX_OPENSSL_QUIC macro, NGX_QUIC is enough. --- auto/lib/openssl/conf | 4 +--- src/core/ngx_core.h | 2 +- src/http/ngx_http.c | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index f0dd1e1e8..61ba8d671 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -145,7 +145,7 @@ fi if [ $USE_OPENSSL_QUIC = YES ]; then ngx_feature="OpenSSL QUIC support" - ngx_feature_name="NGX_OPENSSL_QUIC" + ngx_feature_name="NGX_QUIC" ngx_feature_run=no ngx_feature_incs="#include " ngx_feature_path= @@ -165,8 +165,6 @@ with nginx by using --with-openssl= option. END exit 1 fi - - have=NGX_QUIC . auto/have fi diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 256836546..88db7dc98 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -83,7 +83,7 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include #if (NGX_OPENSSL) #include -#if (NGX_OPENSSL_QUIC) +#if (NGX_QUIC) #include #endif #endif diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index e1604af60..c9aa3761b 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1372,7 +1372,7 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif -#if (NGX_HTTP_QUIC && !defined NGX_OPENSSL_QUIC) +#if (NGX_HTTP_QUIC && !defined NGX_QUIC) if (lsopt->quic) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, -- cgit v1.2.3 From 9d7f2e79176b3fc73c06e8ba1594f287b4536bbe Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 16 Sep 2021 13:13:22 +0300 Subject: HTTP/3: added CONNECT and TRACE methods rejection. It has got lost in e1eb7f4ca9f1, let alone a subsequent update in 63c66b7cc07c. --- src/http/v3/ngx_http_v3_request.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f11c32da9..793a34816 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -45,7 +45,8 @@ static const struct { { ngx_string("LOCK"), NGX_HTTP_LOCK }, { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, { ngx_string("PATCH"), NGX_HTTP_PATCH }, - { ngx_string("TRACE"), NGX_HTTP_TRACE } + { ngx_string("TRACE"), NGX_HTTP_TRACE }, + { ngx_string("CONNECT"), NGX_HTTP_CONNECT } }; @@ -780,6 +781,18 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) } } + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + return NGX_OK; failed: -- cgit v1.2.3 From 00bb4e4b8d2c6fa9a9323b158cfc22071b848e87 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 9 Sep 2021 16:55:00 +0300 Subject: QUIC: separate event handling functions. The functions ngx_quic_handle_read_event() and ngx_quic_handle_write_event() are added. Previously this code was a part of ngx_handle_read_event() and ngx_handle_write_event(). The change simplifies ngx_handle_read_event() and ngx_handle_write_event() by moving QUIC-related code to a QUIC source file. --- src/event/ngx_event.c | 32 +++++++------------------------- src/event/quic/ngx_event_quic.h | 2 ++ src/event/quic/ngx_event_quic_streams.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index d61eda25e..aa47ccf9f 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -273,15 +273,7 @@ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) c = rev->data; if (c->quic) { - - if (!rev->active && !rev->ready) { - rev->active = 1; - - } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) { - rev->active = 0; - } - - return NGX_OK; + return ngx_quic_handle_read_event(rev, flags); } #endif @@ -358,28 +350,18 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) c = wev->data; - if (lowat) { - if (ngx_send_lowat(c, lowat) == NGX_ERROR) { - return NGX_ERROR; - } - } - #if (NGX_QUIC) - if (c->quic) { + return ngx_quic_handle_write_event(wev, lowat); + } +#endif - if (!wev->active && !wev->ready) { - wev->active = 1; - - } else if (wev->active && wev->ready) { - wev->active = 0; + if (lowat) { + if (ngx_send_lowat(c, lowat) == NGX_ERROR) { + return NGX_ERROR; } - - return NGX_OK; } -#endif - if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index d425cee31..dda1e385e 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -93,6 +93,8 @@ void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); uint32_t ngx_quic_version(ngx_connection_t *c); +ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); +ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); 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_streams.c b/src/event/quic/ngx_event_quic_streams.c index a4f4cb57c..69c7220cf 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1470,3 +1470,31 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) return NGX_OK; } + + +ngx_int_t +ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) +{ + if (!rev->active && !rev->ready) { + rev->active = 1; + + } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) { + rev->active = 0; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat) +{ + if (!wev->active && !wev->ready) { + wev->active = 1; + + } else if (wev->active && wev->ready) { + wev->active = 0; + } + + return NGX_OK; +} -- cgit v1.2.3 From 0f3eb180d2af781c98b84c1e5e2b4fe4c0c3be54 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 17 Sep 2021 16:32:23 +0300 Subject: HTTP/3: make ngx_http_log_error() static again. This function was only referenced from ngx_http_v3_create_push_request() to initialize push connection log. Now the log handler is copied from the parent request connection. The change reduces diff to the default branch. --- src/http/ngx_http.h | 1 - src/http/ngx_http_request.c | 3 ++- src/http/v3/ngx_http_v3_filter_module.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index fb4157715..708defebc 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -92,7 +92,6 @@ ngx_int_t ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, void ngx_http_init_connection(ngx_connection_t *c); void ngx_http_close_connection(ngx_connection_t *c); -u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); #if (NGX_HTTP_SSL && defined SSL_CTRL_SET_TLSEXT_HOSTNAME) int ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 30200075e..2b838cfc3 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -50,6 +50,7 @@ static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); static void ngx_http_log_request(ngx_http_request_t *r); +static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); static u_char *ngx_http_log_error_handler(ngx_http_request_t *r, ngx_http_request_t *sr, u_char *buf, size_t len); @@ -3829,7 +3830,7 @@ ngx_http_close_connection(ngx_connection_t *c) } -u_char * +static u_char * ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len) { u_char *p; diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 764d97957..5af17e40a 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -917,7 +917,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ctx->request = NULL; ctx->current_request = NULL; - c->log->handler = ngx_http_log_error; + c->log->handler = pc->log->handler; c->log->data = ctx; c->log->action = "processing pushed request headers"; -- cgit v1.2.3 From 3321ca0c833c88ef4870f3abb8870beb622e2171 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Tue, 21 Sep 2021 14:46:17 +0300 Subject: Configure: simplified condition. --- auto/lib/openssl/conf | 3 --- 1 file changed, 3 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 61ba8d671..a9cb0094e 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -165,10 +165,7 @@ with nginx by using --with-openssl= option. END exit 1 fi -fi - -if [ $USE_OPENSSL_QUIC = YES ]; then ngx_feature="OpenSSL QUIC 0-RTT context" ngx_feature_name="NGX_OPENSSL_QUIC_ZRTT_CTX" ngx_feature_run=no -- cgit v1.2.3 From cafaea71e9369bcdfd44d9b345ed9af9a47ea33e Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Tue, 21 Sep 2021 14:46:25 +0300 Subject: Configure: ordered directories. --- auto/make | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auto/make b/auto/make index 2b8f6ea41..25ee3fb56 100644 --- a/auto/make +++ b/auto/make @@ -6,13 +6,13 @@ echo "creating $NGX_MAKEFILE" mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ + $NGX_OBJS/src/event/quic \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ - $NGX_OBJS/src/misc \ - $NGX_OBJS/src/event/quic + $NGX_OBJS/src/misc ngx_objs_dir=$NGX_OBJS$ngx_regex_dirsep -- cgit v1.2.3 From af2121267b9d59c65b5cb0c3df34b90400f6f87f Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Tue, 21 Sep 2021 14:46:30 +0300 Subject: Configure: USE_OPENSSL_QUIC=YES implies USE_OPENSSL=YES. --- auto/modules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto/modules b/auto/modules index 21cecc276..09651f32b 100644 --- a/auto/modules +++ b/auto/modules @@ -1342,7 +1342,7 @@ if [ $USE_OPENSSL = YES ]; then fi -if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then +if [ $USE_OPENSSL_QUIC = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_quic_module ngx_module_incs= -- cgit v1.2.3 From b2c8e690cee3c450ffb79438f291e639c7891502 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Sep 2021 18:25:26 +0300 Subject: QUIC: simplified stream fd initialization. --- src/event/quic/ngx_event_quic_streams.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 69c7220cf..9473f99b4 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -431,7 +431,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) *log = *c->log; pool->log = log; - sc = ngx_get_connection(-1, log); + sc = ngx_get_connection(c->fd, log); if (sc == NULL) { ngx_destroy_pool(pool); return NULL; @@ -440,7 +440,6 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qs->connection = sc; sc->quic = qs; - sc->fd = c->fd; sc->shared = 1; sc->type = SOCK_STREAM; sc->pool = pool; -- cgit v1.2.3 From 4e2e70b16cf1df40327d4633b8622e66a0986275 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Sep 2021 14:01:18 +0300 Subject: QUIC: set NGX_TCP_NODELAY_DISABLED for fake stream connections. Notably, it is to avoid setting the TCP_NODELAY flag for QUIC streams in ngx_http_upstream_send_response(). It is an invalid operation on inherently SOCK_DGRAM sockets, which leads to QUIC connection close. The change reduces diff to the default branch in stream content phase. --- src/event/quic/ngx_event_quic_streams.c | 1 + src/stream/ngx_stream_core_module.c | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 9473f99b4..7ad443eec 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -450,6 +450,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->local_sockaddr = c->local_sockaddr; sc->local_socklen = c->local_socklen; sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; sc->recv = ngx_quic_stream_recv; sc->send = ngx_quic_stream_send; diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index a31242190..39c720569 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -325,9 +325,6 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); if (c->type == SOCK_STREAM -#if (NGX_STREAM_QUIC) - && c->quic == NULL -#endif && cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { -- cgit v1.2.3 From 3ae914c837b2ad4217165007c980d31161d6db9f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 17 Sep 2021 15:28:31 +0300 Subject: HTTP/3: fixed pushed request finalization in case of error. Previously request could be finalized twice. For example, this could happen if "Host" header was invalid. --- src/http/v3/ngx_http_v3_filter_module.c | 55 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 5af17e40a..34b2991ad 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -880,7 +880,6 @@ static ngx_int_t ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id) { - ngx_pool_t *pool; ngx_connection_t *c, *pc; ngx_http_request_t *r; ngx_http_log_ctx_t *ctx; @@ -889,8 +888,6 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, pc = pr->connection; - r = NULL; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "http3 create push request id:%uL", push_id); @@ -901,7 +898,8 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { - goto failed; + ngx_http_close_connection(c); + return NGX_ERROR; } phc = ngx_http_quic_get_connection(pc); @@ -910,7 +908,8 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { - goto failed; + ngx_http_close_connection(c); + return NGX_ERROR; } ctx->connection = c; @@ -925,7 +924,8 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, r = ngx_http_create_request(c); if (r == NULL) { - goto failed; + ngx_http_close_connection(c); + return NGX_ERROR; } c->data = r; @@ -941,44 +941,49 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, r->header_in = ngx_create_temp_buf(r->pool, cscf->client_header_buffer_size); if (r->header_in == NULL) { - goto failed; + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } if (ngx_list_init(&r->headers_in.headers, r->pool, 4, sizeof(ngx_table_elt_t)) != NGX_OK) { - goto failed; + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; r->schema.data = ngx_pstrdup(r->pool, &pr->schema); if (r->schema.data == NULL) { - goto failed; + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } r->schema.len = pr->schema.len; r->uri_start = ngx_pstrdup(r->pool, path); if (r->uri_start == NULL) { - goto failed; + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } r->uri_end = r->uri_start + path->len; if (ngx_http_parse_uri(r) != NGX_OK) { - goto failed; + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; } if (ngx_http_process_request_uri(r) != NGX_OK) { - goto failed; + return NGX_ERROR; } if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) != NGX_OK) { - goto failed; + return NGX_ERROR; } if (pr->headers_in.accept_encoding) { @@ -986,7 +991,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, &pr->headers_in.accept_encoding->value) != NGX_OK) { - goto failed; + return NGX_ERROR; } } @@ -995,7 +1000,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, &pr->headers_in.accept_language->value) != NGX_OK) { - goto failed; + return NGX_ERROR; } } @@ -1004,7 +1009,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, &pr->headers_in.user_agent->value) != NGX_OK) { - goto failed; + return NGX_ERROR; } } @@ -1014,22 +1019,6 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, ngx_post_event(c->read, &ngx_posted_events); return NGX_OK; - -failed: - - if (r) { - ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - } - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); - - return NGX_ERROR; } @@ -1049,6 +1038,7 @@ ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, p = ngx_pnalloc(r->pool, value->len + 1); if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } @@ -1057,6 +1047,7 @@ ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } -- cgit v1.2.3 From 08dcf62f5b8ee49927dc38bae705b8fa777799e4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Sep 2021 14:08:21 +0300 Subject: HTTP/3: fixed ngx_stat_active counter. Previously the counter was not incremented for HTTP/3 streams, but still decremented in ngx_http_close_connection(). There are two solutions here, one is to increment the counter for HTTP/3 streams, and the other one is not to decrement the counter for HTTP/3 streams. The latter solution looks inconsistent with ngx_stat_reading/ngx_stat_writing, which are incremented on a per-request basis. The change adds ngx_stat_active increment for HTTP/3 request and push streams. --- src/http/v3/ngx_http_v3_filter_module.c | 4 ++++ src/http/v3/ngx_http_v3_request.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 34b2991ad..43d2cd1bd 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -896,6 +896,10 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, return NGX_ABORT; } +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); if (hc == NULL) { ngx_http_close_connection(c); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 793a34816..533a50fb8 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -78,6 +78,10 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + hc = c->data; clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); -- cgit v1.2.3 From 2cd173d450cbb61291c772556609bcfbc9da1170 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 22 Sep 2021 14:10:43 +0300 Subject: HTTP/3: fixed null pointer dereference with server push. See details for HTTP/2 fix in 8b0553239592 for a complete description. --- src/http/v3/ngx_http_v3_filter_module.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 43d2cd1bd..a88b32f07 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -858,6 +858,10 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, return NGX_ABORT; } + if (r->headers_in.host == NULL) { + return NGX_ABORT; + } + push_id = h3c->next_push_id++; rc = ngx_http_v3_create_push_request(r, path, push_id); -- cgit v1.2.3 From 8ce1c2c7e9abc90a63ad74df5ad4cc7c37b24c87 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Mon, 27 Sep 2021 10:10:37 +0300 Subject: Configure: check for QUIC 0-RTT support at compile time. --- auto/lib/openssl/conf | 9 --------- src/event/quic/ngx_event_quic_ssl.c | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index a9cb0094e..8b84c02a4 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -165,13 +165,4 @@ with nginx by using --with-openssl= option. END exit 1 fi - - ngx_feature="OpenSSL QUIC 0-RTT context" - ngx_feature_name="NGX_OPENSSL_QUIC_ZRTT_CTX" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_path= - ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" - ngx_feature_test="SSL_set_quic_early_data_context(NULL, NULL, 0)" - . auto/feature fi diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index f589d74ac..6e2377eac 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -549,7 +549,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } -#if (NGX_OPENSSL_QUIC_ZRTT_CTX) +#if BORINGSSL_API_VERSION >= 11 if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic SSL_set_quic_early_data_context() failed"); -- cgit v1.2.3 From f2859767d4218e60f8ac3adbac6123c8a1f68279 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Mon, 27 Sep 2021 10:10:38 +0300 Subject: Configure: fixed QUIC support test. OpenSSL library QUIC support cannot be tested at configure time when using the --with-openssl option so assume it's present if requested. While here, fixed the error message in case QUIC support is missing. --- auto/lib/openssl/conf | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 8b84c02a4..9ab062091 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -5,12 +5,16 @@ if [ $OPENSSL != NONE ]; then + have=NGX_OPENSSL . auto/have + have=NGX_SSL . auto/have + + if [ $USE_OPENSSL_QUIC = YES ]; then + have=NGX_QUIC . auto/have + fi + case "$CC" in cl | bcc32) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" @@ -33,9 +37,6 @@ if [ $OPENSSL != NONE ]; then ;; *) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" @@ -139,30 +140,28 @@ END exit 1 fi -fi - + if [ $USE_OPENSSL_QUIC = YES ]; then -if [ $USE_OPENSSL_QUIC = YES ]; then - - ngx_feature="OpenSSL QUIC support" - ngx_feature_name="NGX_QUIC" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_path= - ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" - ngx_feature_test="SSL_set_quic_method(NULL, NULL)" - . auto/feature + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_QUIC" + ngx_feature_run=no + ngx_feature_incs="#include " + ngx_feature_path= + ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" + . auto/feature - if [ $ngx_found = no ]; then + if [ $ngx_found = no ]; then cat << END $0: error: certain modules require OpenSSL QUIC support. -You can either do not enable the modules, or install the OpenSSL library -into the system, or build the OpenSSL library statically from the source -with nginx by using --with-openssl= option. +You can either do not enable the modules, or install the OpenSSL library with +QUIC support into the system, or build the OpenSSL library with QUIC support +statically from the source with nginx by using --with-openssl= option. END - exit 1 + exit 1 + fi fi fi -- cgit v1.2.3 From a5b5b6ca0f36ebba327e35c5d5159458df86b60e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 27 Sep 2021 15:38:55 +0300 Subject: QUIC: moved a variable initialization near to its use. This tends to produce slightly more optimal code with pos == NULL when built with Clang on low optimization levels. Spotted by Ruslan Ermilov. --- src/event/quic/ngx_event_quic_transport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 94d516fc7..85418d1e8 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1967,8 +1967,6 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, p = ngx_cpymem(p, value.data, value.len); \ } while (0) - p = pos; - len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, @@ -2026,6 +2024,8 @@ ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, return len; } + p = pos; + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); -- cgit v1.2.3 From 4d92aa79571d095e088c22513262a68aa347950d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 27 Sep 2021 17:42:53 +0300 Subject: HTTP/3: fixed server push after ea9b645472b5. Unlike in HTTP/2, both "host" and ":authority" reside in r->headers_in.server. --- src/http/v3/ngx_http_v3_filter_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index a88b32f07..ee3c99dc1 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -858,7 +858,7 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, return NGX_ABORT; } - if (r->headers_in.host == NULL) { + if (r->headers_in.server.len == 0) { return NGX_ABORT; } -- cgit v1.2.3 From 2765b63216fab23040aa83731ffd7d767cf0fa31 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 29 Sep 2021 15:01:53 +0300 Subject: Fixed mismerge of ssl_reject_handshake in 71b7453fb11f. In particular, this fixes rejecting "listen .. quic|http3" configurations without TLSv1.3 configured. --- src/http/modules/ngx_http_ssl_module.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index efbc4594c..dbb5905df 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -1385,14 +1385,23 @@ ngx_http_ssl_init(ngx_conf_t *cf) sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; if (sscf->certificates) { + + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" must enable TLSv1.3 for " + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + continue; } if (!sscf->reject_handshake) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } @@ -1417,14 +1426,6 @@ ngx_http_ssl_init(ngx_conf_t *cf) name, cscf->file_name, cscf->line); return NGX_ERROR; } - - if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "\"ssl_protocols\" did not enable TLSv1.3 for " - "the \"listen ... %s\" directives in %s:%ui", - name, cscf->file_name, cscf->line); - return NGX_ERROR; - } } } -- cgit v1.2.3 From 1ea6f35fbfecadfe3e78c4b59b8c03a97b696d15 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 29 Sep 2021 15:01:56 +0300 Subject: Stream: detect "listen .. quic" without TLSv1.3. --- src/stream/ngx_stream_ssl_module.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index b7350002c..74a727797 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -1070,7 +1070,10 @@ ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { + ngx_uint_t i; + ngx_stream_listen_t *listen; ngx_stream_handler_pt *h; + ngx_stream_ssl_conf_t *scf; ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1082,5 +1085,23 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; + listen = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts; i++) { + if (!listen[i].quic) { + continue; + } + + scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" must enable TLSv1.3 for " + "the \"listen ... quic\" directive in %s:%ui", + scf->file, scf->line); + return NGX_ERROR; + } + } + return NGX_OK; } -- cgit v1.2.3 From dab6035d68f3dd3f212393635c193c7aefea0d65 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 29 Sep 2021 15:01:59 +0300 Subject: HTTP/3: fixed segfault when using SSL certificates with variables. A QUIC connection doesn't have c->log->data and friends initialized to sensible values. Yet, a request can be created in the certificate callback with such an assumption, which leads to a segmentation fault due to null pointer dereference in ngx_http_free_request(). The fix is to adjust initializing the QUIC part of a connection such that it has all of that in place. Further, this appends logging error context for unsuccessful QUIC handshakes: - cannot load certificate .. while handling frames - SSL_do_handshake() failed .. while sending frames --- src/http/modules/ngx_http_quic_module.c | 2 -- src/http/ngx_http_request.c | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index b41c069b6..ce13a223f 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -198,8 +198,6 @@ ngx_http_quic_init(ngx_connection_t *c) hc->ssl = 1; if (c->quic == NULL) { - c->log->connection = c->number; - qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_quic_module); ngx_quic_run(c, qcf); diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 2b838cfc3..6496a5400 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -299,14 +299,6 @@ ngx_http_init_connection(ngx_connection_t *c) /* the default server configuration for the address:port */ hc->conf_ctx = hc->addr_conf->default_server->ctx; -#if (NGX_HTTP_QUIC) - if (hc->addr_conf->quic) { - if (ngx_http_quic_init(c) == NGX_DONE) { - return; - } - } -#endif - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); if (ctx == NULL) { ngx_http_close_connection(c); @@ -324,6 +316,14 @@ ngx_http_init_connection(ngx_connection_t *c) c->log_error = NGX_ERROR_INFO; +#if (NGX_HTTP_QUIC) + if (hc->addr_conf->quic) { + if (ngx_http_quic_init(c) == NGX_DONE) { + return; + } + } +#endif + rev = c->read; rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; -- cgit v1.2.3 From 3c31d3f42129689c5d8837d94fad0e44db9129a8 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 29 Sep 2021 15:06:28 +0300 Subject: Stream: fixed segfault when using SSL certificates with variables. Similar to the previous change, a segmentation fault occurres when evaluating SSL certificates on a QUIC connection due to an uninitialized stream session. The fix is to adjust initializing the QUIC part of a connection until after it has session and variables initialized. Similarly, this appends logging error context for QUIC connections: - client 127.0.0.1:54749 connected to 127.0.0.1:8880 while handling frames - quic client timed out (60: Operation timed out) while handling quic input --- src/stream/ngx_stream_handler.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index f9030335f..3b95bf812 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -115,23 +115,6 @@ ngx_stream_init_connection(ngx_connection_t *c) } } -#if (NGX_STREAM_QUIC) - - if (addr_conf->quic) { - ngx_quic_conf_t *qcf; - - if (c->quic == NULL) { - c->log->connection = c->number; - - qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, - ngx_stream_quic_module); - ngx_quic_run(c, qcf); - return; - } - } - -#endif - s = ngx_pcalloc(c->pool, sizeof(ngx_stream_session_t)); if (s == NULL) { ngx_stream_close_connection(c); @@ -194,6 +177,21 @@ ngx_stream_init_connection(ngx_connection_t *c) s->start_sec = tp->sec; s->start_msec = tp->msec; +#if (NGX_STREAM_QUIC) + + if (addr_conf->quic) { + ngx_quic_conf_t *qcf; + + if (c->quic == NULL) { + qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, + ngx_stream_quic_module); + ngx_quic_run(c, qcf); + return; + } + } + +#endif + rev = c->read; rev->handler = ngx_stream_session_handler; -- cgit v1.2.3 From b6b2a45fb6824185889373cc0af070c4a90c1b4a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 30 Sep 2021 17:14:42 +0300 Subject: Added r->response_sent flag. The flag indicates that the entire response was sent to the socket up to the last_buf flag. The flag is only usable for protocol implementations that call ngx_http_write_filter() from header filter, such as HTTP/1.x and HTTP/3. --- src/http/ngx_http_request.h | 1 + src/http/ngx_http_write_filter_module.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index aee9109c3..a19e975b2 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -547,6 +547,7 @@ struct ngx_http_request_s { unsigned request_complete:1; unsigned request_output:1; unsigned header_sent:1; + unsigned response_sent:1; unsigned expect_tested:1; unsigned root_tested:1; unsigned done:1; diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c index 6a5d957b1..2a4996251 100644 --- a/src/http/ngx_http_write_filter_module.c +++ b/src/http/ngx_http_write_filter_module.c @@ -239,6 +239,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + return NGX_OK; } @@ -350,6 +354,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } -- cgit v1.2.3 From 38d56f4ccd94ffba49e84ace82125249af7ef20a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Sep 2021 17:08:48 +0300 Subject: HTTP/3: reset streams with incomplete responses or timeouts. This prevents client from closing the QUIC connection due to response parse error. --- src/http/ngx_http_request.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 6496a5400..e37ef8024 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3746,6 +3746,12 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) if (r->connection->timedout) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); +#if (NGX_HTTP_V3) + if (r->connection->quic) { + (void) ngx_quic_reset_stream(r->connection, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + } else +#endif if (clcf->reset_timedout_connection) { linger.l_onoff = 1; linger.l_linger = 0; @@ -3757,6 +3763,14 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) "setsockopt(SO_LINGER) failed"); } } + + } else if (!r->response_sent) { +#if (NGX_HTTP_V3) + if (r->connection->quic) { + (void) ngx_quic_reset_stream(r->connection, + NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +#endif } /* the various request strings were allocated from r->pool */ -- cgit v1.2.3 From 22f2dc2a875318870887315d9697b47911172977 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Sep 2021 14:02:56 +0300 Subject: QUIC: reset stream only once. --- src/event/quic/ngx_event_quic_streams.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 7ad443eec..0bd487f83 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -221,6 +221,12 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; + wev = c->write; + + if (wev->error) { + return NGX_OK; + } + qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -238,7 +244,6 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) ngx_quic_queue_frame(qc, frame); - wev = c->write; wev->error = 1; wev->ready = 1; -- cgit v1.2.3 From 010f974e44082625056f4282ab66696f3708d4cf Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 21 Sep 2021 16:24:33 +0300 Subject: QUIC: send RESET_STREAM in response to STOP_SENDING. As per RFC 9000: An endpoint that receives a STOP_SENDING frame MUST send a RESET_STREAM frame if the stream is in the "Ready" or "Send" state. An endpoint SHOULD copy the error code from the STOP_SENDING frame to the RESET_STREAM frame it sends, but it can use any application error code. --- src/event/quic/ngx_event_quic_streams.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 0bd487f83..a49117dc9 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1253,6 +1253,7 @@ ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) { + ngx_pool_t *pool; ngx_event_t *wev; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -1282,16 +1283,23 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, sc = qs->connection; - wev = sc->write; - wev->error = 1; - wev->ready = 1; + if (ngx_quic_reset_stream(sc, f->error_code) != NGX_OK) { + pool = sc->pool; + + ngx_close_connection(sc); + ngx_destroy_pool(pool); + + return NGX_ERROR; + } return ngx_quic_init_stream(qs); } + if (ngx_quic_reset_stream(qs->connection, f->error_code) != NGX_OK) { + return NGX_ERROR; + } + wev = qs->connection->write; - wev->error = 1; - wev->ready = 1; if (wev->active) { wev->handler(wev); -- cgit v1.2.3 From 5e37df0bf413bf34407ff4b399b58dc006e24b71 Mon Sep 17 00:00:00 2001 From: Martin Duke Date: Tue, 12 Oct 2021 11:56:49 +0300 Subject: QUIC: Check if CID has been used in stateless reset check Section 10.3.1 of RFC9000 requires this check. --- src/event/quic/ngx_event_quic.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 478fc8461..1217d0230 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -370,8 +370,11 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - if (cid->seqnum == 0) { - /* no stateless reset token in initial connection id */ + if (cid->seqnum == 0 || cid->refcnt == 0) { + /* + * No stateless reset token in initial connection id. + * Don't accept a token from an unused connection id. + */ continue; } -- cgit v1.2.3 From 7b12abb0a85e7790dffc08ab3ad64893d957f75c Mon Sep 17 00:00:00 2001 From: Martin Duke Date: Tue, 12 Oct 2021 11:57:50 +0300 Subject: QUIC: attempt decrypt before checking for stateless reset. Checking the reset after encryption avoids false positives. More importantly, it avoids the check entirely in the usual case where decryption succeeds. RFC 9000, 10.3.1 Detecting a Stateless Reset Endpoints MAY skip this check if any packet from a datagram is successfully processed. --- src/event/quic/ngx_event_quic.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 1217d0230..8741b83e6 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -804,8 +804,11 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } - } else { + } + rc = ngx_quic_process_payload(c, pkt); + + if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic stateless reset packet detected"); @@ -817,7 +820,7 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } } - return ngx_quic_process_payload(c, pkt); + return rc; } /* packet does not belong to a connection */ -- cgit v1.2.3 From ec86cf18fa5d64aba662d99ab7d195e2e6009545 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 6 Oct 2021 14:48:59 +0300 Subject: HTTP/3: removed client-side encoder support. Dynamic tables are not used when generating responses anyway. --- src/http/v3/ngx_http_v3_streams.c | 149 -------------------------------------- src/http/v3/ngx_http_v3_streams.h | 7 -- 2 files changed, 156 deletions(-) diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 1cff29319..303d1d301 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -479,155 +479,6 @@ failed: } -ngx_int_t -ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value) -{ - u_char *p, buf[NGX_HTTP_V3_PREFIX_INT_LEN * 2]; - size_t n; - ngx_connection_t *ec; - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client ref insert, %s[%ui] \"%V\"", - dynamic ? "dynamic" : "static", index, value); - - ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); - if (ec == NULL) { - return NGX_ERROR; - } - - p = buf; - - *p = (dynamic ? 0x80 : 0xc0); - p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 6); - - /* XXX option for huffman? */ - *p = 0; - p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); - - n = p - buf; - - if (ec->send(ec, buf, n) != (ssize_t) n) { - goto failed; - } - - if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_http_v3_close_uni_stream(ec); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *ec; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client insert \"%V\":\"%V\"", name, value); - - ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); - if (ec == NULL) { - return NGX_ERROR; - } - - /* XXX option for huffman? */ - buf[0] = 0x40; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, name->len, 5) - buf; - - if (ec->send(ec, buf, n) != (ssize_t) n) { - goto failed; - } - - if (ec->send(ec, name->data, name->len) != (ssize_t) name->len) { - goto failed; - } - - /* XXX option for huffman? */ - buf[0] = 0; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, value->len, 7) - buf; - - if (ec->send(ec, buf, n) != (ssize_t) n) { - goto failed; - } - - if (ec->send(ec, value->data, value->len) != (ssize_t) value->len) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_http_v3_close_uni_stream(ec); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *ec; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client set capacity %ui", capacity); - - ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); - if (ec == NULL) { - return NGX_ERROR; - } - - buf[0] = 0x20; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, capacity, 5) - buf; - - if (ec->send(ec, buf, n) != (ssize_t) n) { - ngx_http_v3_close_uni_stream(ec); - return NGX_ERROR; - } - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *ec; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client duplicate %ui", index); - - ec = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_ENCODER); - if (ec == NULL) { - return NGX_ERROR; - } - - buf[0] = 0; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, index, 5) - buf; - - if (ec->send(ec, buf, n) != (ssize_t) n) { - ngx_http_v3_close_uni_stream(ec); - return NGX_ERROR; - } - - return NGX_OK; -} - - ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) { diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h index 4f7290e4c..42ff849be 100644 --- a/src/http/v3/ngx_http_v3_streams.h +++ b/src/http/v3/ngx_http_v3_streams.h @@ -27,13 +27,6 @@ ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); -ngx_int_t ngx_http_v3_send_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value); -ngx_int_t ngx_http_v3_send_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value); -ngx_int_t ngx_http_v3_send_set_capacity(ngx_connection_t *c, - ngx_uint_t capacity); -ngx_int_t ngx_http_v3_send_duplicate(ngx_connection_t *c, ngx_uint_t index); ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, -- cgit v1.2.3 From 0c33e484a4333fe2a343baf3aeefae3212534db3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 6 Oct 2021 14:51:16 +0300 Subject: HTTP/3: fixed request length calculation. Previously, when request was blocked, r->request_length was not updated. --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 533a50fb8..44aef49e4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -297,6 +297,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } + r->request_length += b->pos - p; + if (rc == NGX_BUSY) { if (rev->error) { ngx_http_close_request(r, NGX_HTTP_CLOSE); @@ -310,8 +312,6 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } - r->request_length += b->pos - p; - if (rc == NGX_AGAIN) { continue; } -- cgit v1.2.3 From 434f11bf3f4c9c8466a946c775441ecd6f768c13 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 7 Oct 2021 13:22:42 +0300 Subject: HTTP/3: traffic-based flood detection. With this patch, all traffic over HTTP/3 bidi and uni streams is counted in the h3c->total_bytes field, and payload traffic is counted in the h3c->payload_bytes field. As long as total traffic is many times larger than payload traffic, we consider this to be a flood. Request header traffic is counted as if all fields are literal. Response header traffic is counted as is. --- src/http/v3/ngx_http_v3.c | 19 +++++++++++ src/http/v3/ngx_http_v3.h | 4 +++ src/http/v3/ngx_http_v3_filter_module.c | 51 ++++++++++++++++++++++------ src/http/v3/ngx_http_v3_request.c | 27 +++++++++++++++ src/http/v3/ngx_http_v3_streams.c | 60 ++++++++++++++++++++++++++------- 5 files changed, 138 insertions(+), 23 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 2c838f4b5..500113509 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -86,3 +86,22 @@ ngx_http_v3_cleanup_session(void *data) ngx_del_timer(&h3c->keepalive); } } + + +ngx_int_t +ngx_http_v3_check_flood(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "HTTP/3 flood detected"); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 9076b6ff5..53f38a7f2 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -128,6 +128,9 @@ struct ngx_http_v3_session_s { uint64_t max_push_id; uint64_t goaway_push_id; + off_t total_bytes; + off_t payload_bytes; + ngx_uint_t goaway; /* unsigned goaway:1; */ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; @@ -136,6 +139,7 @@ struct ngx_http_v3_session_s { void ngx_http_v3_init(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_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 ee3c99dc1..f835f8e5c 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -101,6 +101,7 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; + ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -120,6 +121,8 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) return NGX_OK; } + h3c = ngx_http_v3_get_session(r->connection); + if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } @@ -531,6 +534,8 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) n = b->last - b->pos; + h3c->payload_bytes += n; + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + ngx_http_v3_encode_varlen_int(NULL, n); @@ -571,6 +576,9 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; @@ -590,6 +598,10 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); } + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + return ngx_http_write_filter(r, out); } @@ -1096,9 +1108,12 @@ static ngx_chain_t * ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, uint64_t push_id) { - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 create push promise id:%uL", push_id); @@ -1233,6 +1248,8 @@ ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, n = b->last - b->pos; + h3c->payload_bytes += n; + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + ngx_http_v3_encode_varlen_int(NULL, n); @@ -1265,6 +1282,7 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; if (in == NULL) { @@ -1276,6 +1294,8 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) return ngx_http_next_body_filter(r, in); } + h3c = ngx_http_v3_get_session(r->connection); + out = NULL; ll = &out; @@ -1340,6 +1360,8 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) tl->next = out; out = tl; + + h3c->payload_bytes += size; } if (cl->buf->last_buf) { @@ -1356,6 +1378,10 @@ ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) *ll = NULL; } + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + rc = ngx_http_next_body_filter(r, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, @@ -1369,13 +1395,16 @@ static ngx_chain_t * ngx_http_v3_create_trailers(ngx_http_request_t *r, ngx_http_v3_filter_ctx_t *ctx) { - size_t len, n; - u_char *p; - ngx_buf_t *b; - ngx_uint_t i; - ngx_chain_t *cl, *hl; - ngx_list_part_t *part; - ngx_table_elt_t *header; + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); len = 0; @@ -1461,6 +1490,8 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r, n = b->last - b->pos; + h3c->payload_bytes += n; + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (hl == NULL) { return NULL; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 44aef49e4..bb9a72248 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -218,6 +218,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; ngx_http_core_srv_conf_t *cscf; ngx_http_v3_parse_headers_t *st; @@ -233,6 +234,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) return; } + h3c = ngx_http_v3_get_session(c); + st = &r->v3_parse->headers; b = r->header_in; @@ -298,6 +301,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) } r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } if (rc == NGX_BUSY) { if (rev->error) { @@ -318,6 +327,10 @@ 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); + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, &st->field_rep.field.value) != NGX_OK) @@ -1080,6 +1093,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -1088,6 +1102,8 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) rb = r->request_body; st = &r->v3_parse->body; + h3c = ngx_http_v3_get_session(r->connection); + if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1135,6 +1151,11 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } if (rc == NGX_AGAIN) { continue; @@ -1178,6 +1199,8 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; if (st->length < 8) { @@ -1222,12 +1245,16 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->pos += (size_t) st->length; rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; st->length = 0; } else { st->length -= size; rb->received += size; r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; cl->buf->pos = cl->buf->last; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 303d1d301..2ff1320ae 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -171,6 +171,7 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) ngx_buf_t b; ngx_int_t rc; ngx_connection_t *c; + ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; c = rev->data; @@ -207,6 +208,14 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) b.pos = buf; b.last = buf + n; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); if (rc == NGX_DONE) { @@ -282,6 +291,9 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); n = p - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (sc->send(sc, buf, n) != (ssize_t) n) { goto failed; } @@ -291,7 +303,6 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) goto failed; } - h3c = ngx_http_v3_get_session(c); h3c->npushing++; cln->handler = ngx_http_v3_push_cleanup; @@ -383,6 +394,9 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (sc->send(sc, buf, n) != (ssize_t) n) { goto failed; } @@ -403,6 +417,7 @@ 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; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -431,6 +446,9 @@ ngx_http_v3_send_settings(ngx_connection_t *c) p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->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) { goto failed; } @@ -448,9 +466,10 @@ failed: ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) { - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; - size_t n; - ngx_connection_t *cc; + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); @@ -465,6 +484,9 @@ ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); n = p - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (cc->send(cc, buf, n) != (ssize_t) n) { goto failed; } @@ -482,9 +504,10 @@ failed: ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client ack section %ui", stream_id); @@ -497,6 +520,9 @@ ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) buf[0] = 0x80; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR; @@ -509,9 +535,10 @@ ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client cancel stream %ui", stream_id); @@ -524,6 +551,9 @@ ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) buf[0] = 0x40; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR; @@ -536,9 +566,10 @@ ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client increment insert count %ui", inc); @@ -551,6 +582,9 @@ ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) buf[0] = 0; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR; -- cgit v1.2.3 From 6e58593a593804cfad04a8ddbea086fec1872ef0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 13 Oct 2021 14:41:46 +0300 Subject: QUIC: traffic-based flood detection. With this patch, all traffic over a QUIC connection is compared to traffic over QUIC streams. As long as total traffic is many times larger than stream traffic, we consider this to be a flood. --- src/event/quic/ngx_event_quic.c | 34 +++++++++++++++++++++++++----- src/event/quic/ngx_event_quic_connection.h | 2 ++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 8741b83e6..38138a6c1 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -665,13 +665,17 @@ ngx_quic_close_timer_handler(ngx_event_t *ev) static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { - u_char *p; - ngx_int_t rc; - ngx_uint_t good; - ngx_quic_header_t pkt; + size_t size; + u_char *p; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; good = 0; + size = b->last - b->pos; + p = b->pos; while (p < b->last) { @@ -736,7 +740,27 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) p = b->pos; } - return good ? NGX_OK : NGX_DECLINED; + if (!good) { + return NGX_DECLINED; + } + + qc = ngx_quic_get_connection(c); + + if (qc) { + 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"); + + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "QUIC flood detected"; + return NGX_ERROR; + } + } + + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index f3de5b136..b58e9f586 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -236,6 +236,8 @@ struct ngx_quic_connection_s { ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; + off_t received; + ngx_uint_t error; enum ssl_encryption_level_t error_level; ngx_uint_t error_ftype; -- cgit v1.2.3 From da28a4c6267b8b29d9188f89ab8fad6de08ea688 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 13 Oct 2021 14:46:51 +0300 Subject: QUIC: limited the total number of frames. Exceeding 10000 allocated frames is considered a flood. --- src/event/quic/ngx_event_quic_connection.h | 2 +- src/event/quic/ngx_event_quic_frames.c | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index b58e9f586..9f3cb2cd0 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -228,8 +228,8 @@ struct ngx_quic_connection_s { ngx_chain_t *free_bufs; ngx_buf_t *free_shadow_bufs; -#ifdef NGX_QUIC_DEBUG_ALLOC ngx_uint_t nframes; +#ifdef NGX_QUIC_DEBUG_ALLOC ngx_uint_t nbufs; #endif diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 438565858..8d9fe24c2 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -38,18 +38,22 @@ ngx_quic_alloc_frame(ngx_connection_t *c) "quic reuse frame n:%ui", qc->nframes); #endif - } else { + } else if (qc->nframes < 10000) { frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); if (frame == NULL) { return NULL; } -#ifdef NGX_QUIC_DEBUG_ALLOC ++qc->nframes; +#ifdef NGX_QUIC_DEBUG_ALLOC ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc frame n:%ui", qc->nframes); #endif + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + return NULL; } ngx_memzero(frame, sizeof(ngx_quic_frame_t)); -- cgit v1.2.3 From 0cd45dea762d3fff535dededf1a4490730f6e7f8 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 15 Oct 2021 12:26:42 +0300 Subject: QUIC: optimized ack range processing. The sent queue is sorted by packet number. It is possible to avoid traversing full queue while handling ack ranges. It makes sense to start traversing from the queue head (i.e. check oldest packets first). --- src/event/quic/ngx_event_quic_ack.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 3e4bb6d4f..22697ccb6 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -223,14 +223,18 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, st->max_pn = NGX_TIMER_INFINITE; found = 0; - q = ngx_queue_last(&ctx->sent); + q = ngx_queue_head(&ctx->sent); while (q != ngx_queue_sentinel(&ctx->sent)) { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - q = ngx_queue_prev(q); + q = ngx_queue_next(q); + + if (f->pnum > max) { + break; + } - if (f->pnum >= min && f->pnum <= max) { + if (f->pnum >= min) { ngx_quic_congestion_ack(c, f); switch (f->type) { -- cgit v1.2.3 From 12bda330d9126f0943ca567b12a716fab2ec7197 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 19 Oct 2021 14:32:50 +0300 Subject: QUIC: switched to integer arithmetic in rtt calculations. RFC 9002 uses constants implying effective implementation, i.e. using bit shift operations instead of floating point. --- src/event/quic/ngx_event_quic_ack.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 22697ccb6..e5e5d3611 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -14,17 +14,12 @@ /* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ #define NGX_QUIC_PKT_THR 3 /* packets */ -/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ -#define NGX_QUIC_TIME_THR 1.125 +/* RFC 9002, 6.1.2. Time Threshold: kGranularity */ #define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ /* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ #define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 -#define ngx_quic_lost_threshold(qc) \ - ngx_max(NGX_QUIC_TIME_THR * ngx_max((qc)->latest_rtt, (qc)->avg_rtt), \ - NGX_QUIC_TIME_GRANULARITY) - /* send time of ACK'ed packets */ typedef struct { @@ -34,6 +29,7 @@ typedef struct { } ngx_quic_ack_stat_t; +static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc); static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, enum ssl_encryption_level_t level, ngx_msec_t send_time); static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, @@ -50,6 +46,19 @@ static void ngx_quic_congestion_lost(ngx_connection_t *c, static void ngx_quic_lost_handler(ngx_event_t *ev); +/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ +static ngx_inline ngx_msec_t +ngx_quic_lost_threshold(ngx_quic_connection_t *qc) +{ + ngx_msec_t thr; + + thr = ngx_max(qc->latest_rtt, qc->avg_rtt); + thr += thr >> 3; + + return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); +} + + ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *f) @@ -198,9 +207,9 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, adjusted_rtt -= ack_delay; } - qc->avg_rtt = 0.875 * qc->avg_rtt + 0.125 * adjusted_rtt; + qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); - qc->rttvar = 0.75 * qc->rttvar + 0.25 * rttvar_sample; + qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From 2f754d5dcfe5a7921a86cb3e24e89696437b4555 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 26 Oct 2021 17:43:10 +0300 Subject: QUIC: refactored OCSP validation in preparation for 0-RTT support. --- src/event/quic/ngx_event_quic_ssl.c | 12 +----------- src/event/quic/ngx_event_quic_streams.c | 32 +++++++++++++++++++++++++++++++- src/event/quic/ngx_event_quic_streams.h | 2 +- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 6e2377eac..bcee112e0 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -361,7 +361,6 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) { int n, sslerr; - ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *cl; ngx_ssl_conn_t *ssl_conn; @@ -463,19 +462,10 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) return NGX_ERROR; } - rc = ngx_ssl_ocsp_validate(c); - - if (rc == NGX_ERROR) { + if (ngx_quic_init_streams(c) != NGX_OK) { return NGX_ERROR; } - if (rc == NGX_AGAIN) { - c->ssl->handler = ngx_quic_init_streams; - return NGX_OK; - } - - ngx_quic_init_streams(c); - return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index a49117dc9..2ba5ade97 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -16,6 +16,7 @@ static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id); static ngx_int_t ngx_quic_init_stream(ngx_quic_stream_t *qs); +static void ngx_quic_init_streams_handler(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); @@ -369,8 +370,37 @@ ngx_quic_init_stream(ngx_quic_stream_t *qs) } -void +ngx_int_t ngx_quic_init_streams(ngx_connection_t *c) +{ + ngx_int_t rc; + 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; + } + + ngx_quic_init_streams_handler(c); + + return NGX_OK; +} + + +static void +ngx_quic_init_streams_handler(ngx_connection_t *c) { ngx_queue_t *q; ngx_quic_stream_t *qs; diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h index 95cdfca1c..c914fde24 100644 --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -31,7 +31,7 @@ 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); -void ngx_quic_init_streams(ngx_connection_t *c); +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, -- cgit v1.2.3 From 01d27365c6b0abbced06c2669072e6568a4e1cb6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 26 Oct 2021 17:43:10 +0300 Subject: QUIC: speeding up processing 0-RTT. After fe919fd63b0b, processing QUIC streams was postponed until after handshake completion, which means that 0-RTT is effectively off. With ssl_ocsp enabled, it could be further delayed. This differs from how OCSP validation works with SSL_read_early_data(). With this change, processing QUIC streams is unlocked when obtaining 0-RTT secret. --- src/event/quic/ngx_event_quic_ssl.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index bcee112e0..8899bc626 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -71,8 +71,20 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, secret_len, rsecret); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, - cipher, rsecret, secret_len); + if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + cipher, rsecret, secret_len) + != 1) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + if (ngx_quic_init_streams(c) != NGX_OK) { + return 0; + } + } + + return 1; } @@ -131,6 +143,10 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } if (level == ssl_encryption_early_data) { + if (ngx_quic_init_streams(c) != NGX_OK) { + return 0; + } + return 1; } -- cgit v1.2.3 From 8f8cb92e9229d75ea5816f35c6b4bfdfb253a486 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 26 Oct 2021 18:05:57 +0300 Subject: QUIC: style. --- src/event/quic/ngx_event_quic_bpf_code.c | 1 - src/event/quic/ngx_event_quic_protection.c | 1 - src/event/quic/ngx_event_quic_socket.c | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic_bpf_code.c b/src/event/quic/ngx_event_quic_bpf_code.c index 990686545..5c9dea1c1 100644 --- a/src/event/quic/ngx_event_quic_bpf_code.c +++ b/src/event/quic/ngx_event_quic_bpf_code.c @@ -86,4 +86,3 @@ ngx_bpf_program_t ngx_quic_reuseport_helper = { .license = "BSD", .type = BPF_PROG_TYPE_SK_REUSEPORT, }; - diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 317eb094c..43336b41e 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -1188,4 +1188,3 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) return NGX_OK; } - diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 968ad6a73..ddde68f09 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -350,6 +350,4 @@ ngx_quic_get_unconnected_socket(ngx_connection_t *c) } return NULL; - } - - +} -- cgit v1.2.3 From f5d0c6db674b9c40f995559051e8847157229b57 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 23 Sep 2021 16:25:49 +0300 Subject: QUIC: added shutdown support in stream proxy. --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_streams.c | 50 +++++++++++++++++++++++++++++++++ src/stream/ngx_stream_proxy_module.c | 15 ++++++++++ 3 files changed, 66 insertions(+) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index dda1e385e..47359fe85 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -92,6 +92,7 @@ void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); 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); uint32_t ngx_quic_version(ngx_connection_t *c); ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 2ba5ade97..e1c131590 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -252,6 +252,56 @@ 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) +{ + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + if (how != NGX_WRITE_SHUTDOWN) { + return NGX_OK; + } + + wev = c->write; + + if (wev->error) { + return NGX_OK; + } + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL shutdown", qs->id); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM; + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = 1; + + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = c->sent; + frame->u.stream.length = 0; + + ngx_quic_queue_frame(qc, frame); + + wev->ready = 1; + wev->error = 1; + + return NGX_OK; +} + + static ngx_quic_stream_t * ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) { diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 934e7d8f2..8a7f3b665 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -1767,6 +1767,21 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, if (dst->type == SOCK_STREAM && pscf->half_close && src->read->eof && !u->half_closed && !dst->buffered) { + +#if (NGX_STREAM_QUIC) + if (dst->quic) { + + if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN) + != NGX_OK) + { + ngx_stream_proxy_finalize(s, + NGX_STREAM_INTERNAL_SERVER_ERROR); + return; + } + + } else +#endif + if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed"); -- cgit v1.2.3 From dbb59fba8ce551a790aada19d4c447efb7545815 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 22 Oct 2021 12:59:44 +0300 Subject: QUIC: fixed processing of minimum packet size. If packet needs to be expanded (for example Initial to 1200 bytes), but path limit is less, such packet should not be created/sent. --- src/event/quic/ngx_event_quic_output.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index dbcdb70d5..d7e74a63a 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -184,6 +184,10 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + if (min > len) { + continue; + } + n = ngx_quic_output_packet(c, ctx, p, len, min, qsock); if (n == NGX_ERROR) { return NGX_ERROR; -- cgit v1.2.3 From 47caa664896a97a5e32bd572ad84880a2771534e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 30 Sep 2021 12:02:29 +0300 Subject: QUIC: added function to initialize packet. --- src/event/quic/ngx_event_quic_output.c | 120 +++++++++++++++------------------ 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index d7e74a63a..1fc8fadd5 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -60,6 +60,8 @@ static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock); +static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_socket_t *qsock, ngx_quic_header_t *pkt); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen); @@ -530,18 +532,17 @@ static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) { - size_t len, hlen, pad_len; - u_char *p; - ssize_t flen; - ngx_str_t out, res; - ngx_int_t rc; - ngx_uint_t nframes, has_pr; - ngx_msec_t now; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_header_t pkt; - ngx_quic_connection_t *qc; - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + size_t len, hlen, pad_len; + u_char *p; + ssize_t flen; + ngx_str_t out, res; + ngx_int_t rc; + ngx_uint_t nframes, has_pr; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; if (ngx_queue_empty(&ctx->frames)) { return 0; @@ -552,7 +553,7 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, qsock->sid.seqnum, ngx_quic_level_name(ctx->level), max, min); - qc = ngx_quic_get_connection(c); + ngx_quic_init_packet(c, ctx, qsock, &pkt); hlen = (ctx->level == ssl_encryption_application) ? NGX_QUIC_MAX_SHORT_HEADER @@ -561,8 +562,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, hlen += EVP_GCM_TLS_TAG_LEN; hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len; - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); - now = ngx_current_msec; nframes = 0; p = src; @@ -630,33 +629,6 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, out.data = src; out.len = len; - pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - - if (ctx->level == ssl_encryption_initial) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; - - } else if (ctx->level == ssl_encryption_handshake) { - pkt.flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; - - } else { - if (qc->key_phase) { - pkt.flags |= NGX_QUIC_PKT_KPHASE; - } - } - - ngx_quic_set_packet_number(&pkt, ctx); - - pkt.version = qc->version; - pkt.log = c->log; - pkt.level = ctx->level; - - pkt.dcid.data = qsock->cid->id; - pkt.dcid.len = qsock->cid->len; - - pkt.scid.data = qsock->sid.id; - pkt.scid.len = qsock->sid.len; - pad_len = 4; if (min || has_pr) { @@ -729,6 +701,46 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } +static void +ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_socket_t *qsock, ngx_quic_header_t *pkt) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(pkt, sizeof(ngx_quic_header_t)); + + pkt->flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt->flags |= NGX_QUIC_PKT_KPHASE; + } + } + + pkt->dcid.data = qsock->cid->id; + pkt->dcid.len = qsock->cid->len; + + pkt->scid.data = qsock->sid.id; + pkt->scid.len = qsock->sid.len; + + pkt->version = qc->version; + pkt->log = c->log; + pkt->level = ctx->level; + + pkt->keys = qc->keys; + + ngx_quic_set_packet_number(pkt, ctx); +} + + static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen) @@ -1251,8 +1263,9 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + ngx_quic_init_packet(c, ctx, qc->socket, &pkt); len = ngx_quic_create_frame(NULL, frame); if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { @@ -1271,27 +1284,6 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, len = min; } - pkt.keys = qc->keys; - pkt.flags = NGX_QUIC_PKT_FIXED_BIT; - - if (qc->key_phase) { - pkt.flags |= NGX_QUIC_PKT_KPHASE; - } - - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - - ngx_quic_set_packet_number(&pkt, ctx); - - pkt.version = qc->version; - pkt.log = c->log; - pkt.level = ctx->level; - - pkt.dcid.data = qc->socket->cid->id; - pkt.dcid.len = qc->socket->cid->len; - - pkt.scid.data = qc->socket->sid.id; - pkt.scid.len = qc->socket->sid.len; - pkt.payload.data = src; pkt.payload.len = len; -- cgit v1.2.3 From 151985c931b4c96ca95e78b60f28d537e41352cc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 7 Oct 2021 12:24:47 +0300 Subject: QUIC: removed unused argument in ngx_quic_create_short_header(). --- src/event/quic/ngx_event_quic_transport.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 85418d1e8..16ae9c37c 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -96,7 +96,7 @@ static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp); static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); + u_char **pnp); static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type); @@ -618,7 +618,7 @@ ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, u_char **pnp) { return ngx_quic_short_pkt(pkt->flags) - ? ngx_quic_create_short_header(pkt, out, pkt_len, pnp) + ? ngx_quic_create_short_header(pkt, out, pnp) : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); } @@ -676,7 +676,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp) + u_char **pnp) { u_char *p, *start; -- cgit v1.2.3 From 5f9c4e15a398bc10e23c7d366d181380135e2503 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 7 Oct 2021 13:48:29 +0300 Subject: QUIC: refactored packet creation. The "min" and "max" arguments refer to UDP datagram size. Generating payload requires to account properly for header size, which is variable and depends on payload size and packet number. --- src/event/quic/ngx_event_quic_output.c | 121 ++++++++++++++--------------- src/event/quic/ngx_event_quic_protection.c | 5 +- src/event/quic/ngx_event_quic_transport.c | 52 +++++++++++-- src/event/quic/ngx_event_quic_transport.h | 4 +- 4 files changed, 108 insertions(+), 74 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 1fc8fadd5..add69b1a2 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -10,10 +10,6 @@ #include -#define NGX_QUIC_MAX_SHORT_HEADER 25 /* 1 flags + 20 dcid + 4 pn */ -#define NGX_QUIC_MAX_LONG_HEADER 56 - /* 1 flags + 4 version + 2 x (1 + 20) s/dcid + 4 pn + 4 len + token len */ - #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT 1252 #define NGX_QUIC_MAX_UDP_PAYLOAD_OUT6 1232 @@ -532,12 +528,12 @@ static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) { - size_t len, hlen, pad_len; + size_t len, pad, min_payload, max_payload; u_char *p; ssize_t flen; - ngx_str_t out, res; + ngx_str_t res; ngx_int_t rc; - ngx_uint_t nframes, has_pr; + ngx_uint_t nframes, expand; ngx_msec_t now; ngx_queue_t *q; ngx_quic_frame_t *f; @@ -555,18 +551,22 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_init_packet(c, ctx, qsock, &pkt); - hlen = (ctx->level == ssl_encryption_application) - ? NGX_QUIC_MAX_SHORT_HEADER - : NGX_QUIC_MAX_LONG_HEADER; + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); - hlen += EVP_GCM_TLS_TAG_LEN; - hlen -= NGX_QUIC_MAX_CID_LEN - qsock->cid->len; + if (min_payload > max_payload) { + return 0; + } now = ngx_current_msec; nframes = 0; p = src; len = 0; - has_pr = 0; + expand = 0; for (q = ngx_queue_head(&ctx->frames); q != ngx_queue_sentinel(&ctx->frames); @@ -574,18 +574,39 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, { f = ngx_queue_data(q, ngx_quic_frame_t, queue); - if (f->type == NGX_QUIC_FT_PATH_RESPONSE - || f->type == NGX_QUIC_FT_PATH_CHALLENGE) + if (!expand && (f->type == NGX_QUIC_FT_PATH_RESPONSE + || f->type == NGX_QUIC_FT_PATH_CHALLENGE)) { - has_pr = 1; + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a + * PATH_CHALLENGE frame to at least the smallest allowed + * maximum datagram size of 1200 bytes... + * + * (same applies to PATH_RESPONSE frames) + */ + + if (max < 1200) { + /* expanded packet will not fit */ + break; + } + + if (min < 1200) { + min = 1200; + + min_payload = ngx_quic_payload_size(&pkt, min); + } + + expand = 1; } - if (hlen + len >= max) { + if (len >= max_payload) { break; } - if (hlen + len + f->len > max) { - rc = ngx_quic_split_frame(c, f, max - hlen - len); + if (len + f->len > max_payload) { + rc = ngx_quic_split_frame(c, f, max_payload - len); if (rc == NGX_ERROR) { return NGX_ERROR; @@ -626,53 +647,21 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return 0; } - out.data = src; - out.len = len; - - pad_len = 4; - - if (min || has_pr) { - hlen = EVP_GCM_TLS_TAG_LEN - + ngx_quic_create_header(&pkt, NULL, out.len, NULL); - - /* - * RFC 9000, 8.2.1. Initiating Path Validation - * - * An endpoint MUST expand datagrams that contain a - * PATH_CHALLENGE frame to at least the smallest allowed - * maximum datagram size of 1200 bytes, unless the - * anti-amplification limit for the path does not permit - * sending a datagram of this size. - * - * (same applies to PATH_RESPONSE frames) - */ - - if (has_pr) { - min = ngx_max(1200, min); - } + if (len < min_payload) { + ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } - if (min > hlen + pad_len) { - pad_len = min - hlen; - } - } - - if (out.len < pad_len) { - /* compensate for potentially enlarged header in Length bytes */ - pad_len -= ngx_quic_create_header(&pkt, NULL, pad_len, NULL) - - ngx_quic_create_header(&pkt, NULL, out.len, NULL); - ngx_memset(p, NGX_QUIC_FT_PADDING, pad_len - out.len); - out.len = pad_len; - } - - pkt.payload = out; + pkt.payload.data = src; + pkt.payload.len = len; res.data = data; ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet tx %s bytes:%ui" " need_ack:%d number:%L encoded nl:%d trunc:0x%xD", - ngx_quic_level_name(ctx->level), out.len, pkt.need_ack, - pkt.number, pkt.num_len, pkt.trunc); + ngx_quic_level_name(ctx->level), pkt.payload.len, + pkt.need_ack, pkt.number, pkt.num_len, pkt.trunc); if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { return NGX_ERROR; @@ -1253,6 +1242,7 @@ ssize_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, size_t min, struct sockaddr *sockaddr, socklen_t socklen) { + size_t min_payload, pad; ssize_t len; ngx_str_t res; ngx_quic_header_t pkt; @@ -1267,6 +1257,11 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ngx_quic_init_packet(c, ctx, qc->socket, &pkt); + min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; + + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + len = ngx_quic_create_frame(NULL, frame); if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { return -1; @@ -1279,10 +1274,10 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, return -1; } - if (len < (ssize_t) min) { - ngx_memset(src + len, NGX_QUIC_FT_PADDING, min - len); - len = min; - } + if (len < (ssize_t) min_payload) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } pkt.payload.data = src; pkt.payload.len = len; diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 43336b41e..d193a7738 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -836,11 +836,10 @@ ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) ngx_quic_ciphers_t ciphers; u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; - out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; - ad.data = res->data; - ad.len = ngx_quic_create_header(pkt, ad.data, out.len, &pnp); + ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); + out.len = pkt->payload.len + EVP_GCM_TLS_TAG_LEN; out.data = res->data + ad.len; #ifdef NGX_QUIC_DEBUG_CRYPTO diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 16ae9c37c..6bc188b59 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -94,7 +94,7 @@ static ngx_int_t ngx_quic_supported_version(uint32_t version); static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); + u_char **pnp); static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp); @@ -613,25 +613,63 @@ ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) } +/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ size_t -ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, size_t pkt_len, - u_char **pnp) +ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) +{ + size_t len; + + if ngx_quic_short_pkt(pkt->flags) { + + len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; + } + + /* flags, version, dcid and scid with lengths and zero-length token */ + len = 5 + 2 + pkt->dcid.len + pkt->scid.len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + + if (len > pkt_len) { + return 0; + } + + /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ + len += ngx_quic_varint_len(pkt_len - len) + + pkt->num_len + EVP_GCM_TLS_TAG_LEN; + + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) { return ngx_quic_short_pkt(pkt->flags) ? ngx_quic_create_short_header(pkt, out, pnp) - : ngx_quic_create_long_header(pkt, out, pkt_len, pnp); + : ngx_quic_create_long_header(pkt, out, pnp); } static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp) + u_char **pnp) { + size_t rem_len; u_char *p, *start; + rem_len = pkt->num_len + pkt->payload.len + EVP_GCM_TLS_TAG_LEN; + if (out == NULL) { return 5 + 2 + pkt->dcid.len + pkt->scid.len - + ngx_quic_varint_len(pkt_len + pkt->num_len) + pkt->num_len + + ngx_quic_varint_len(rem_len) + pkt->num_len + (pkt->level == ssl_encryption_initial ? 1 : 0); } @@ -651,7 +689,7 @@ ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, ngx_quic_build_int(&p, 0); } - ngx_quic_build_int(&p, pkt_len + pkt->num_len); + ngx_quic_build_int(&p, rem_len); *pnp = p; diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 493882308..ed5777b16 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -345,8 +345,10 @@ ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); +size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); + size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, - size_t pkt_len, u_char **pnp); + u_char **pnp); size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, u_char **start); -- cgit v1.2.3 From 0572c2a69f4edef04e3babdb6f9ef18ff52a9619 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 3 Nov 2021 13:36:21 +0300 Subject: QUIC: connections with wrong ALPN protocols are now rejected. Previously, it was not enforced in the stream module. Now, since b9e02e9b2f1d it is possible to specify protocols. Since ALPN is always required, the 'require_alpn' setting is now obsolete. --- src/event/quic/ngx_event_quic.h | 1 - src/event/quic/ngx_event_quic_ssl.c | 25 +++++++++++++------------ src/http/modules/ngx_http_quic_module.c | 1 - src/stream/ngx_stream_quic_module.c | 1 - 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 47359fe85..839570af0 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -60,7 +60,6 @@ typedef struct { ngx_quic_tp_t tp; ngx_flag_t retry; ngx_flag_t gso_enabled; - ngx_flag_t require_alpn; ngx_str_t host_key; u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 8899bc626..839bb3161 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -175,6 +175,10 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ngx_connection_t *c; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + unsigned int alpn_len; + const unsigned char *alpn_data; +#endif c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); qc = ngx_quic_get_connection(c); @@ -190,21 +194,18 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, */ #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - if (qc->conf->require_alpn) { - unsigned int len; - const unsigned char *data; - SSL_get0_alpn_selected(ssl_conn, &data, &len); + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); - if (len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; - qc->error_reason = "unsupported protocol in ALPN extension"; + if (alpn_len == 0) { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; - } - } #endif SSL_get_peer_quic_transport_params(ssl_conn, &client_params, diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index ce13a223f..9e6d17ead 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -331,7 +331,6 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->retry = NGX_CONF_UNSET; conf->gso_enabled = NGX_CONF_UNSET; - conf->require_alpn = 1; return conf; } diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 2cd811ad4..b40b17c93 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -241,7 +241,6 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.retry_scid = { 0, NULL }; * conf->tp.preferred_address = NULL * conf->host_key = { 0, NULL } - * conf->require_alpn = 0; */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; -- cgit v1.2.3 From 9ca3a02e684a3b5f19b6fec5b486d59eb4a4b319 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 13 Oct 2021 14:48:33 +0300 Subject: QUIC: fixed removal of unused client IDs. If client ID was never used, its refcount is zero. To keep things simple, the ngx_quic_unref_client_id() function is now aware of such IDs. If client ID was used, the ngx_quic_replace_retired_client_id() function is supposed to find all users and unref the ID, thus ngx_quic_unref_client_id() should not be called after it. --- src/event/quic/ngx_event_quic_connid.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 273b58c65..2ad65077a 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -183,9 +183,10 @@ retire: if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) { return NGX_ERROR; } - } - ngx_quic_unref_client_id(c, cid); + } else { + ngx_quic_unref_client_id(c, cid); + } } done: @@ -534,7 +535,9 @@ ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) { ngx_quic_connection_t *qc; - cid->refcnt--; + if (cid->refcnt) { + cid->refcnt--; + } /* else: unused client id */ if (cid->refcnt) { return; -- cgit v1.2.3 From df22336bfa387c1724a3b8b9ce97d002ab081721 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 8 Nov 2021 15:41:12 +0300 Subject: QUIC: converted client_tp_done to bitfield. --- src/event/quic/ngx_event_quic_connection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 9f3cb2cd0..ee80342fa 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -200,7 +200,6 @@ struct ngx_quic_connection_s { uint64_t server_seqnum; uint64_t path_seqnum; - ngx_uint_t client_tp_done; ngx_quic_tp_t tp; ngx_quic_tp_t ctp; @@ -253,6 +252,7 @@ struct ngx_quic_connection_s { unsigned draining:1; unsigned key_phase:1; unsigned validated:1; + unsigned client_tp_done:1; }; -- cgit v1.2.3 From 6f9f8bf96e8ebe04572333e15388e7bc0db79ede Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Nov 2021 13:49:01 +0300 Subject: QUIC: removed dead code. The function is no longer used since b3d9e57d0f62. --- src/event/quic/ngx_event_quic_transport.c | 37 ------------------------------- src/event/quic/ngx_event_quic_transport.h | 2 -- 2 files changed, 39 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 6bc188b59..c39d1094c 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -147,28 +147,6 @@ uint32_t ngx_quic_versions[] = { (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) -/* literal errors indexed by corresponding value */ -static char *ngx_quic_errors[] = { - "NO_ERROR", - "INTERNAL_ERROR", - "CONNECTION_REFUSED", - "FLOW_CONTROL_ERROR", - "STREAM_LIMIT_ERROR", - "STREAM_STATE_ERROR", - "FINAL_SIZE_ERROR", - "FRAME_ENCODING_ERROR", - "TRANSPORT_PARAMETER_ERROR", - "CONNECTION_ID_LIMIT_ERROR", - "PROTOCOL_VIOLATION", - "INVALID_TOKEN", - "APPLICATION_ERROR", - "CRYPTO_BUFFER_EXCEEDED", - "KEY_UPDATE_ERROR", - "AEAD_LIMIT_REACHED", - "NO_VIABLE_PATH", -}; - - static ngx_inline u_char * ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) { @@ -305,21 +283,6 @@ ngx_quic_build_int(u_char **pos, uint64_t value) } -u_char * -ngx_quic_error_text(uint64_t error_code) -{ - if (error_code >= NGX_QUIC_ERR_CRYPTO_ERROR) { - return (u_char *) "handshake error"; - } - - if (error_code >= NGX_QUIC_ERR_LAST) { - return (u_char *) "unknown error"; - } - - return (u_char *) ngx_quic_errors[error_code]; -} - - ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt) { diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index ed5777b16..c84f4576b 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -90,7 +90,6 @@ #define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE /* 22.5. QUIC Transport Error Codes Registry */ -/* Keep in sync with ngx_quic_errors[] */ #define NGX_QUIC_ERR_NO_ERROR 0x00 #define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 #define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 @@ -109,7 +108,6 @@ #define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F #define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 -#define NGX_QUIC_ERR_LAST 0x11 #define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 #define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) -- cgit v1.2.3 From a3163fa4b29f6aa1f8ae341814451ec91a3f0ee5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 9 Nov 2021 21:17:05 +0300 Subject: QUIC: fixed GSO packets count. Thanks to Andrey Kolyshkin --- src/event/quic/ngx_event_quic_output.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index add69b1a2..f121d2327 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -378,8 +378,10 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) return NGX_ERROR; } - p += n; - nseg++; + if (n) { + p += n; + nseg++; + } } else { n = 0; -- cgit v1.2.3 From 62b2eea0fee3c7cb4cec288065c0a8235f128c42 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 10 Nov 2021 14:36:36 +0300 Subject: QUIC: removed ngx_quic_error_text() declaration. This is a leftover from cab3b7a070ef. --- src/event/quic/ngx_event_quic_transport.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index c84f4576b..2d30b010c 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -337,8 +337,6 @@ typedef struct { } ngx_quic_header_t; -u_char *ngx_quic_error_text(uint64_t error_code); - ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); -- cgit v1.2.3 From 1562200066d73a037ecfa4e15548cbcc239b354f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 11 Nov 2021 15:15:07 +0300 Subject: QUIC: fixed PATH_RESPONSE frame expansion. The PATH_RESPONSE frame must be expanded to 1200, except the case when anti-amplification limit is in effect, i.e. on unvalidated paths. Previously, the anti-amplification limit was always applied. --- src/event/quic/ngx_event_quic_migration.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 050b785a6..bea51081d 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -47,12 +47,20 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, path = qsock->path; /* + * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes. + * ... * An endpoint MUST NOT expand the datagram containing the PATH_RESPONSE * if the resulting data exceeds the anti-amplification limit. */ - max = path->received * 3; - max = (path->sent >= max) ? 0 : max - path->sent; - pad = ngx_min(1200, max); + if (path->state != NGX_QUIC_PATH_VALIDATED) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + pad = ngx_min(1200, max); + + } else { + pad = 1200; + } sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); if (sent < 0) { -- cgit v1.2.3 From 6118ec73cfef53897d00cc81810ee661321f0057 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 15:22:33 +0300 Subject: HTTP/3: adjusted QUIC connection finalization. When an HTTP/3 function returns an error in context of a QUIC stream, it's this function's responsibility now to finalize the entire QUIC connection with the right code, if required. Previously, QUIC connection finalization could be done both outside and inside such functions. The new rule follows a similar rule for logging, leads to cleaner code, and allows to provide more details about the error. While here, a few error cases are no longer treated as fatal and QUIC connection is no longer finalized in these cases. A few other cases now lead to stream reset instead of connection finalization. --- src/http/v3/ngx_http_v3.c | 12 +++++- src/http/v3/ngx_http_v3_request.c | 17 ++------- src/http/v3/ngx_http_v3_streams.c | 79 +++++++++++++++++++++++++++++++-------- src/http/v3/ngx_http_v3_tables.c | 4 ++ 4 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 500113509..97d8a5e34 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -33,7 +33,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); if (h3c == NULL) { - return NGX_ERROR; + goto failed; } h3c->max_push_id = (uint64_t) -1; @@ -49,7 +49,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) cln = ngx_pool_cleanup_add(pc->pool, 0); if (cln == NULL) { - return NGX_ERROR; + goto failed; } cln->handler = ngx_http_v3_cleanup_session; @@ -58,6 +58,14 @@ ngx_http_v3_init_session(ngx_connection_t *c) hc->v3_session = h3c; return ngx_http_v3_send_settings(c); + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "failed to create http3 session"); + return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index bb9a72248..5c905bc3a 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -65,8 +65,6 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_http_core_srv_conf_t *cscf; if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); ngx_http_close_connection(c); return; } @@ -110,8 +108,6 @@ ngx_http_v3_init(ngx_connection_t *c) h3c->goaway = 1; if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "goaway error"); ngx_http_close_connection(c); return; } @@ -287,15 +283,14 @@ ngx_http_v3_process_request(ngx_event_t *rev) rc = ngx_http_v3_parse_headers(c, st, b); if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request headers"); + ngx_quic_reset_stream(c, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); break; } if (rc == NGX_ERROR) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); break; } @@ -1167,17 +1162,13 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } if (rc > 0) { - ngx_http_v3_finalize_connection(r->connection, rc, - "client sent invalid body"); + ngx_quic_reset_stream(r->connection, rc); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent invalid body"); return NGX_HTTP_BAD_REQUEST; } if (rc == NGX_ERROR) { - ngx_http_v3_finalize_connection(r->connection, - NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 2ff1320ae..257ec317a 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -283,7 +283,7 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) sc = ngx_quic_open_stream(c, 0); if (sc == NULL) { - return NULL; + goto failed; } p = buf; @@ -318,7 +318,13 @@ ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) failed: - ngx_http_v3_close_uni_stream(sc); + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create push stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } return NULL; } @@ -368,7 +374,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) sc = ngx_quic_open_stream(c, 0); if (sc == NULL) { - return NULL; + goto failed; } sc->quic->cancelable = 1; @@ -405,7 +411,13 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) failed: - ngx_http_v3_close_uni_stream(sc); + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create server stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } return NULL; } @@ -424,7 +436,7 @@ ngx_http_v3_send_settings(ngx_connection_t *c) cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); if (cc == NULL) { - return NGX_DECLINED; + return NGX_ERROR; } h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); @@ -457,6 +469,10 @@ ngx_http_v3_send_settings(ngx_connection_t *c) failed: + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send settings"); ngx_http_v3_close_uni_stream(cc); return NGX_ERROR; @@ -475,7 +491,7 @@ ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); if (cc == NULL) { - return NGX_DECLINED; + return NGX_ERROR; } n = ngx_http_v3_encode_varlen_int(NULL, id); @@ -495,6 +511,10 @@ ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) failed: + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send goaway"); ngx_http_v3_close_uni_stream(cc); return NGX_ERROR; @@ -510,7 +530,7 @@ ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client ack section %ui", stream_id); + "http3 send section acknowledgement %ui", stream_id); dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { @@ -524,11 +544,21 @@ ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) h3c->total_bytes += n; if (dc->send(dc, buf, n) != (ssize_t) n) { - ngx_http_v3_close_uni_stream(dc); - return NGX_ERROR; + goto failed; } return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send section acknowledgement"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send section acknowledgement"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; } @@ -541,7 +571,7 @@ ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client cancel stream %ui", stream_id); + "http3 send stream cancellation %ui", stream_id); dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { @@ -555,11 +585,20 @@ ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) h3c->total_bytes += n; if (dc->send(dc, buf, n) != (ssize_t) n) { - ngx_http_v3_close_uni_stream(dc); - return NGX_ERROR; + goto failed; } return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send stream cancellation"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; } @@ -572,7 +611,7 @@ ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 client increment insert count %ui", inc); + "http3 send insert count increment %ui", inc); dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); if (dc == NULL) { @@ -586,11 +625,21 @@ ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) h3c->total_bytes += n; if (dc->send(dc, buf, n) != (ssize_t) n) { - ngx_http_v3_close_uni_stream(dc); - return NGX_ERROR; + goto failed; } return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send insert count increment"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send insert count increment"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c index 348088774..c6d543ac4 100644 --- a/src/http/v3/ngx_http_v3_tables.c +++ b/src/http/v3/ngx_http_v3_tables.c @@ -589,6 +589,10 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) if (h3c->nblocked == h3scf->max_blocked_streams) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client exceeded http3_max_blocked_streams limit"); + + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, + "too many blocked streams"); return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; } -- cgit v1.2.3 From a6fb8fe85077bd10e11231c70ece803284890520 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 15:47:06 +0300 Subject: HTTP/3: allowed QUIC stream connection reuse. A QUIC stream connection is treated as reusable until first bytes of request arrive, which is also when the request object is now allocated. A connection closed as a result of draining, is reset with the error code H3_REQUEST_REJECTED. Such behavior is allowed by quic-http-34: Once a request stream has been opened, the request MAY be cancelled by either endpoint. Clients cancel requests if the response is no longer of interest; servers cancel requests if they are unable to or choose not to respond. When the server cancels a request without performing any application processing, the request is considered "rejected." The server SHOULD abort its response stream with the error code H3_REQUEST_REJECTED. The client can treat requests rejected by the server as though they had never been sent at all, thereby allowing them to be retried later. --- src/http/ngx_http_request.c | 27 ++++--- src/http/v3/ngx_http_v3.h | 4 ++ src/http/v3/ngx_http_v3_request.c | 144 +++++++++++++++++++++++++++++++++----- src/http/v3/ngx_http_v3_streams.c | 17 +++-- 4 files changed, 154 insertions(+), 38 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 88516cb4d..7125e7dd1 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -3731,15 +3731,14 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) log->action = "closing request"; - if (r->connection->timedout) { + if (r->connection->timedout +#if (NGX_HTTP_QUIC) + && r->connection->quic == NULL +#endif + ) + { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); -#if (NGX_HTTP_V3) - if (r->connection->quic) { - (void) ngx_quic_reset_stream(r->connection, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); - } else -#endif if (clcf->reset_timedout_connection) { linger.l_onoff = 1; linger.l_linger = 0; @@ -3751,14 +3750,6 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) "setsockopt(SO_LINGER) failed"); } } - - } else if (!r->response_sent) { -#if (NGX_HTTP_V3) - if (r->connection->quic) { - (void) ngx_quic_reset_stream(r->connection, - NGX_HTTP_V3_ERR_INTERNAL_ERROR); - } -#endif } /* the various request strings were allocated from r->pool */ @@ -3818,6 +3809,12 @@ ngx_http_close_connection(ngx_connection_t *c) #endif +#if (NGX_HTTP_V3) + if (ngx_http_v3_connection(c)) { + ngx_http_v3_reset_connection(c); + } +#endif + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 53f38a7f2..97e8a1c29 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -90,6 +90,9 @@ #define ngx_http_v3_shutdown_connection(c, code, reason) \ ngx_quic_shutdown_connection(c->quic->parent, code, reason) +#define ngx_http_v3_connection(c) \ + ((c)->quic ? ngx_http_quic_get_connection(c)->addr_conf->http3 : 0) + typedef struct { size_t max_table_capacity; @@ -138,6 +141,7 @@ struct ngx_http_v3_session_s { void ngx_http_v3_init(ngx_connection_t *c); +void ngx_http_v3_reset_connection(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); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5c905bc3a..6f980ed0b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,7 @@ #include +static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); 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, @@ -53,12 +54,8 @@ static const struct { void ngx_http_v3_init(ngx_connection_t *c) { - size_t size; uint64_t n; - ngx_buf_t *b; ngx_event_t *rev; - ngx_pool_cleanup_t *cln; - ngx_http_request_t *r; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -96,7 +93,7 @@ ngx_http_v3_init(ngx_connection_t *c) h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { - ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + c->close = 1; ngx_http_close_connection(c); return; } @@ -116,21 +113,57 @@ ngx_http_v3_init(ngx_connection_t *c) "reached maximum number of requests"); } - cln = ngx_pool_cleanup_add(c->pool, 0); - if (cln == NULL) { + rev = c->read; + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } +} - cln->handler = ngx_http_v3_cleanup_request; - cln->data = c; - h3c->nrequests++; +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_v3_session_t *h3c; + ngx_http_core_srv_conf_t *cscf; - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_connection(c); + return; } + if (c->close) { + ngx_http_close_connection(c); + return; + } + + hc = c->data; cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; @@ -159,8 +192,49 @@ ngx_http_v3_init(ngx_connection_t *c) b->end = b->last + size; } + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + /* + * We are trying to not hold c->buffer's memory for an idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } + + return; + } + + if (n == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client closed connection"); + ngx_http_close_connection(c); + return; + } + + b->last += n; + c->log->action = "reading client request"; + ngx_reusable_connection(c, 0); + r = ngx_http_create_request(c); if (r == NULL) { ngx_http_close_connection(c); @@ -171,7 +245,7 @@ ngx_http_v3_init(ngx_connection_t *c) r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); if (r->v3_parse == NULL) { - ngx_http_close_connection(c); + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } @@ -179,23 +253,59 @@ ngx_http_v3_init(ngx_connection_t *c) * cscf->large_client_header_buffers.num; c->data = r; - c->requests = n + 1; + c->requests = (c->quic->id >> 2) + 1; - rev = c->read; - rev->handler = ngx_http_v3_process_request; + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = r; + h3c = ngx_http_v3_get_session(c); + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + rev->handler = ngx_http_v3_process_request; ngx_http_v3_process_request(rev); } +void +ngx_http_v3_reset_connection(ngx_connection_t *c) +{ + if (c->timedout) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + + } else if (c->close) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + + } else if (c->requests == 0 || c->error) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +} + + static void ngx_http_v3_cleanup_request(void *data) { - ngx_connection_t *c = data; + ngx_http_request_t *r = data; + ngx_connection_t *c; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; + c = r->connection; + + if (!r->response_sent) { + c->error = 1; + } + h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 257ec317a..23b16cbc2 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -49,7 +49,8 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, "reached maximum number of uni streams"); - ngx_http_close_connection(c); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); return; } @@ -57,7 +58,11 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { - ngx_http_close_connection(c); + 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; } @@ -79,12 +84,12 @@ ngx_http_v3_close_uni_stream(ngx_connection_t *c) ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; - us = c->data; - h3c = ngx_http_v3_get_session(c); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); - if (us->index >= 0) { + us = c->data; + + if (us && us->index >= 0) { + h3c = ngx_http_v3_get_session(c); h3c->known_streams[us->index] = NULL; } -- cgit v1.2.3 From 5c99f43e6f6f2e97a625bf00330f2f8d5af29815 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 14:48:11 +0300 Subject: HTTP/3: send Stream Cancellation instruction. As per quic-qpack-21: When a stream is reset or reading is abandoned, the decoder emits a Stream Cancellation instruction. Previously the instruction was not sent. Now it's sent when closing QUIC stream connection if dynamic table capacity is non-zero and eof was not received from client. The latter condition means that a trailers section may still be on its way from client and the stream needs to be cancelled. --- src/http/v3/ngx_http_v3_request.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6f980ed0b..e0c3a71ba 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -279,6 +279,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->max_table_capacity > 0 && !c->read->eof) { + (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + } + if (c->timedout) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); -- cgit v1.2.3 From 54655cebbb99693c7f05b689eb450a2c47ca79eb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 12 Nov 2021 16:29:07 +0300 Subject: QUIC: stop processing new client streams at the closing state. --- src/event/quic/ngx_event_quic_streams.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index e1c131590..68f6b867c 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -314,7 +314,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) qc = ngx_quic_get_connection(c); - if (qc->shutdown) { + if (qc->shutdown || qc->closing) { return NGX_QUIC_STREAM_GONE; } @@ -385,7 +385,7 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) return NULL; } - if (qc->shutdown) { + if (qc->shutdown || qc->closing) { return NGX_QUIC_STREAM_GONE; } } -- cgit v1.2.3 From 50dd9ba7e85a8d4eeecaad8776f83adc607fd132 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 Nov 2021 19:07:00 +0300 Subject: QUIC: reject streams which we could not create. The reasons why a stream may not be created by server currently include hitting worker_connections limit and memory allocation error. Previously in these cases the entire QUIC connection was closed and all its streams were shut down. Now the new stream is rejected and existing streams continue working. To reject an HTTP/3 request stream, RESET_STREAM and STOP_SENDING with H3_REQUEST_REJECTED error code are sent to client. HTTP/3 uni streams and Stream streams are not rejected. --- src/event/quic/ngx_event_quic.h | 3 ++ src/event/quic/ngx_event_quic_streams.c | 75 +++++++++++++++++++++++++++++++-- src/http/modules/ngx_http_quic_module.c | 3 ++ src/stream/ngx_stream_quic_module.c | 3 ++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 839570af0..1007c491d 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -61,6 +61,9 @@ typedef struct { ngx_flag_t retry; ngx_flag_t gso_enabled; ngx_str_t host_key; + ngx_int_t stream_close_code; + ngx_int_t stream_reject_code_uni; + ngx_int_t stream_reject_code_bidi; u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 68f6b867c..bcb10d74d 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -15,6 +15,7 @@ static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id); +static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); static ngx_int_t ngx_quic_init_stream(ngx_quic_stream_t *qs); static void ngx_quic_init_streams_handler(ngx_connection_t *c); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, @@ -377,8 +378,13 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) for ( /* void */ ; min_id < id; min_id += 0x04) { qs = ngx_quic_create_stream(c, min_id); + if (qs == NULL) { - return NULL; + if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { + return NULL; + } + + continue; } if (ngx_quic_init_stream(qs) != NGX_OK) { @@ -390,7 +396,66 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) } } - return ngx_quic_create_stream(c, id); + qs = ngx_quic_create_stream(c, id); + + if (qs == NULL) { + if (ngx_quic_reject_stream(c, id) != NGX_OK) { + return NULL; + } + + return NGX_QUIC_STREAM_GONE; + } + + return qs; +} + + +static ngx_int_t +ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t code; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->conf->stream_reject_code_uni + : qc->conf->stream_reject_code_bidi; + + if (code == 0) { + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL reject err:0x%xL", id, code); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = id; + frame->u.reset_stream.error_code = code; + frame->u.reset_stream.final_size = 0; + + ngx_quic_queue_frame(qc, frame); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = id; + frame->u.stop_sending.error_code = code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; } @@ -866,7 +931,9 @@ ngx_quic_stream_cleanup_handler(void *data) if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { - if (!c->read->pending_eof && !c->read->error) { + if (!c->read->pending_eof && !c->read->error + && qc->conf->stream_close_code) + { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { goto done; @@ -875,7 +942,7 @@ ngx_quic_stream_cleanup_handler(void *data) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STOP_SENDING; frame->u.stop_sending.id = qs->id; - frame->u.stop_sending.error_code = 0x100; /* HTTP/3 no error */ + frame->u.stop_sending.error_code = qc->conf->stream_close_code; ngx_quic_queue_frame(qc, frame); } diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 9e6d17ead..323ee2ead 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -314,6 +314,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.sr_enabled = 0 * conf->tp.preferred_address = NULL * conf->host_key = { 0, NULL } + * cong->stream_reject_code_uni = 0; */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; @@ -331,6 +332,8 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->retry = NGX_CONF_UNSET; conf->gso_enabled = NGX_CONF_UNSET; + conf->stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + conf->stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; return conf; } diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index b40b17c93..7ad96a11c 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -241,6 +241,9 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) * conf->tp.retry_scid = { 0, NULL }; * conf->tp.preferred_address = NULL * conf->host_key = { 0, NULL } + * conf->stream_close_code = 0; + * conf->stream_reject_code_uni = 0; + * conf->stream_reject_code_bidi= 0; */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; -- cgit v1.2.3 From bfa2d1d59932b9983e3a694ed651bc4fe349edc5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Nov 2021 18:49:48 +0300 Subject: HTTP/3: fixed compilation with QUIC, but without HTTP/3. --- src/http/modules/ngx_http_quic_module.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c index 323ee2ead..9470df60e 100644 --- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -332,8 +332,10 @@ ngx_http_quic_create_srv_conf(ngx_conf_t *cf) conf->retry = NGX_CONF_UNSET; conf->gso_enabled = NGX_CONF_UNSET; +#if (NGX_HTTP_V3) conf->stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; conf->stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; +#endif return conf; } -- cgit v1.2.3 From 4bb4792907464c98e3fea1bd784caf5c9d456334 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Nov 2021 23:07:38 +0300 Subject: QUIC: update stream flow control credit on STREAM_DATA_BLOCKED. Previously, after receiving STREAM_DATA_BLOCKED, current flow control limit was sent to client. Now, if the limit can be updated to the full window size, it is updated and the new value is sent to client, otherwise nothing is sent. The change lets client update flow control credit on demand. Also, it saves traffic by not sending MAX_STREAM_DATA with the same value twice. --- src/event/quic/ngx_event_quic_streams.c | 81 ++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index bcb10d74d..534e0608a 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -31,6 +31,7 @@ static size_t ngx_quic_max_stream_flow(ngx_connection_t *c); static void ngx_quic_stream_cleanup_handler(void *data); static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last); static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); +static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c); ngx_connection_t * @@ -1190,8 +1191,6 @@ ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) { - uint64_t limit; - ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1217,29 +1216,10 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, return NGX_OK; } - limit = qs->recv_max_data; - - if (ngx_quic_init_stream(qs) != NGX_OK) { - return NGX_ERROR; - } - - } else { - limit = qs->recv_max_data; - } - - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; + return ngx_quic_init_stream(qs); } - frame->level = pkt->level; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = f->id; - frame->u.max_stream_data.limit = limit; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; + return ngx_quic_update_max_stream_data(qs->connection); } @@ -1587,22 +1567,9 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) if (!rev->pending_eof && !rev->error && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { - qs->recv_max_data = qs->recv_offset + qs->recv_window; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flow update msd:%uL", qs->recv_max_data); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { + if (ngx_quic_update_max_stream_data(c) != NGX_OK) { return NGX_ERROR; } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; - frame->u.max_stream_data.id = qs->id; - frame->u.max_stream_data.limit = qs->recv_max_data; - - ngx_quic_queue_frame(qc, frame); } qc->streams.recv_offset += len; @@ -1632,6 +1599,46 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) } +static ngx_int_t +ngx_quic_update_max_stream_data(ngx_connection_t *c) +{ + uint64_t recv_max_data; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + recv_max_data = qs->recv_offset + qs->recv_window; + + if (qs->recv_max_data == recv_max_data) { + return NGX_OK; + } + + qs->recv_max_data = recv_max_data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update msd:%uL", qs->recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { -- cgit v1.2.3 From d4add9784568913fea143004fa2d0af364c03562 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Nov 2021 23:07:51 +0300 Subject: QUIC: handle DATA_BLOCKED frame from client. Previously the frame was not handled and connection was closed with an error. Now, after receiving this frame, global flow control is updated and new flow control credit is sent to client. --- src/event/quic/ngx_event_quic.c | 11 ++++++ src/event/quic/ngx_event_quic_streams.c | 60 ++++++++++++++++++++++++--------- src/event/quic/ngx_event_quic_streams.h | 2 ++ 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 38138a6c1..af6f3e69e 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1252,6 +1252,17 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) break; + case NGX_QUIC_FT_DATA_BLOCKED: + + if (ngx_quic_handle_data_blocked_frame(c, pkt, + &frame.u.data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 534e0608a..ced35370b 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -32,6 +32,7 @@ static void ngx_quic_stream_cleanup_handler(void *data); static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last); static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c); +static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); ngx_connection_t * @@ -1187,6 +1188,14 @@ ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, } +ngx_int_t +ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f) +{ + return ngx_quic_update_max_data(c); +} + + ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) @@ -1544,7 +1553,6 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) uint64_t len; ngx_event_t *rev; ngx_connection_t *pc; - ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1577,22 +1585,9 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) if (qc->streams.recv_max_data <= qc->streams.recv_offset + qc->streams.recv_window / 2) { - qc->streams.recv_max_data = qc->streams.recv_offset - + qc->streams.recv_window; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, - "quic flow update md:%uL", qc->streams.recv_max_data); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { + if (ngx_quic_update_max_data(pc) != NGX_OK) { return NGX_ERROR; } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_MAX_DATA; - frame->u.max_data.max_data = qc->streams.recv_max_data; - - ngx_quic_queue_frame(qc, frame); } return NGX_OK; @@ -1639,6 +1634,41 @@ ngx_quic_update_max_stream_data(ngx_connection_t *c) } +static ngx_int_t +ngx_quic_update_max_data(ngx_connection_t *c) +{ + uint64_t recv_max_data; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + recv_max_data = qc->streams.recv_offset + qc->streams.recv_window; + + if (qc->streams.recv_max_data == recv_max_data) { + return NGX_OK; + } + + qc->streams.recv_max_data = recv_max_data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update md:%uL", qc->streams.recv_max_data); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h index c914fde24..fb6dbbd8f 100644 --- a/src/event/quic/ngx_event_quic_streams.h +++ b/src/event/quic/ngx_event_quic_streams.h @@ -20,6 +20,8 @@ ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, ngx_quic_max_data_frame_t *f); ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f); ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, -- cgit v1.2.3 From 1688afd955e02b4a12ddf394b42a132e5e4daffc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Nov 2021 14:19:31 +0300 Subject: QUIC: additional checks for the RETIRE_CONNECTION_ID frame. --- src/event/quic/ngx_event_quic_connid.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 2ad65077a..8b9805b1e 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -365,6 +365,39 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); + if (f->sequence_number >= qc->server_seqnum) { + /* + * RFC 9000, 19.16. + * + * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence + * number greater than any previously sent to the peer MUST be + * treated as a connection error of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire was never issued"; + + return NGX_ERROR; + } + + qsock = ngx_quic_get_socket(c); + + if (qsock->sid.seqnum == f->sequence_number) { + + /* + * RFC 9000, 19.16. + * + * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST + * NOT refer to the Destination Connection ID field of the packet in + * which the frame is contained. The peer MAY treat this as a + * connection error of type PROTOCOL_VIOLATION. + */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire refers DCID"; + + return NGX_ERROR; + } + qsock = ngx_quic_find_socket(c, f->sequence_number); if (qsock == NULL) { return NGX_OK; -- cgit v1.2.3 From e165526e43d60b9249690faccbb380be20e88af3 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Nov 2021 14:19:36 +0300 Subject: QUIC: fixed handling of RETIRE_CONNECTION_ID frame. Previously, the retired socket was not closed if it didn't match active or backup. New sockets could not be created (due to count limit), since retired socket was not closed before calling ngx_quic_create_sockets(). When replacing retired socket, new socket is only requested after closing old one, to avoid hitting the limit on the number of active connection ids. Together with added restrictions, this fixes an issue when a current socket could be closed during migration, recreated and erroneously reused leading to null pointer dereference. --- src/event/quic/ngx_event_quic_connid.c | 80 ++++++++++++++++++++++------------ src/event/quic/ngx_event_quic_socket.c | 4 +- src/event/quic/ngx_event_quic_socket.h | 1 + 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 8b9805b1e..503a71b4e 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -403,6 +403,10 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, return NGX_OK; } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket #%uL is retired", qsock->sid.seqnum); + + /* check if client is willing to retire sid we have in use */ if (qsock->sid.seqnum == qc->socket->sid.seqnum) { tmp = &qc->socket; @@ -410,46 +414,68 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, tmp = &qc->backup; } else { - tmp = NULL; - } - - if (ngx_quic_create_sockets(c) != NGX_OK) { - return NGX_ERROR; - } - if (tmp) { - /* replace socket in use (active or backup) */ + ngx_quic_close_socket(c, qsock); - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic %s socket #%uL:%uL:%uL retired", - (*tmp) == qc->socket ? "active" : "backup", - (*tmp)->sid.seqnum, (*tmp)->cid->seqnum, - (*tmp)->path->seqnum); - - qsock = ngx_quic_get_unconnected_socket(c); - if (qsock == NULL) { + /* restore socket count up to a limit after deletion */ + if (ngx_quic_create_sockets(c) != NGX_OK) { return NGX_ERROR; } - path = (*tmp)->path; - cid = (*tmp)->cid; + return NGX_OK; + } - ngx_quic_connect(c, qsock, path, cid); + /* preserve path/cid from retired socket */ + path = qsock->path; + cid = qsock->cid; + /* ensure that closing_socket will not drop path and cid */ + path->refcnt++; + cid->refcnt++; - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic %s socket is now #%uL:%uL:%uL (%s)", - (*tmp) == qc->socket ? "active" : "backup", - qsock->sid.seqnum, qsock->cid->seqnum, - qsock->path->seqnum, - ngx_quic_path_state_str(qsock->path)); + ngx_quic_close_socket(c, qsock); - ngx_quic_close_socket(c, *tmp); /* no longer used */ + /* restore original values */ + path->refcnt--; + cid->refcnt--; - *tmp = qsock; + /* restore socket count up to a limit after deletion */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + goto failed; } + qsock = ngx_quic_get_unconnected_socket(c); + if (qsock == NULL) { + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "not enough server IDs"; + goto failed; + } + + ngx_quic_connect(c, qsock, path, cid); + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %s socket is now #%uL:%uL:%uL (%s)", + (*tmp) == qc->socket ? "active" : "backup", + qsock->sid.seqnum, qsock->cid->seqnum, + qsock->path->seqnum, + ngx_quic_path_state_str(qsock->path)); + + /* restore active/backup pointer in quic connection */ + *tmp = qsock; + return NGX_OK; + +failed: + + /* + * socket was closed, path and cid were preserved artifically + * to be reused, but it didn't happen, thus unref here + */ + + ngx_quic_unref_path(c, path); + ngx_quic_unref_client_id(c, cid); + + return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index ddde68f09..3b507fca8 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -14,8 +14,6 @@ static ngx_int_t ngx_quic_create_temp_socket(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_str_t *dcid, ngx_quic_path_t *path, ngx_quic_client_id_t *cid); -static void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path); - ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, @@ -207,7 +205,7 @@ ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) } -static void +void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path) { ngx_quic_connection_t *qc; diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h index 1372822c0..72ed67ad0 100644 --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -22,6 +22,7 @@ ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_socket_t *qsock); void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); +void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path); void ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *qsock, ngx_quic_path_t *path, ngx_quic_client_id_t *cid); -- cgit v1.2.3 From b8aa869a6f4ee0fda4ab32d5c002fc10bd738079 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 29 Nov 2021 11:49:09 +0300 Subject: QUIC: refactored multiple QUIC packets handling. Single UDP datagram may contain multiple QUIC datagrams. In order to facilitate handling of such cases, 'first' flag in the ngx_quic_header_t structure is introduced. --- src/event/quic/ngx_event_quic.c | 13 ++++++++----- src/event/quic/ngx_event_quic_migration.c | 7 +------ src/event/quic/ngx_event_quic_transport.h | 1 + 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index af6f3e69e..53e50a8b9 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -358,7 +358,7 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); /* A stateless reset uses an entire UDP datagram */ - if (pkt->raw->start != pkt->data) { + if (!pkt->first) { return NGX_DECLINED; } @@ -666,7 +666,7 @@ static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) { size_t size; - u_char *p; + u_char *p, *start; ngx_int_t rc; ngx_uint_t good; ngx_quic_header_t pkt; @@ -676,7 +676,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) size = b->last - b->pos; - p = b->pos; + p = start = b->pos; while (p < b->last) { @@ -685,6 +685,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) pkt.data = p; pkt.len = b->last - p; pkt.log = c->log; + pkt.first = (p == start) ? 1 : 0; pkt.flags = p[0]; pkt.raw->pos++; @@ -979,8 +980,10 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->decrypted = 1; - if (ngx_quic_update_paths(c, pkt) != NGX_OK) { - return NGX_ERROR; + if (pkt->first) { + if (ngx_quic_update_paths(c, pkt) != NGX_OK) { + return NGX_ERROR; + } } if (c->ssl == NULL) { diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index bea51081d..4f7ab2c97 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -388,12 +388,7 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) update: - if (pkt->raw->start == pkt->data) { - len = pkt->raw->last - pkt->raw->start; - - } else { - len = 0; - } + len = pkt->raw->last - pkt->raw->start; /* TODO: this may be too late in some cases; * for example, if error happens during decrypt(), we cannot diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 2d30b010c..2cd372cba 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -334,6 +334,7 @@ typedef struct { unsigned decrypted:1; unsigned validated:1; unsigned retried:1; + unsigned first:1; } ngx_quic_header_t; -- cgit v1.2.3 From 82b4912a8ebbbff46a70c0186bde0a0e6cfdab2c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 29 Nov 2021 11:51:14 +0300 Subject: QUIC: fixed migration during NAT rebinding. The RFC 9000 allows a packet from known CID arrive from unknown path: These requirements regarding connection ID reuse apply only to the sending of packets, as unintentional changes in path without a change in connection ID are possible. For example, after a period of network inactivity, NAT rebinding might cause packets to be sent on a new path when the client resumes sending. Before the patch, such packets were rejected with an error in the ngx_quic_check_migration() function. Removing the check makes the separate function excessive - remaining checks are early migration check and "disable_active_migration" check. The latter is a transport parameter sent to client and it should not be used by server. The server should send "disable_active_migration" "if the endpoint does not support active connection migration" (18.2). The support status depends on nginx configuration: to have migration working with multiple workers, you need bpf helper, available on recent Linux systems. The patch does not set "disable_active_migration" automatically and leaves it for the administrator. By default, active migration is enabled. RFC 900 says that it is ok to migrate if the peer violates "disable_active_migration" flag requirements: If the peer violates this requirement, the endpoint MUST either drop the incoming packets on that path without generating a Stateless Reset OR proceed with path validation and allow the peer to migrate. Generating a Stateless Reset or closing the connection would allow third parties in the network to cause connections to close by spoofing or otherwise manipulating observed traffic. So, nginx adheres to the second option and proceeds to path validation. Note: The ngtcp2 may be used for testing both active migration and NAT rebinding: ngtcp2/client --change-local-addr=200ms --delay-stream=500ms ngtcp2/client --change-local-addr=200ms --delay-stream=500ms --nat-rebinding \ --- src/event/quic/ngx_event_quic.c | 17 +++++-- src/event/quic/ngx_event_quic_migration.c | 79 +++++++------------------------ src/event/quic/ngx_event_quic_migration.h | 4 +- 3 files changed, 31 insertions(+), 69 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 53e50a8b9..03d703b2c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -812,11 +812,6 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } - rc = ngx_quic_check_migration(c, pkt); - if (rc != NGX_OK) { - return rc; - } - if (pkt->level != ssl_encryption_application) { if (pkt->version != qc->version) { @@ -825,6 +820,18 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_DECLINED; } + if (pkt->first) { + if (ngx_quic_find_path(c, c->udp->dgram->sockaddr, + c->udp->dgram->socklen) + == NULL) + { + /* packet comes from unknown path, possibly migration */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too early migration attempt"); + return NGX_DECLINED; + } + } + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { return NGX_DECLINED; } diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 4f7ab2c97..887824573 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -278,66 +278,6 @@ ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, } -ngx_int_t -ngx_quic_check_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) -{ - ngx_quic_path_t *path; - ngx_quic_socket_t *qsock; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - qsock = ngx_quic_get_socket(c); - - if (c->udp->dgram == NULL) { - /* 2nd QUIC packet in first UDP datagram */ - return NGX_OK; - } - - path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, - c->udp->dgram->socklen); - if (path == NULL) { - /* packet comes from unknown path, possibly migration */ - - if (qc->tp.disable_active_migration) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic migration disabled, dropping packet " - "from unknown path"); - return NGX_DECLINED; - } - - if (pkt->level != ssl_encryption_application) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too early migration attempt"); - return NGX_DECLINED; - } - - return NGX_OK; - } - - /* packet from known path */ - - if (qsock->path == NULL) { - /* client switched to previously unused server id */ - return NGX_OK; - } - - if (path == qsock->path) { - /* regular packet to expected path */ - return NGX_OK; - } - - /* client is trying to use server id already used on other path */ - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic attempt to use socket #%uL:%uL:%uL with path #%uL", - qsock->sid.seqnum, qsock->cid->seqnum, - qsock->path->seqnum, path->seqnum); - - return NGX_DECLINED; -} - - ngx_int_t ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -348,9 +288,10 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_connection_t *qc; qsock = ngx_quic_get_socket(c); - path = qsock->path; - if (path) { + if (c->udp->dgram == NULL && qsock->path) { + /* 1st ever packet in connection, path already exists */ + path = qsock->path; goto update; } @@ -363,6 +304,20 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) if (path == NULL) { return NGX_ERROR; } + + if (qsock->path) { + /* NAT rebinding case: packet to same CID, but from new address */ + + ngx_quic_unref_path(c, qsock->path); + + qsock->path = path; + path->refcnt++; + + goto update; + } + + } else if (qsock->path) { + goto update; } /* prefer unused client IDs if available */ diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 7c37c9085..4ad3213b6 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -30,11 +30,11 @@ ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_path_challenge_frame_t *f); +ngx_quic_path_t *ngx_quic_find_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen); ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, socklen_t socklen); -ngx_int_t ngx_quic_check_migration(ngx_connection_t *c, - ngx_quic_header_t *pkt); ngx_int_t ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt); ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt); -- cgit v1.2.3 From 0de6a1ebb48c13e658b0e94be1a3045a725c4624 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 18 Nov 2021 14:33:21 +0300 Subject: QUIC: removed unnecessary closing of active/backup sockets. All open sockets are stored in a queue. There is no need to close some of them separately. If it happens that active and backup point to same socket, double close may happen (leading to possible segfault). --- src/event/quic/ngx_event_quic_socket.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 3b507fca8..4a9fb232d 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -288,12 +288,6 @@ ngx_quic_close_sockets(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - ngx_quic_close_socket(c, qc->socket); - - if (qc->backup) { - ngx_quic_close_socket(c, qc->backup); - } - while (!ngx_queue_empty(&qc->sockets)) { q = ngx_queue_head(&qc->sockets); qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); -- cgit v1.2.3 From ac851d7f69362ffeff4fe1f581434ab3ac9f9c59 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 30 Nov 2021 14:30:59 +0300 Subject: QUIC: simplified ngx_quic_send_alert() callback. Removed sending CLOSE_CONNECTION directly to avoid duplicate frames, since it is sent later again in SSL_do_handshake() error handling. As such, removed redundant settings of error fields set elsewhere. While here, improved debug message. --- src/event/quic/ngx_event_quic_output.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index f121d2327..178f0b28a 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -847,23 +847,17 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_alert() lvl:%d alert:%d", - (int) level, (int) alert); + "quic ngx_quic_send_alert() level:%s alert:%d", + ngx_quic_level_name(level), (int) alert); + + /* already closed on regular shutdown */ qc = ngx_quic_get_connection(c); if (qc == NULL) { return 1; } - qc->error_level = level; qc->error = NGX_QUIC_ERR_CRYPTO(alert); - qc->error_reason = "TLS alert"; - qc->error_app = 0; - qc->error_ftype = 0; - - if (ngx_quic_send_cc(c) != NGX_OK) { - return 0; - } return 1; } -- cgit v1.2.3 From a981efe6e803acc17af428ea16385df1e8e05c00 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 30 Nov 2021 14:30:59 +0300 Subject: QUIC: ngx_quic_send_alert() callback moved to its place. --- src/event/quic/ngx_event_quic_output.c | 26 -------------------------- src/event/quic/ngx_event_quic_output.h | 3 --- src/event/quic/ngx_event_quic_ssl.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 178f0b28a..4d97626a9 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -837,32 +837,6 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) } -int -ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, - uint8_t alert) -{ - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_send_alert() level:%s alert:%d", - ngx_quic_level_name(level), (int) alert); - - /* already closed on regular shutdown */ - - qc = ngx_quic_get_connection(c); - if (qc == NULL) { - return 1; - } - - qc->error = NGX_QUIC_ERR_CRYPTO(alert); - - return 1; -} - - ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h index 396932fe2..66b7d12ff 100644 --- a/src/event/quic/ngx_event_quic_output.h +++ b/src/event/quic/ngx_event_quic_output.h @@ -19,9 +19,6 @@ ngx_int_t ngx_quic_output(ngx_connection_t *c); ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt); -int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, - enum ssl_encryption_level_t level, uint8_t alert); - ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); ngx_int_t ngx_quic_send_cc(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 839bb3161..9fc5c3985 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -34,6 +34,8 @@ static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const uint8_t *data, size_t len); static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); @@ -287,6 +289,32 @@ ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) } +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() level:%s alert:%d", + ngx_quic_level_name(level), (int) alert); + + /* already closed on regular shutdown */ + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + + return 1; +} + + ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) -- cgit v1.2.3 From 468641cbc30c67bef830d92b7a84044c6a09378e Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 1 Dec 2021 18:33:29 +0300 Subject: QUIC: removed excessive check. The c->udp->dgram may be NULL only if the quic connection was just created: the ngx_event_udp_recvmsg() passes information about datagrams to existing connections by providing information in c->udp. If case of a new connection, c->udp is allocated by the QUIC code during creation of quic connection (it uses c->sockaddr to initialize qsock->path). Thus the check for qsock->path is excessive and can be read wrong, assuming that other options possible, leading to warnings from clang static analyzer. --- src/event/quic/ngx_event_quic_migration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 887824573..c3758ad4f 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -289,7 +289,7 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) qsock = ngx_quic_get_socket(c); - if (c->udp->dgram == NULL && qsock->path) { + if (c->udp->dgram == NULL) { /* 1st ever packet in connection, path already exists */ path = qsock->path; goto update; -- cgit v1.2.3 From 7e7e552a10b9e5d0fd94b1a657061253ccac709e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 2 Dec 2021 13:59:09 +0300 Subject: HTTP/3: adjusted ALPN macro names to align with 61abb35bb8cf. --- src/http/modules/ngx_http_quic_module.h | 2 +- src/http/modules/ngx_http_ssl_module.c | 8 ++++---- src/http/v3/ngx_http_v3.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h index 8cadfbb87..3b96fcc47 100644 --- a/src/http/modules/ngx_http_quic_module.h +++ b/src/http/modules/ngx_http_quic_module.h @@ -14,7 +14,7 @@ #include -#define NGX_HTTP_QUIC_ALPN_ADVERTISE "\x0Ahq-interop" +#define NGX_HTTP_QUIC_ALPN_PROTO "\x0Ahq-interop" #define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index f2a85f12e..afeb68462 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -455,15 +455,15 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, if (hc->addr_conf->quic) { #if (NGX_HTTP_V3) if (hc->addr_conf->http3) { - srv = (unsigned char *) NGX_HTTP_V3_ALPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_V3_ALPN_ADVERTISE) - 1; + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; } else #endif { - srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_ADVERTISE; - srvlen = sizeof(NGX_HTTP_QUIC_ALPN_ADVERTISE) - 1; + srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_QUIC_ALPN_PROTO) - 1; fmt = NGX_HTTP_QUIC_ALPN_DRAFT_FMT; } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 97e8a1c29..758cbd1af 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -19,7 +19,7 @@ #include -#define NGX_HTTP_V3_ALPN_ADVERTISE "\x02h3" +#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" #define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" #define NGX_HTTP_V3_VARLEN_INT_LEN 4 -- cgit v1.2.3 From e6949057ea3fcdd6f0d1559e11e9163c48a311a0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 2 Dec 2021 13:59:56 +0300 Subject: QUIC: logging of CRYPTO frame payload under NGX_QUIC_DEBUG_FRAMES. --- src/event/quic/ngx_event_quic_frames.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 8d9fe24c2..4a3902f7f 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -573,6 +573,20 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) case NGX_QUIC_FT_CRYPTO: p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", f->u.crypto.length, f->u.crypto.offset); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + break; case NGX_QUIC_FT_PADDING: -- cgit v1.2.3 From ea55dbccb248629628baad2b85d7634c82b613ec Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 2 Dec 2021 14:09:52 +0300 Subject: QUIC: fixed using of retired connection id (ticket #2289). RFC 9000 19.16 The sequence number specified in a RETIRE_CONNECTION_ID frame MUST NOT refer to the Destination Connection ID field of the packet in which the frame is contained. Before the patch, the RETIRE_CONNECTION_ID frame was sent before switching to the new client id. If retired client id was currently in use, this lead to violation of the spec. --- src/event/quic/ngx_event_quic_connid.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 503a71b4e..d87948021 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -77,6 +77,7 @@ ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_new_conn_id_frame_t *f) { + uint64_t seq; ngx_str_t id; ngx_queue_t *q; ngx_quic_client_id_t *cid, *item; @@ -173,10 +174,7 @@ retire: } /* this connection id must be retired */ - - if (ngx_quic_send_retire_connection_id(c, cid->seqnum) != NGX_OK) { - return NGX_ERROR; - } + seq = cid->seqnum; if (cid->refcnt) { /* we are going to retire client id which is in use */ @@ -187,6 +185,10 @@ retire: } else { ngx_quic_unref_client_id(c, cid); } + + if (ngx_quic_send_retire_connection_id(c, seq) != NGX_OK) { + return NGX_ERROR; + } } done: -- cgit v1.2.3 From 731915a0c5e90b79d3cca1a4b0a3c33e1f77631c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 6 Dec 2021 13:02:36 +0300 Subject: HTTP/3: merged ngx_http_quic_module into ngx_http_v3_module. --- auto/modules | 19 +- auto/options | 3 - src/http/modules/ngx_http_quic_module.c | 570 -------------------------------- src/http/modules/ngx_http_quic_module.h | 28 -- src/http/modules/ngx_http_ssl_module.c | 15 +- src/http/ngx_http.c | 24 +- src/http/ngx_http.h | 3 - src/http/ngx_http_core_module.c | 6 +- src/http/ngx_http_request.c | 18 +- src/http/ngx_http_upstream.c | 4 +- src/http/v3/ngx_http_v3.h | 10 +- src/http/v3/ngx_http_v3_module.c | 431 +++++++++++++++++++++++- src/http/v3/ngx_http_v3_request.c | 123 ++++++- 13 files changed, 582 insertions(+), 672 deletions(-) delete mode 100644 src/http/modules/ngx_http_quic_module.c delete mode 100644 src/http/modules/ngx_http_quic_module.h diff --git a/auto/modules b/auto/modules index 09651f32b..f98eeafed 100644 --- a/auto/modules +++ b/auto/modules @@ -437,9 +437,11 @@ if [ $HTTP = YES ]; then fi if [ $HTTP_V3 = YES ]; then + USE_OPENSSL_QUIC=YES + HTTP_SSL=YES + have=NGX_HTTP_V3 . auto/have have=NGX_HTTP_HEADERS . auto/have - HTTP_QUIC=YES ngx_module_name=ngx_http_v3_module ngx_module_incs=src/http/v3 @@ -713,21 +715,6 @@ if [ $HTTP = YES ]; then . auto/module fi - if [ $HTTP_QUIC = YES ]; then - USE_OPENSSL_QUIC=YES - have=NGX_HTTP_QUIC . auto/have - HTTP_SSL=YES - - ngx_module_name=ngx_http_quic_module - ngx_module_incs= - ngx_module_deps=src/http/modules/ngx_http_quic_module.h - ngx_module_srcs=src/http/modules/ngx_http_quic_module.c - ngx_module_libs= - ngx_module_link=$HTTP_QUIC - - . auto/module - fi - if [ $HTTP_SSL = YES ]; then USE_OPENSSL=YES have=NGX_HTTP_SSL . auto/have diff --git a/auto/options b/auto/options index d677dd970..51387f412 100644 --- a/auto/options +++ b/auto/options @@ -60,7 +60,6 @@ HTTP_CACHE=YES HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO -HTTP_QUIC=NO HTTP_V2=NO HTTP_V3=NO HTTP_SSI=YES @@ -237,7 +236,6 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --http-scgi-temp-path=*) NGX_HTTP_SCGI_TEMP_PATH="$value" ;; --with-http_ssl_module) HTTP_SSL=YES ;; - --with-http_quic_module) HTTP_QUIC=YES ;; --with-http_v2_module) HTTP_V2=YES ;; --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; @@ -458,7 +456,6 @@ cat << END --without-quic_bpf_module disable ngx_quic_bpf_module --with-http_ssl_module enable ngx_http_ssl_module - --with-http_quic_module enable ngx_http_quic_module --with-http_v2_module enable ngx_http_v2_module --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module diff --git a/src/http/modules/ngx_http_quic_module.c b/src/http/modules/ngx_http_quic_module.c deleted file mode 100644 index 9470df60e..000000000 --- a/src/http/modules/ngx_http_quic_module.c +++ /dev/null @@ -1,570 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - * Copyright (C) Roman Arutyunyan - */ - - -#include -#include -#include - - -static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_quic_add_variables(ngx_conf_t *cf); -static void *ngx_http_quic_create_srv_conf(ngx_conf_t *cf); -static char *ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, - void *child); -static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, - void *data); -static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, - void *data); -static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); - -static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = - { ngx_http_quic_max_ack_delay }; -static ngx_conf_post_t ngx_http_quic_max_udp_payload_size_post = - { ngx_http_quic_max_udp_payload_size }; -static ngx_conf_num_bounds_t ngx_http_quic_ack_delay_exponent_bounds = - { ngx_conf_check_num_bounds, 0, 20 }; -static ngx_conf_num_bounds_t ngx_http_quic_active_connection_id_limit_bounds = - { ngx_conf_check_num_bounds, 2, -1 }; - - -static ngx_command_t ngx_http_quic_commands[] = { - - { ngx_string("quic_max_idle_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_idle_timeout), - NULL }, - - { ngx_string("quic_max_ack_delay"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_ack_delay), - &ngx_http_quic_max_ack_delay_post }, - - { ngx_string("quic_max_udp_payload_size"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_udp_payload_size), - &ngx_http_quic_max_udp_payload_size_post }, - - { ngx_string("quic_initial_max_data"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_data), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_local"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_remote"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote), - NULL }, - - { ngx_string("quic_initial_max_stream_data_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni), - NULL }, - - { ngx_string("quic_initial_max_streams_bidi"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi), - NULL }, - - { ngx_string("quic_initial_max_streams_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni), - NULL }, - - { ngx_string("quic_ack_delay_exponent"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), - &ngx_http_quic_ack_delay_exponent_bounds }, - - { ngx_string("quic_disable_active_migration"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.disable_active_migration), - NULL }, - - { ngx_string("quic_active_connection_id_limit"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.active_connection_id_limit), - &ngx_http_quic_active_connection_id_limit_bounds }, - - { ngx_string("quic_retry"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, retry), - NULL }, - - { ngx_string("quic_gso"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, gso_enabled), - NULL }, - - { ngx_string("quic_host_key"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_http_quic_host_key, - NGX_HTTP_SRV_CONF_OFFSET, - 0, - NULL }, - - ngx_null_command -}; - - -static ngx_http_module_t ngx_http_quic_module_ctx = { - ngx_http_quic_add_variables, /* preconfiguration */ - NULL, /* postconfiguration */ - - NULL, /* create main configuration */ - NULL, /* init main configuration */ - - ngx_http_quic_create_srv_conf, /* create server configuration */ - ngx_http_quic_merge_srv_conf, /* merge server configuration */ - - NULL, /* create location configuration */ - NULL /* merge location configuration */ -}; - - -ngx_module_t ngx_http_quic_module = { - NGX_MODULE_V1, - &ngx_http_quic_module_ctx, /* module context */ - ngx_http_quic_commands, /* 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 -}; - - -static ngx_http_variable_t ngx_http_quic_vars[] = { - - { ngx_string("quic"), NULL, ngx_http_variable_quic, 0, 0, 0 }, - - ngx_http_null_variable -}; - -static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); - - -ngx_int_t -ngx_http_quic_init(ngx_connection_t *c) -{ - uint64_t n; - ngx_quic_conf_t *qcf; - ngx_http_connection_t *hc, *phc; - ngx_http_core_loc_conf_t *clcf; - - hc = c->data; - - hc->ssl = 1; - - if (c->quic == NULL) { - qcf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_quic_module); - - ngx_quic_run(c, qcf); - - return NGX_DONE; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http init quic stream"); - -#if (NGX_HTTP_V3) - if (!hc->addr_conf->http3) -#endif - { - /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ - - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_quic_finalize_connection(c->quic->parent, 0x101, - "unexpected uni stream"); - ngx_http_close_connection(c); - return NGX_DONE; - } - - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - - n = c->quic->id >> 2; - - if (n >= clcf->keepalive_requests) { - ngx_quic_finalize_connection(c->quic->parent, 0x101, - "reached maximum number of requests"); - ngx_http_close_connection(c); - return NGX_DONE; - } - - if (ngx_current_msec - c->quic->parent->start_time - > clcf->keepalive_time) - { - ngx_quic_finalize_connection(c->quic->parent, 0x101, - "reached maximum time for requests"); - ngx_http_close_connection(c); - return NGX_DONE; - } - } - - phc = ngx_http_quic_get_connection(c); - - if (phc->ssl_servername) { - hc->ssl_servername = phc->ssl_servername; - hc->conf_ctx = phc->conf_ctx; - - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - ngx_set_connection_log(c, clcf->error_log); - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_variable_quic(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data) -{ - if (r->connection->quic) { - - v->len = 4; - v->valid = 1; - v->no_cacheable = 1; - v->not_found = 0; - v->data = (u_char *) "quic"; - return NGX_OK; - } - - v->not_found = 1; - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_quic_add_variables(ngx_conf_t *cf) -{ - ngx_http_variable_t *var, *v; - - for (v = ngx_http_quic_vars; v->name.len; v++) { - var = ngx_http_add_variable(cf, &v->name, v->flags); - if (var == NULL) { - return NGX_ERROR; - } - - var->get_handler = v->get_handler; - var->data = v->data; - } - - return NGX_OK; -} - - -static void * -ngx_http_quic_create_srv_conf(ngx_conf_t *cf) -{ - ngx_quic_conf_t *conf; - - conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); - if (conf == NULL) { - return NULL; - } - - /* - * set by ngx_pcalloc(): - * - * conf->tp.original_dcid = { 0, NULL }; - * conf->tp.initial_scid = { 0, NULL }; - * conf->tp.retry_scid = { 0, NULL }; - * conf->tp.sr_token = { 0 } - * conf->tp.sr_enabled = 0 - * conf->tp.preferred_address = NULL - * conf->host_key = { 0, NULL } - * cong->stream_reject_code_uni = 0; - */ - - conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; - conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC; - conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; - conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; - conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; - conf->tp.disable_active_migration = NGX_CONF_UNSET; - conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; - - conf->retry = NGX_CONF_UNSET; - conf->gso_enabled = NGX_CONF_UNSET; -#if (NGX_HTTP_V3) - conf->stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; - conf->stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; -#endif - - return conf; -} - - -static char * -ngx_http_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_quic_conf_t *prev = parent; - ngx_quic_conf_t *conf = child; - - ngx_http_ssl_srv_conf_t *sscf; - - ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, - prev->tp.max_idle_timeout, 60000); - - ngx_conf_merge_msec_value(conf->tp.max_ack_delay, - prev->tp.max_ack_delay, - NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - - ngx_conf_merge_size_value(conf->tp.max_udp_payload_size, - prev->tp.max_udp_payload_size, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_data, - prev->tp.initial_max_data, - 16 * NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local, - prev->tp.initial_max_stream_data_bidi_local, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote, - prev->tp.initial_max_stream_data_bidi_remote, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni, - prev->tp.initial_max_stream_data_uni, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi, - prev->tp.initial_max_streams_bidi, 16); - - ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni, - prev->tp.initial_max_streams_uni, 3); - - ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent, - prev->tp.ack_delay_exponent, - NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - - ngx_conf_merge_value(conf->tp.disable_active_migration, - prev->tp.disable_active_migration, 0); - - ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, - prev->tp.active_connection_id_limit, 2); - - ngx_conf_merge_value(conf->retry, prev->retry, 0); - ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); - - ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); - - if (conf->host_key.len == 0) { - - conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; - conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); - if (conf->host_key.data == NULL) { - return NGX_CONF_ERROR; - } - - if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) - <= 0) - { - return NGX_CONF_ERROR; - } - } - - if (ngx_quic_derive_key(cf->log, "av_token_key", - &conf->host_key, &ngx_http_quic_salt, - conf->av_token_key, NGX_QUIC_AV_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - if (ngx_quic_derive_key(cf->log, "sr_token_key", - &conf->host_key, &ngx_http_quic_salt, - conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); - conf->ssl = &sscf->ssl; - - return NGX_CONF_OK; -} - - -static char * -ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) -{ - ngx_msec_t *sp = data; - - if (*sp >= 16384) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_ack_delay\" must be less than 16384"); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * -ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_udp_payload_size\" must be between " - "%d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * -ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_quic_conf_t *qcf = conf; - - u_char *buf; - size_t size; - ssize_t n; - ngx_str_t *value; - ngx_file_t file; - ngx_file_info_t fi; - - if (qcf->host_key.len) { - return "is duplicate"; - } - - buf = NULL; -#if (NGX_SUPPRESS_WARN) - size = 0; -#endif - - value = cf->args->elts; - - if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&file, sizeof(ngx_file_t)); - file.name = value[1]; - file.log = cf->log; - - file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); - - if (file.fd == NGX_INVALID_FILE) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, - ngx_open_file_n " \"%V\" failed", &file.name); - return NGX_CONF_ERROR; - } - - if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_fd_info_n " \"%V\" failed", &file.name); - goto failed; - } - - size = ngx_file_size(&fi); - - if (size == 0) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"%V\" zero key size", &file.name); - goto failed; - } - - buf = ngx_pnalloc(cf->pool, size); - if (buf == NULL) { - goto failed; - } - - n = ngx_read_file(&file, buf, size, 0); - - if (n == NGX_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_read_file_n " \"%V\" failed", &file.name); - goto failed; - } - - if ((size_t) n != size) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, - ngx_read_file_n " \"%V\" returned only " - "%z bytes instead of %uz", &file.name, n, size); - goto failed; - } - - qcf->host_key.data = buf; - qcf->host_key.len = n; - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - return NGX_CONF_OK; - -failed: - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - if (buf) { - ngx_explicit_memzero(buf, size); - } - - return NGX_CONF_ERROR; -} diff --git a/src/http/modules/ngx_http_quic_module.h b/src/http/modules/ngx_http_quic_module.h deleted file mode 100644 index 3b96fcc47..000000000 --- a/src/http/modules/ngx_http_quic_module.h +++ /dev/null @@ -1,28 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - * Copyright (C) Roman Arutyunyan - */ - - -#ifndef _NGX_HTTP_QUIC_H_INCLUDED_ -#define _NGX_HTTP_QUIC_H_INCLUDED_ - - -#include -#include -#include - - -#define NGX_HTTP_QUIC_ALPN_PROTO "\x0Ahq-interop" -#define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" - - -#define ngx_http_quic_get_connection(c) \ - ((ngx_http_connection_t *) (c)->quic->parent->data) - - -ngx_int_t ngx_http_quic_init(ngx_connection_t *c); - - -#endif /* _NGX_HTTP_QUIC_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index afeb68462..3af21178b 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -416,7 +416,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) const char *fmt; #endif unsigned int srvlen; @@ -424,10 +424,10 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #if (NGX_DEBUG) unsigned int i; #endif -#if (NGX_HTTP_V2 || NGX_HTTP_QUIC) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V2 || NGX_HTTP_QUIC || NGX_DEBUG) +#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); @@ -441,7 +441,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } #endif -#if (NGX_HTTP_V2 || NGX_HTTP_QUIC) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; #endif @@ -451,17 +451,14 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; } else #endif -#if (NGX_HTTP_QUIC) - if (hc->addr_conf->quic) { #if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { if (hc->addr_conf->http3) { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; - } else -#endif - { + } else { srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_QUIC_ALPN_PROTO) - 1; fmt = NGX_HTTP_QUIC_ALPN_DRAFT_FMT; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 908e88103..187d867bb 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1237,13 +1237,11 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) ngx_uint_t ssl; #endif -#if (NGX_HTTP_QUIC) - ngx_uint_t quic; -#endif #if (NGX_HTTP_V2) ngx_uint_t http2; #endif #if (NGX_HTTP_V3) + ngx_uint_t quic; ngx_uint_t http3; #endif @@ -1278,13 +1276,11 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) ssl = lsopt->ssl || addr[i].opt.ssl; #endif -#if (NGX_HTTP_QUIC) - quic = lsopt->quic || addr[i].opt.quic; -#endif #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif #if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; http3 = lsopt->http3 || addr[i].opt.http3; #endif @@ -1320,13 +1316,11 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_SSL) addr[i].opt.ssl = ssl; #endif -#if (NGX_HTTP_QUIC) - addr[i].opt.quic = quic; -#endif #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif #if (NGX_HTTP_V3) + addr[i].opt.quic = quic; addr[i].opt.http3 = http3; #endif @@ -1371,7 +1365,7 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif -#if (NGX_HTTP_QUIC && !defined NGX_QUIC) +#if (NGX_HTTP_V3 && !defined NGX_QUIC) if (lsopt->quic) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, @@ -1841,7 +1835,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) ls->quic = addr->opt.quic; #endif @@ -1874,13 +1868,11 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #if (NGX_HTTP_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; #endif -#if (NGX_HTTP_QUIC) - addrs[i].conf.quic = addr[i].opt.quic; -#endif #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; addrs[i].conf.http3 = addr[i].opt.http3; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1945,13 +1937,11 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #if (NGX_HTTP_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; #endif -#if (NGX_HTTP_QUIC) - addrs6[i].conf.quic = addr[i].opt.quic; -#endif #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; addrs6[i].conf.http3 = addr[i].opt.http3; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index 708defebc..5ac65c859 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -52,9 +52,6 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r, #if (NGX_HTTP_SSL) #include #endif -#if (NGX_HTTP_QUIC) -#include -#endif struct ngx_http_log_ctx_s { diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 19bc75136..1e8e27802 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4097,14 +4097,14 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (ngx_strcmp(value[n].data, "quic") == 0) { -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) lsopt.quic = 1; lsopt.type = SOCK_DGRAM; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"quic\" parameter requires " - "ngx_http_quic_module"); + "ngx_http_v3_module"); return NGX_CONF_ERROR; #endif } @@ -4232,7 +4232,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } #endif -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (lsopt.ssl && lsopt.quic) { return "\"ssl\" parameter is incompatible with \"quic\""; } diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 7125e7dd1..0acedb6e9 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -316,14 +316,6 @@ ngx_http_init_connection(ngx_connection_t *c) c->log_error = NGX_ERROR_INFO; -#if (NGX_HTTP_QUIC) - if (hc->addr_conf->quic) { - if (ngx_http_quic_init(c) == NGX_DONE) { - return; - } - } -#endif - rev = c->read; rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; @@ -335,7 +327,7 @@ ngx_http_init_connection(ngx_connection_t *c) #endif #if (NGX_HTTP_V3) - if (hc->addr_conf->http3) { + if (hc->addr_conf->quic) { ngx_http_v3_init(c); return; } @@ -2746,7 +2738,7 @@ ngx_http_finalize_connection(ngx_http_request_t *r) } #endif -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (r->connection->quic) { ngx_http_close_request(r, 0); return; @@ -2967,7 +2959,7 @@ ngx_http_test_reading(ngx_http_request_t *r) #endif -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (c->quic) { if (c->read->error) { @@ -3732,7 +3724,7 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) log->action = "closing request"; if (r->connection->timedout -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) && r->connection->quic == NULL #endif ) @@ -3810,7 +3802,7 @@ ngx_http_close_connection(ngx_connection_t *c) #endif #if (NGX_HTTP_V3) - if (ngx_http_v3_connection(c)) { + if (c->quic) { ngx_http_v3_reset_connection(c); } #endif diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d27a53ea4..670fc022f 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -525,7 +525,7 @@ ngx_http_upstream_init(ngx_http_request_t *r) } #endif -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (c->quic) { ngx_http_upstream_init_request(r); return; @@ -1365,7 +1365,7 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, } #endif -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (c->quic) { if (c->write->error) { diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 758cbd1af..bee073e1d 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -22,6 +22,9 @@ #define NGX_HTTP_V3_ALPN_PROTO "\x02h3" #define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" +#define NGX_HTTP_QUIC_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" + #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 @@ -74,6 +77,9 @@ #define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 +#define ngx_http_quic_get_connection(c) \ + ((ngx_http_connection_t *) (c)->quic->parent->data) + #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session #define ngx_http_v3_get_module_loc_conf(c, module) \ @@ -90,15 +96,13 @@ #define ngx_http_v3_shutdown_connection(c, code, reason) \ ngx_quic_shutdown_connection(c->quic->parent, code, reason) -#define ngx_http_v3_connection(c) \ - ((c)->quic ? ngx_http_quic_get_connection(c)->addr_conf->http3 : 0) - typedef struct { size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; ngx_uint_t max_uni_streams; + ngx_quic_conf_t quic; } ngx_http_v3_srv_conf_t; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 4b59b097c..14e24d29a 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,15 +10,34 @@ #include +static ngx_int_t ngx_http_v3_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); +static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, + void *data); +static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = + { ngx_http_quic_max_ack_delay }; +static ngx_conf_post_t ngx_http_quic_max_udp_payload_size_post = + { ngx_http_quic_max_udp_payload_size }; +static ngx_conf_num_bounds_t ngx_http_quic_ack_delay_exponent_bounds = + { ngx_conf_check_num_bounds, 0, 20 }; +static ngx_conf_num_bounds_t ngx_http_quic_active_connection_id_limit_bounds = + { ngx_conf_check_num_bounds, 2, -1 }; + + static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("http3_max_table_capacity"), @@ -63,12 +82,119 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_loc_conf_t, push_preload), NULL }, + { ngx_string("quic_max_idle_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_idle_timeout), + NULL }, + + { ngx_string("quic_max_ack_delay"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_ack_delay), + &ngx_http_quic_max_ack_delay_post }, + + { ngx_string("quic_max_udp_payload_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_udp_payload_size), + &ngx_http_quic_max_udp_payload_size_post }, + + { ngx_string("quic_initial_max_data"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_data), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_local"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, + quic.tp.initial_max_stream_data_bidi_local), + NULL }, + + { ngx_string("quic_initial_max_stream_data_bidi_remote"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, + quic.tp.initial_max_stream_data_bidi_remote), + NULL }, + + { ngx_string("quic_initial_max_stream_data_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_stream_data_uni), + NULL }, + + { ngx_string("quic_initial_max_streams_bidi"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_streams_bidi), + NULL }, + + { ngx_string("quic_initial_max_streams_uni"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_streams_uni), + NULL }, + + { ngx_string("quic_ack_delay_exponent"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.ack_delay_exponent), + &ngx_http_quic_ack_delay_exponent_bounds }, + + { ngx_string("quic_disable_active_migration"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.disable_active_migration), + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.tp.active_connection_id_limit), + &ngx_http_quic_active_connection_id_limit_bounds }, + + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.retry), + NULL }, + + { ngx_string("quic_gso"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), + NULL }, + + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + ngx_null_command }; static ngx_http_module_t ngx_http_v3_module_ctx = { - NULL, /* preconfiguration */ + ngx_http_v3_add_variables, /* preconfiguration */ NULL, /* postconfiguration */ NULL, /* create main configuration */ @@ -98,6 +224,55 @@ ngx_module_t ngx_http_v3_module = { }; +static ngx_http_variable_t ngx_http_v3_vars[] = { + + { ngx_string("quic"), NULL, ngx_http_v3_variable_quic, 0, 0, 0 }, + + ngx_http_null_variable +}; + +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + + +static ngx_int_t +ngx_http_v3_variable_quic(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + if (r->connection->quic) { + + v->len = 4; + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + v->data = (u_char *) "quic"; + return NGX_OK; + } + + v->not_found = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + static void * ngx_http_v3_create_srv_conf(ngx_conf_t *cf) { @@ -108,11 +283,42 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) return NULL; } + /* + * set by ngx_pcalloc(): + * + * h3scf->quic.tp.original_dcid = { 0, NULL }; + * h3scf->quic.tp.initial_scid = { 0, NULL }; + * h3scf->quic.tp.retry_scid = { 0, NULL }; + * h3scf->quic.tp.sr_token = { 0 } + * h3scf->quic.tp.sr_enabled = 0 + * h3scf->quic.tp.preferred_address = NULL + * h3scf->quic.host_key = { 0, NULL } + * h3scf->quic.stream_reject_code_uni = 0; + */ + h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_uni_streams = NGX_CONF_UNSET_UINT; + h3scf->quic.tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; + h3scf->quic.tp.max_ack_delay = NGX_CONF_UNSET_MSEC; + h3scf->quic.tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; + h3scf->quic.tp.initial_max_data = NGX_CONF_UNSET_SIZE; + h3scf->quic.tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; + h3scf->quic.tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; + h3scf->quic.tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; + h3scf->quic.tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; + h3scf->quic.tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; + h3scf->quic.tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; + h3scf->quic.tp.disable_active_migration = NGX_CONF_UNSET; + h3scf->quic.tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + h3scf->quic.retry = NGX_CONF_UNSET; + h3scf->quic.gso_enabled = NGX_CONF_UNSET; + h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; + return h3scf; } @@ -123,6 +329,8 @@ 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_conf_merge_size_value(conf->max_table_capacity, prev->max_table_capacity, 16384); @@ -135,7 +343,228 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->max_uni_streams, prev->max_uni_streams, 3); + ngx_conf_merge_msec_value(conf->quic.tp.max_idle_timeout, + prev->quic.tp.max_idle_timeout, 60000); + + ngx_conf_merge_msec_value(conf->quic.tp.max_ack_delay, + prev->quic.tp.max_ack_delay, + NGX_QUIC_DEFAULT_MAX_ACK_DELAY); + + ngx_conf_merge_size_value(conf->quic.tp.max_udp_payload_size, + prev->quic.tp.max_udp_payload_size, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + ngx_conf_merge_size_value(conf->quic.tp.initial_max_data, + prev->quic.tp.initial_max_data, + 16 * NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_bidi_local, + prev->quic.tp.initial_max_stream_data_bidi_local, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_bidi_remote, + prev->quic.tp.initial_max_stream_data_bidi_remote, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_uni, + prev->quic.tp.initial_max_stream_data_uni, + NGX_QUIC_STREAM_BUFSIZE); + + ngx_conf_merge_uint_value(conf->quic.tp.initial_max_streams_bidi, + prev->quic.tp.initial_max_streams_bidi, 16); + + ngx_conf_merge_uint_value(conf->quic.tp.initial_max_streams_uni, + prev->quic.tp.initial_max_streams_uni, 3); + + ngx_conf_merge_uint_value(conf->quic.tp.ack_delay_exponent, + prev->quic.tp.ack_delay_exponent, + NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); + + ngx_conf_merge_value(conf->quic.tp.disable_active_migration, + prev->quic.tp.disable_active_migration, 0); + + ngx_conf_merge_uint_value(conf->quic.tp.active_connection_id_limit, + prev->quic.tp.active_connection_id_limit, 2); + + ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); + ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); + + ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); + + if (conf->quic.host_key.len == 0) { + + conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->quic.host_key.data = ngx_palloc(cf->pool, + conf->quic.host_key.len); + if (conf->quic.host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_CONF_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + conf->quic.ssl = &sscf->ssl; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) +{ + ngx_msec_t *sp = data; + + if (*sp >= 16384) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_ack_delay\" must be less than 16384"); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) +{ + size_t *sp = data; + + if (*sp < NGX_QUIC_MIN_INITIAL_SIZE + || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"quic_max_udp_payload_size\" must be between " + "%d and %d", + NGX_QUIC_MIN_INITIAL_SIZE, + NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_v3_srv_conf_t *h3scf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + ngx_quic_conf_t *qcf; + + qcf = &h3scf->quic; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e0c3a71ba..a4b570370 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,8 @@ #include +static void ngx_http_v3_init_hq_stream(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_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); @@ -53,24 +55,130 @@ static const struct { void ngx_http_v3_init(ngx_connection_t *c) +{ + ngx_http_connection_t *hc, *phc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + hc = c->data; + + hc->ssl = 1; + + if (c->quic == NULL) { + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + ngx_quic_run(c, &h3scf->quic); + + return; + } + + phc = ngx_http_quic_get_connection(c); + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; + hc->conf_ctx = phc->conf_ctx; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + ngx_set_connection_log(c, clcf->error_log); + } + + if (!hc->addr_conf->http3) { + ngx_http_v3_init_hq_stream(c); + return; + } + + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + + } else { + ngx_http_v3_init_request_stream(c); + } +} + + +static void +ngx_http_v3_init_hq_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; ngx_http_connection_t *hc; - ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; - if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = c->data; + + /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "unexpected uni stream"); ngx_http_close_connection(c); return; } - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_http_v3_init_uni_stream(c); + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests) { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "reached maximum number of requests"); + ngx_http_close_connection(c); + return; + } + + if (ngx_current_msec - c->quic->parent->start_time + > clcf->keepalive_time) + { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "reached maximum time for requests"); + ngx_http_close_connection(c); return; } + rev = c->read; + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_init_request_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_event_t *rev; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); #if (NGX_STAT_STUB) @@ -279,8 +387,15 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { + ngx_http_connection_t *hc; ngx_http_v3_srv_conf_t *h3scf; + hc = ngx_http_quic_get_connection(c); + + if (!hc->addr_conf->http3) { + return; + } + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); if (h3scf->max_table_capacity > 0 && !c->read->eof) { -- cgit v1.2.3 From d84c1f7885cc898f626057c314cdae4047c5d513 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 4 Dec 2021 10:52:55 +0300 Subject: HTTP/3: http3_hq directive and NGX_HTTP_V3_HQ macro. Listen quic parameter is no longer supported. --- README | 6 ++--- src/http/modules/ngx_http_ssl_module.c | 41 +++++++++++++++++++--------------- src/http/ngx_http.c | 11 +++------ src/http/ngx_http_core_module.c | 26 +-------------------- src/http/ngx_http_core_module.h | 1 - src/http/ngx_http_request.c | 2 +- src/http/v3/ngx_http_v3.h | 7 ++++-- src/http/v3/ngx_http_v3_module.c | 16 +++++++++++++ src/http/v3/ngx_http_v3_request.c | 24 ++++++++++++-------- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/README b/README index cdbfde7af..1ecf46e07 100644 --- a/README +++ b/README @@ -89,9 +89,8 @@ Experimental QUIC support for nginx 3. Configuration - The HTTP "listen" directive got two new options: "http3" and "quic". - The "http3" option enables HTTP/3 over QUIC on the specified port. - The "quic" option enables QUIC for older HTTP versions on this port. + The HTTP "listen" directive got a new option "http3" which enables + HTTP/3 over QUIC on the specified port. The Stream "listen" directive got a new option "quic" which enables QUIC as client transport protocol instead of TCP or plain UDP. @@ -140,6 +139,7 @@ Experimental QUIC support for nginx http3_max_concurrent_pushes http3_push http3_push_preload + http3_hq (requires NGX_HTTP_V3_HQ macro) An additional variable is available: $quic. The value of $quic is "quic" if QUIC connection is used, diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 3af21178b..f43e51eac 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -417,18 +417,21 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, void *arg) { #if (NGX_HTTP_V3) - const char *fmt; + const char *fmt; #endif - unsigned int srvlen; - unsigned char *srv; + unsigned int srvlen; + unsigned char *srv; #if (NGX_DEBUG) - unsigned int i; + unsigned int i; #endif #if (NGX_HTTP_V2 || NGX_HTTP_V3) - ngx_http_connection_t *hc; + ngx_http_connection_t *hc; +#endif +#if (NGX_HTTP_V3 && NGX_HTTP_V3_HQ) + ngx_http_v3_srv_conf_t *h3scf; #endif #if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) - ngx_connection_t *c; + ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); #endif @@ -452,16 +455,21 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } else #endif #if (NGX_HTTP_V3) - if (hc->addr_conf->quic) { - if (hc->addr_conf->http3) { + if (hc->addr_conf->http3) { + +#if (NGX_HTTP_V3_HQ) + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (h3scf->hq) { + srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; + fmt = NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT; + } else +#endif + { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; - - } else { - srv = (unsigned char *) NGX_HTTP_QUIC_ALPN_PROTO; - srvlen = sizeof(NGX_HTTP_QUIC_ALPN_PROTO) - 1; - fmt = NGX_HTTP_QUIC_ALPN_DRAFT_FMT; } /* QUIC draft */ @@ -1317,16 +1325,13 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl && !addr[a].opt.quic) { + if (!addr[a].opt.ssl && !addr[a].opt.http3) { continue; } if (addr[a].opt.http3) { name = "http3"; - } else if (addr[a].opt.quic) { - name = "quic"; - } else { name = "ssl"; } @@ -1336,7 +1341,7 @@ ngx_http_ssl_init(ngx_conf_t *cf) if (sscf->certificates) { - if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "\"ssl_protocols\" must enable TLSv1.3 for " "the \"listen ... %s\" directive in %s:%ui", diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 187d867bb..4886a88bf 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1241,7 +1241,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_uint_t http2; #endif #if (NGX_HTTP_V3) - ngx_uint_t quic; ngx_uint_t http3; #endif @@ -1280,7 +1279,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, http2 = lsopt->http2 || addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - quic = lsopt->quic || addr[i].opt.quic; http3 = lsopt->http3 || addr[i].opt.http3; #endif @@ -1320,7 +1318,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, addr[i].opt.http2 = http2; #endif #if (NGX_HTTP_V3) - addr[i].opt.quic = quic; addr[i].opt.http3 = http3; #endif @@ -1367,10 +1364,10 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V3 && !defined NGX_QUIC) - if (lsopt->quic) { + if (lsopt->http3) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "nginx was built with OpenSSL that lacks QUIC " - "support, QUIC is not enabled for %V", + "support, HTTP/3 is not enabled for %V", &lsopt->addr_text); } @@ -1836,7 +1833,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; #if (NGX_HTTP_V3) - ls->quic = addr->opt.quic; + ls->quic = addr->opt.http3; #endif return ls; @@ -1872,7 +1869,6 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, addrs[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs[i].conf.quic = addr[i].opt.quic; addrs[i].conf.http3 = addr[i].opt.http3; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1941,7 +1937,6 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, addrs6[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs6[i].conf.quic = addr[i].opt.quic; addrs6[i].conf.http3 = addr[i].opt.http3; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 1e8e27802..12f7bfb27 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4096,22 +4096,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - if (ngx_strcmp(value[n].data, "quic") == 0) { -#if (NGX_HTTP_V3) - lsopt.quic = 1; - lsopt.type = SOCK_DGRAM; - continue; -#else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the \"quic\" parameter requires " - "ngx_http_v3_module"); - return NGX_CONF_ERROR; -#endif - } - if (ngx_strcmp(value[n].data, "http3") == 0) { #if (NGX_HTTP_V3) - lsopt.quic = 1; lsopt.http3 = 1; lsopt.type = SOCK_DGRAM; continue; @@ -4224,20 +4210,10 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } -#if (NGX_HTTP_SSL) - -#if (NGX_HTTP_V3) +#if (NGX_HTTP_SSL && NGX_HTTP_V3) if (lsopt.ssl && lsopt.http3) { return "\"ssl\" parameter is incompatible with \"http3\""; } -#endif - -#if (NGX_HTTP_V3) - if (lsopt.ssl && lsopt.quic) { - return "\"ssl\" parameter is incompatible with \"quic\""; - } -#endif - #endif for (n = 0; n < u.naddrs; n++) { diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 148696641..822c7d264 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -74,7 +74,6 @@ typedef struct { unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; - unsigned quic:1; unsigned http2:1; unsigned http3:1; #if (NGX_HAVE_INET6) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 0acedb6e9..e8907c100 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -327,7 +327,7 @@ ngx_http_init_connection(ngx_connection_t *c) #endif #if (NGX_HTTP_V3) - if (hc->addr_conf->quic) { + if (hc->addr_conf->http3) { ngx_http_v3_init(c); return; } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index bee073e1d..f470b327d 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -22,8 +22,8 @@ #define NGX_HTTP_V3_ALPN_PROTO "\x02h3" #define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" -#define NGX_HTTP_QUIC_ALPN_PROTO "\x0Ahq-interop" -#define NGX_HTTP_QUIC_ALPN_DRAFT_FMT "\x05hq-%02uD" +#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT "\x05hq-%02uD" #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 @@ -102,6 +102,9 @@ typedef struct { ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; ngx_uint_t max_uni_streams; +#if (NGX_HTTP_V3_HQ) + ngx_flag_t hq; +#endif ngx_quic_conf_t quic; } ngx_http_v3_srv_conf_t; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 14e24d29a..0b2e59b9a 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -68,6 +68,15 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_uni_streams), NULL }, +#if (NGX_HTTP_V3_HQ) + { ngx_string("http3_hq"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, hq), + NULL }, +#endif + { ngx_string("http3_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_v3_push, @@ -300,6 +309,9 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_uni_streams = NGX_CONF_UNSET_UINT; +#if (NGX_HTTP_V3_HQ) + h3scf->hq = NGX_CONF_UNSET; +#endif h3scf->quic.tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; h3scf->quic.tp.max_ack_delay = NGX_CONF_UNSET_MSEC; @@ -343,6 +355,10 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->max_uni_streams, prev->max_uni_streams, 3); +#if (NGX_HTTP_V3_HQ) + ngx_conf_merge_value(conf->hq, prev->hq, 0); +#endif + ngx_conf_merge_msec_value(conf->quic.tp.max_idle_timeout, prev->quic.tp.max_idle_timeout, 60000); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a4b570370..7fce688aa 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,7 +10,9 @@ #include +#if (NGX_HTTP_V3_HQ) static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); +#endif 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_request(void *data); @@ -64,11 +66,10 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl = 1; - if (c->quic == NULL) { - h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + if (c->quic == NULL) { ngx_quic_run(c, &h3scf->quic); - return; } @@ -82,10 +83,12 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } - if (!hc->addr_conf->http3) { +#if (NGX_HTTP_V3_HQ) + if (h3scf->hq) { ngx_http_v3_init_hq_stream(c); return; } +#endif if (ngx_http_v3_init_session(c) != NGX_OK) { ngx_http_close_connection(c); @@ -101,6 +104,8 @@ ngx_http_v3_init(ngx_connection_t *c) } +#if (NGX_HTTP_V3_HQ) + static void ngx_http_v3_init_hq_stream(ngx_connection_t *c) { @@ -168,6 +173,8 @@ ngx_http_v3_init_hq_stream(ngx_connection_t *c) } } +#endif + static void ngx_http_v3_init_request_stream(ngx_connection_t *c) @@ -387,16 +394,15 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { - ngx_http_connection_t *hc; ngx_http_v3_srv_conf_t *h3scf; - hc = ngx_http_quic_get_connection(c); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (!hc->addr_conf->http3) { +#if (NGX_HTTP_V3_HQ) + if (h3scf->hq) { return; } - - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); +#endif if (h3scf->max_table_capacity > 0 && !c->read->eof) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); -- cgit v1.2.3 From 835854520a07adf6e3bedfad486a92cecdcd33ac Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 1 Dec 2021 11:02:17 +0300 Subject: HTTP/3: $http3 variable. A new variable $http3 is added. The variable equals to "h3" for HTTP/3 connections, "hq" for hq connections and is an empty string otherwise. The variable $quic is eliminated. The new variable is similar to $http2 variable. --- README | 6 +++++- src/http/v3/ngx_http_v3_module.c | 32 +++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/README b/README index 1ecf46e07..7ade4c34c 100644 --- a/README +++ b/README @@ -141,7 +141,11 @@ Experimental QUIC support for nginx http3_push_preload http3_hq (requires NGX_HTTP_V3_HQ macro) - An additional variable is available: $quic. + In http, an additional variable is available: $http3. + The value of $http3 is "h3" for HTTP/3 connections, + "hq" for hq connections, or an empty string otherwise. + + In stream, an additional variable is available: $quic. The value of $quic is "quic" if QUIC connection is used, or an empty string otherwise. diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 0b2e59b9a..697eaa4e9 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -10,7 +10,7 @@ #include -static ngx_int_t ngx_http_v3_variable_quic(ngx_http_request_t *r, +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); @@ -235,7 +235,7 @@ ngx_module_t ngx_http_v3_module = { static ngx_http_variable_t ngx_http_v3_vars[] = { - { ngx_string("quic"), NULL, ngx_http_v3_variable_quic, 0, 0, 0 }, + { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, ngx_http_null_variable }; @@ -244,20 +244,38 @@ static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); static ngx_int_t -ngx_http_v3_variable_quic(ngx_http_request_t *r, +ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->connection->quic) { +#if (NGX_HTTP_V3_HQ) + + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - v->len = 4; + if (h3scf->hq) { + v->len = sizeof("hq") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "hq"; + + return NGX_OK; + } + +#endif + + v->len = sizeof("h3") - 1; v->valid = 1; - v->no_cacheable = 1; + v->no_cacheable = 0; v->not_found = 0; - v->data = (u_char *) "quic"; + v->data = (u_char *) "h3"; + return NGX_OK; } - v->not_found = 1; + *v = ngx_http_variable_null_value; return NGX_OK; } -- cgit v1.2.3 From 0791b508807eac65681c9c33d27acece67a9a421 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Dec 2021 15:19:54 +0300 Subject: QUIC: simplified configuration. Directives that set transport parameters are removed from the configuration. Corresponding values are derived from the quic configuration or initialized to default. Whenever possible, quic configuration parameters are taken from higher-level protocol settings, i.e. HTTP/3. --- README | 28 ++-- src/event/quic/ngx_event_quic.c | 5 +- src/event/quic/ngx_event_quic.h | 36 ++--- src/event/quic/ngx_event_quic_streams.c | 10 +- src/event/quic/ngx_event_quic_transport.c | 44 ++++++ src/event/quic/ngx_event_quic_transport.h | 27 ++++ src/http/v3/ngx_http_v3.h | 5 +- src/http/v3/ngx_http_v3_module.c | 230 +++++------------------------- src/http/v3/ngx_http_v3_request.c | 3 +- src/http/v3/ngx_http_v3_streams.c | 5 +- src/stream/ngx_stream_quic_module.c | 181 +++-------------------- 11 files changed, 162 insertions(+), 412 deletions(-) diff --git a/README b/README index 7ade4c34c..5fd38cbf2 100644 --- a/README +++ b/README @@ -98,21 +98,6 @@ Experimental QUIC support for nginx Along with "http3" or "quic", you also have to specify "reuseport" option [6] to make it work properly with multiple workers. - A number of directives were added that specify transport parameter values: - - quic_max_idle_timeout - quic_max_ack_delay - quic_max_udp_payload_size - quic_initial_max_data - quic_initial_max_stream_data_bidi_local - quic_initial_max_stream_data_bidi_remote - quic_initial_max_stream_data_uni - quic_initial_max_streams_bidi - quic_initial_max_streams_uni - quic_ack_delay_exponent - quic_disable_active_migration - quic_active_connection_id_limit - To enable address validation: quic_retry on; @@ -129,14 +114,23 @@ Experimental QUIC support for nginx quic_gso on; + To limit maximum packet size: + + quic_mtu ; + + To set host key for various tokens: + + quic_host_key ; + + By default this Linux-specific optimization [8] is disabled. Enable if your network interface is configured to support GSO. A number of directives were added that configure HTTP/3: - http3_max_table_capacity - http3_max_blocked_streams + http3_stream_buffer_size http3_max_concurrent_pushes + http3_max_concurrent_streams http3_push http3_push_preload http3_hq (requires NGX_HTTP_V3_HQ macro) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 03d703b2c..cb71a16b1 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -294,7 +294,10 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->path_validation.cancelable = 1; qc->conf = conf; - qc->tp = conf->tp; + + if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { + return NULL; + } ctp = &qc->ctp; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 1007c491d..8ae7bf643 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -27,43 +27,23 @@ #define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 -#define NGX_QUIC_STREAM_BUFSIZE 65536 - - -typedef struct { - /* configurable */ - ngx_msec_t max_idle_timeout; - ngx_msec_t max_ack_delay; - - size_t max_udp_payload_size; - size_t initial_max_data; - size_t initial_max_stream_data_bidi_local; - size_t initial_max_stream_data_bidi_remote; - size_t initial_max_stream_data_uni; - ngx_uint_t initial_max_streams_bidi; - ngx_uint_t initial_max_streams_uni; - ngx_uint_t ack_delay_exponent; - ngx_uint_t active_connection_id_limit; - ngx_flag_t disable_active_migration; - ngx_str_t original_dcid; - ngx_str_t initial_scid; - ngx_str_t retry_scid; - u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; - - /* TODO */ - void *preferred_address; -} ngx_quic_tp_t; - typedef struct { ngx_ssl_t *ssl; - ngx_quic_tp_t tp; + ngx_flag_t retry; ngx_flag_t gso_enabled; + ngx_flag_t disable_active_migration; + ngx_msec_t timeout; ngx_str_t host_key; + size_t mtu; + size_t stream_buffer_size; + ngx_uint_t max_concurrent_streams_bidi; + ngx_uint_t max_concurrent_streams_uni; ngx_int_t stream_close_code; ngx_int_t stream_reject_code_uni; ngx_int_t stream_reject_code_bidi; + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index ced35370b..60e693bbd 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -851,7 +851,7 @@ ngx_quic_max_stream_flow(ngx_connection_t *c) qs = c->quic; qc = ngx_quic_get_connection(qs->parent); - size = NGX_QUIC_STREAM_BUFSIZE; + size = qc->conf->stream_buffer_size; sent = c->sent; unacked = sent - qs->acked; @@ -859,15 +859,13 @@ ngx_quic_max_stream_flow(ngx_connection_t *c) qc->streams.send_max_data = qc->ctp.initial_max_data; } - if (unacked >= NGX_QUIC_STREAM_BUFSIZE) { + if (unacked >= size) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send flow hit buffer size"); return 0; } - if (unacked + size > NGX_QUIC_STREAM_BUFSIZE) { - size = NGX_QUIC_STREAM_BUFSIZE - unacked; - } + size -= unacked; if (qc->streams.sent >= qc->streams.send_max_data) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1493,7 +1491,7 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) sent = qs->connection->sent; unacked = sent - qs->acked; - if (unacked >= NGX_QUIC_STREAM_BUFSIZE && wev->active) { + if (unacked >= qc->conf->stream_buffer_size && wev->active) { wev->ready = 1; ngx_post_event(wev, &ngx_posted_events); } diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index c39d1094c..0ff42de2d 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1937,6 +1937,50 @@ ngx_quic_create_retire_connection_id(u_char *p, } +ngx_int_t +ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) +{ + ngx_uint_t nstreams; + + ngx_memzero(tp, sizeof(ngx_quic_tp_t)); + + /* + * set by ngx_memzero(): + * + * tp->disable_active_migration = 0; + * tp->original_dcid = { 0, NULL }; + * tp->initial_scid = { 0, NULL }; + * tp->retry_scid = { 0, NULL }; + * tp->sr_token = { 0 } + * tp->sr_enabled = 0 + * tp->preferred_address = NULL + */ + + tp->max_idle_timeout = qcf->timeout; + + tp->max_udp_payload_size = qcf->mtu; + + nstreams = qcf->max_concurrent_streams_bidi + + qcf->max_concurrent_streams_uni; + + tp->initial_max_data = nstreams * qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; + tp->initial_max_stream_data_uni = qcf->stream_buffer_size; + + tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; + tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; + + tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + + tp->active_connection_id_limit = 2; + tp->disable_active_migration = qcf->disable_active_migration; + + return NGX_OK; +} + + ssize_t ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, size_t *clen) diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 2cd372cba..64ebfa979 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -338,6 +338,31 @@ typedef struct { } ngx_quic_header_t; +typedef struct { + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + size_t max_udp_payload_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; + + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + + /* TODO */ + void *preferred_address; +} ngx_quic_tp_t; + + ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); @@ -358,6 +383,8 @@ ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, uint64_t *gap, uint64_t *range); 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); ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index f470b327d..91fa19f10 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -45,6 +45,8 @@ #define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06 #define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 +#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 + #define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 #define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 #define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 @@ -52,6 +54,7 @@ #define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 #define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 #define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 +#define NGX_HTTP_V3_MAX_UNI_STREAMS 3 /* HTTP/3 errors */ #define NGX_HTTP_V3_ERR_NO_ERROR 0x100 @@ -101,7 +104,7 @@ typedef struct { size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; - ngx_uint_t max_uni_streams; + ngx_uint_t max_concurrent_streams; #if (NGX_HTTP_V3_HQ) ngx_flag_t hq; #endif diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 697eaa4e9..455b613e1 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -16,9 +16,7 @@ static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, - void *data); -static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, +static char *ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -28,32 +26,12 @@ static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = - { ngx_http_quic_max_ack_delay }; -static ngx_conf_post_t ngx_http_quic_max_udp_payload_size_post = - { ngx_http_quic_max_udp_payload_size }; -static ngx_conf_num_bounds_t ngx_http_quic_ack_delay_exponent_bounds = - { ngx_conf_check_num_bounds, 0, 20 }; -static ngx_conf_num_bounds_t ngx_http_quic_active_connection_id_limit_bounds = - { ngx_conf_check_num_bounds, 2, -1 }; +static ngx_conf_post_t ngx_http_quic_mtu_post = + { ngx_http_quic_mtu }; static ngx_command_t ngx_http_v3_commands[] = { - { ngx_string("http3_max_table_capacity"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_table_capacity), - NULL }, - - { ngx_string("http3_max_blocked_streams"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_blocked_streams), - NULL }, - { ngx_string("http3_max_concurrent_pushes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -61,11 +39,11 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), NULL }, - { ngx_string("http3_max_uni_streams"), + { ngx_string("http3_max_concurrent_streams"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_uni_streams), + offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), NULL }, #if (NGX_HTTP_V3_HQ) @@ -91,92 +69,13 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_loc_conf_t, push_preload), NULL }, - { ngx_string("quic_max_idle_timeout"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_idle_timeout), - NULL }, - - { ngx_string("quic_max_ack_delay"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_ack_delay), - &ngx_http_quic_max_ack_delay_post }, - - { ngx_string("quic_max_udp_payload_size"), + { ngx_string("http3_stream_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.max_udp_payload_size), - &ngx_http_quic_max_udp_payload_size_post }, - - { ngx_string("quic_initial_max_data"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_data), + offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size), NULL }, - { ngx_string("quic_initial_max_stream_data_bidi_local"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, - quic.tp.initial_max_stream_data_bidi_local), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_remote"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, - quic.tp.initial_max_stream_data_bidi_remote), - NULL }, - - { ngx_string("quic_initial_max_stream_data_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_stream_data_uni), - NULL }, - - { ngx_string("quic_initial_max_streams_bidi"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_streams_bidi), - NULL }, - - { ngx_string("quic_initial_max_streams_uni"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.initial_max_streams_uni), - NULL }, - - { ngx_string("quic_ack_delay_exponent"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.ack_delay_exponent), - &ngx_http_quic_ack_delay_exponent_bounds }, - - { ngx_string("quic_disable_active_migration"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.disable_active_migration), - NULL }, - - { ngx_string("quic_active_connection_id_limit"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.tp.active_connection_id_limit), - &ngx_http_quic_active_connection_id_limit_bounds }, - { ngx_string("quic_retry"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -191,6 +90,13 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), NULL }, + { ngx_string("quic_mtu"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.mtu), + &ngx_http_quic_mtu_post }, + { ngx_string("quic_host_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_quic_host_key, @@ -313,37 +219,23 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): * - * h3scf->quic.tp.original_dcid = { 0, NULL }; - * h3scf->quic.tp.initial_scid = { 0, NULL }; - * h3scf->quic.tp.retry_scid = { 0, NULL }; - * h3scf->quic.tp.sr_token = { 0 } - * h3scf->quic.tp.sr_enabled = 0 - * h3scf->quic.tp.preferred_address = NULL * h3scf->quic.host_key = { 0, NULL } * h3scf->quic.stream_reject_code_uni = 0; + * h3scf->quic.disable_active_migration = 0; + * h3scf->quic.timeout = 0; + * h3scf->max_blocked_streams = 0; */ - - h3scf->max_table_capacity = NGX_CONF_UNSET_SIZE; - h3scf->max_blocked_streams = NGX_CONF_UNSET_UINT; + h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; - h3scf->max_uni_streams = NGX_CONF_UNSET_UINT; + h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; #if (NGX_HTTP_V3_HQ) h3scf->hq = NGX_CONF_UNSET; #endif - h3scf->quic.tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; - h3scf->quic.tp.max_ack_delay = NGX_CONF_UNSET_MSEC; - h3scf->quic.tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; - h3scf->quic.tp.initial_max_data = NGX_CONF_UNSET_SIZE; - h3scf->quic.tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; - h3scf->quic.tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; - h3scf->quic.tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; - h3scf->quic.tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; - h3scf->quic.tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; - h3scf->quic.tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; - h3scf->quic.tp.disable_active_migration = NGX_CONF_UNSET; - h3scf->quic.tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; - + h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; + h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; + h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; + h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; h3scf->quic.retry = NGX_CONF_UNSET; h3scf->quic.gso_enabled = NGX_CONF_UNSET; h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; @@ -361,64 +253,27 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_ssl_srv_conf_t *sscf; - ngx_conf_merge_size_value(conf->max_table_capacity, - prev->max_table_capacity, 16384); - - ngx_conf_merge_uint_value(conf->max_blocked_streams, - prev->max_blocked_streams, 16); - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, prev->max_concurrent_pushes, 10); - ngx_conf_merge_uint_value(conf->max_uni_streams, - prev->max_uni_streams, 3); + ngx_conf_merge_uint_value(conf->max_concurrent_streams, + prev->max_concurrent_streams, 128); + + conf->max_blocked_streams = conf->max_concurrent_streams; #if (NGX_HTTP_V3_HQ) ngx_conf_merge_value(conf->hq, prev->hq, 0); #endif - ngx_conf_merge_msec_value(conf->quic.tp.max_idle_timeout, - prev->quic.tp.max_idle_timeout, 60000); - - ngx_conf_merge_msec_value(conf->quic.tp.max_ack_delay, - prev->quic.tp.max_ack_delay, - NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - ngx_conf_merge_size_value(conf->quic.tp.max_udp_payload_size, - prev->quic.tp.max_udp_payload_size, + ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->quic.tp.initial_max_data, - prev->quic.tp.initial_max_data, - 16 * NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_bidi_local, - prev->quic.tp.initial_max_stream_data_bidi_local, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_bidi_remote, - prev->quic.tp.initial_max_stream_data_bidi_remote, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->quic.tp.initial_max_stream_data_uni, - prev->quic.tp.initial_max_stream_data_uni, - NGX_QUIC_STREAM_BUFSIZE); + ngx_conf_merge_size_value(conf->quic.stream_buffer_size, + prev->quic.stream_buffer_size, + 65536); - ngx_conf_merge_uint_value(conf->quic.tp.initial_max_streams_bidi, - prev->quic.tp.initial_max_streams_bidi, 16); - - ngx_conf_merge_uint_value(conf->quic.tp.initial_max_streams_uni, - prev->quic.tp.initial_max_streams_uni, 3); - - ngx_conf_merge_uint_value(conf->quic.tp.ack_delay_exponent, - prev->quic.tp.ack_delay_exponent, - NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - - ngx_conf_merge_value(conf->quic.tp.disable_active_migration, - prev->quic.tp.disable_active_migration, 0); - - ngx_conf_merge_uint_value(conf->quic.tp.active_connection_id_limit, - prev->quic.tp.active_connection_id_limit, 2); + conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); @@ -465,23 +320,7 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) static char * -ngx_http_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) -{ - ngx_msec_t *sp = data; - - if (*sp >= 16384) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_ack_delay\" must be less than 16384"); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * -ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) +ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; @@ -489,8 +328,7 @@ ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_udp_payload_size\" must be between " - "%d and %d", + "\"quic_mtu\" must be between %d and %d", NGX_QUIC_MIN_INITIAL_SIZE, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7fce688aa..e103a7eca 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -66,9 +66,11 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl = 1; + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); if (c->quic == NULL) { + h3scf->quic.timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; } @@ -79,7 +81,6 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl_servername = phc->ssl_servername; hc->conf_ctx = phc->conf_ctx; - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ngx_set_connection_log(c, clcf->error_log); } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c index 23b16cbc2..f0a599655 100644 --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -36,16 +36,13 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { uint64_t n; - ngx_http_v3_srv_conf_t *h3scf; ngx_http_v3_uni_stream_t *us; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - n = c->quic->id >> 2; - if (n >= h3scf->max_uni_streams) { + if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, "reached maximum number of uni streams"); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 7ad96a11c..34f1e18ef 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -16,111 +16,36 @@ static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, - void *data); -static char *ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, - void *data); +static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data); static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); - -static ngx_conf_post_t ngx_stream_quic_max_ack_delay_post = - { ngx_stream_quic_max_ack_delay }; -static ngx_conf_post_t ngx_stream_quic_max_udp_payload_size_post = - { ngx_stream_quic_max_udp_payload_size }; -static ngx_conf_num_bounds_t ngx_stream_quic_ack_delay_exponent_bounds = - { ngx_conf_check_num_bounds, 0, 20 }; -static ngx_conf_num_bounds_t - ngx_stream_quic_active_connection_id_limit_bounds = - { ngx_conf_check_num_bounds, 2, -1 }; - +static ngx_conf_post_t ngx_stream_quic_mtu_post = + { ngx_stream_quic_mtu }; static ngx_command_t ngx_stream_quic_commands[] = { - { ngx_string("quic_max_idle_timeout"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_idle_timeout), - NULL }, - - { ngx_string("quic_max_ack_delay"), + { ngx_string("quic_timeout"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_ack_delay), - &ngx_stream_quic_max_ack_delay_post }, - - { ngx_string("quic_max_udp_payload_size"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.max_udp_payload_size), - &ngx_stream_quic_max_udp_payload_size_post }, - - { ngx_string("quic_initial_max_data"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_data), + offsetof(ngx_quic_conf_t, timeout), NULL }, - { ngx_string("quic_initial_max_stream_data_bidi_local"), + { ngx_string("quic_mtu"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_local), - NULL }, - - { ngx_string("quic_initial_max_stream_data_bidi_remote"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_bidi_remote), - NULL }, + offsetof(ngx_quic_conf_t, mtu), + &ngx_stream_quic_mtu_post }, - { ngx_string("quic_initial_max_stream_data_uni"), + { ngx_string("quic_stream_buffer_size"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_stream_data_uni), - NULL }, - - { ngx_string("quic_initial_max_streams_bidi"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_streams_bidi), - NULL }, - - { ngx_string("quic_initial_max_streams_uni"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.initial_max_streams_uni), - NULL }, - - { ngx_string("quic_ack_delay_exponent"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.ack_delay_exponent), - &ngx_stream_quic_ack_delay_exponent_bounds }, - - { ngx_string("quic_disable_active_migration"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_flag_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.disable_active_migration), + offsetof(ngx_quic_conf_t, stream_buffer_size), NULL }, - { ngx_string("quic_active_connection_id_limit"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, tp.active_connection_id_limit), - &ngx_stream_quic_active_connection_id_limit_bounds }, - { ngx_string("quic_retry"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -236,28 +161,17 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) /* * set by ngx_pcalloc(): * - * conf->tp.original_dcid = { 0, NULL }; - * conf->tp.initial_scid = { 0, NULL }; - * conf->tp.retry_scid = { 0, NULL }; - * conf->tp.preferred_address = NULL * conf->host_key = { 0, NULL } * conf->stream_close_code = 0; * conf->stream_reject_code_uni = 0; * conf->stream_reject_code_bidi= 0; */ - conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; - conf->tp.max_ack_delay = NGX_CONF_UNSET_MSEC; - conf->tp.max_udp_payload_size = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_data = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_bidi_local = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_bidi_remote = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_stream_data_uni = NGX_CONF_UNSET_SIZE; - conf->tp.initial_max_streams_bidi = NGX_CONF_UNSET_UINT; - conf->tp.initial_max_streams_uni = NGX_CONF_UNSET_UINT; - conf->tp.ack_delay_exponent = NGX_CONF_UNSET_UINT; - conf->tp.disable_active_migration = NGX_CONF_UNSET; - conf->tp.active_connection_id_limit = NGX_CONF_UNSET_UINT; + conf->timeout = NGX_CONF_UNSET_MSEC; + conf->mtu = NGX_CONF_UNSET_SIZE; + conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; + conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; + conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; conf->retry = NGX_CONF_UNSET; conf->gso_enabled = NGX_CONF_UNSET; @@ -274,48 +188,16 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_ssl_conf_t *scf; - ngx_conf_merge_msec_value(conf->tp.max_idle_timeout, - prev->tp.max_idle_timeout, 60000); + ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); - ngx_conf_merge_msec_value(conf->tp.max_ack_delay, - prev->tp.max_ack_delay, - NGX_QUIC_DEFAULT_MAX_ACK_DELAY); - - ngx_conf_merge_size_value(conf->tp.max_udp_payload_size, - prev->tp.max_udp_payload_size, + ngx_conf_merge_size_value(conf->mtu, prev->mtu, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->tp.initial_max_data, - prev->tp.initial_max_data, - 16 * NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_local, - prev->tp.initial_max_stream_data_bidi_local, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_bidi_remote, - prev->tp.initial_max_stream_data_bidi_remote, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_size_value(conf->tp.initial_max_stream_data_uni, - prev->tp.initial_max_stream_data_uni, - NGX_QUIC_STREAM_BUFSIZE); - - ngx_conf_merge_uint_value(conf->tp.initial_max_streams_bidi, - prev->tp.initial_max_streams_bidi, 16); + ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, + prev->max_concurrent_streams_bidi, 16); - ngx_conf_merge_uint_value(conf->tp.initial_max_streams_uni, - prev->tp.initial_max_streams_uni, 16); - - ngx_conf_merge_uint_value(conf->tp.ack_delay_exponent, - prev->tp.ack_delay_exponent, - NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT); - - ngx_conf_merge_value(conf->tp.disable_active_migration, - prev->tp.disable_active_migration, 0); - - ngx_conf_merge_uint_value(conf->tp.active_connection_id_limit, - prev->tp.active_connection_id_limit, 2); + ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni, + prev->max_concurrent_streams_uni, 3); ngx_conf_merge_value(conf->retry, prev->retry, 0); ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); @@ -361,23 +243,7 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) static char * -ngx_stream_quic_max_ack_delay(ngx_conf_t *cf, void *post, void *data) -{ - ngx_msec_t *sp = data; - - if (*sp >= 16384) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_ack_delay\" must be less than 16384"); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - -static char * -ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) +ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; @@ -385,8 +251,7 @@ ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data) || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_max_udp_payload_size\" must be between " - "%d and %d", + "\"quic_mtu\" must be between %d and %d", NGX_QUIC_MIN_INITIAL_SIZE, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); -- cgit v1.2.3 From d1bc1da950488d8d3114a06ca00346fa58848a2d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 7 Dec 2021 13:01:28 +0300 Subject: HTTP/3: renamed files. ngx_http_v3_tables.h and ngx_http_v3_tables.c are renamed to ngx_http_v3_table.h and ngx_http_v3_table.c to better match HTTP/2 code. ngx_http_v3_streams.h and ngx_http_v3_streams.c are renamed to ngx_http_v3_uni.h and ngx_http_v3_uni.c to better match their content. --- auto/modules | 8 +- src/http/v3/ngx_http_v3.h | 4 +- src/http/v3/ngx_http_v3_streams.c | 733 -------------------------------------- src/http/v3/ngx_http_v3_streams.h | 38 -- src/http/v3/ngx_http_v3_table.c | 678 +++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_table.h | 53 +++ src/http/v3/ngx_http_v3_tables.c | 678 ----------------------------------- src/http/v3/ngx_http_v3_tables.h | 53 --- src/http/v3/ngx_http_v3_uni.c | 733 ++++++++++++++++++++++++++++++++++++++ src/http/v3/ngx_http_v3_uni.h | 38 ++ 10 files changed, 1508 insertions(+), 1508 deletions(-) delete mode 100644 src/http/v3/ngx_http_v3_streams.c delete mode 100644 src/http/v3/ngx_http_v3_streams.h create mode 100644 src/http/v3/ngx_http_v3_table.c create mode 100644 src/http/v3/ngx_http_v3_table.h delete mode 100644 src/http/v3/ngx_http_v3_tables.c delete mode 100644 src/http/v3/ngx_http_v3_tables.h create mode 100644 src/http/v3/ngx_http_v3_uni.c create mode 100644 src/http/v3/ngx_http_v3_uni.h diff --git a/auto/modules b/auto/modules index f98eeafed..bc1b9bafd 100644 --- a/auto/modules +++ b/auto/modules @@ -448,13 +448,13 @@ if [ $HTTP = YES ]; then ngx_module_deps="src/http/v3/ngx_http_v3.h \ src/http/v3/ngx_http_v3_encode.h \ src/http/v3/ngx_http_v3_parse.h \ - src/http/v3/ngx_http_v3_tables.h \ - src/http/v3/ngx_http_v3_streams.h" + src/http/v3/ngx_http_v3_table.h \ + src/http/v3/ngx_http_v3_uni.h" ngx_module_srcs="src/http/v3/ngx_http_v3.c \ src/http/v3/ngx_http_v3_encode.c \ src/http/v3/ngx_http_v3_parse.c \ - src/http/v3/ngx_http_v3_tables.c \ - src/http/v3/ngx_http_v3_streams.c \ + src/http/v3/ngx_http_v3_table.c \ + src/http/v3/ngx_http_v3_uni.c \ src/http/v3/ngx_http_v3_request.c \ src/http/v3/ngx_http_v3_module.c" ngx_module_libs= diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 91fa19f10..b7951e9bb 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -15,8 +15,8 @@ #include #include -#include -#include +#include +#include #define NGX_HTTP_V3_ALPN_PROTO "\x02h3" diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c deleted file mode 100644 index f0a599655..000000000 --- a/src/http/v3/ngx_http_v3_streams.c +++ /dev/null @@ -1,733 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include - - -typedef struct { - ngx_http_v3_parse_uni_t parse; - ngx_int_t index; -} ngx_http_v3_uni_stream_t; - - -typedef struct { - ngx_queue_t queue; - uint64_t id; - ngx_connection_t *connection; - ngx_uint_t *npushing; -} ngx_http_v3_push_t; - - -static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); -static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); -static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); -static void ngx_http_v3_push_cleanup(void *data); -static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, - ngx_uint_t type); - - -void -ngx_http_v3_init_uni_stream(ngx_connection_t *c) -{ - uint64_t n; - ngx_http_v3_uni_stream_t *us; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); - - n = c->quic->id >> 2; - - if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { - ngx_http_v3_finalize_connection(c, - NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "reached maximum number of uni streams"); - c->data = NULL; - ngx_http_v3_close_uni_stream(c); - return; - } - - c->quic->cancelable = 1; - - us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); - if (us == 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->index = -1; - - c->data = us; - - c->read->handler = ngx_http_v3_uni_read_handler; - c->write->handler = ngx_http_v3_dummy_write_handler; - - ngx_http_v3_uni_read_handler(c->read); -} - - -static void -ngx_http_v3_close_uni_stream(ngx_connection_t *c) -{ - ngx_pool_t *pool; - ngx_http_v3_session_t *h3c; - ngx_http_v3_uni_stream_t *us; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); - - us = c->data; - - if (us && us->index >= 0) { - h3c = ngx_http_v3_get_session(c); - h3c->known_streams[us->index] = NULL; - } - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); -} - - -ngx_int_t -ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) -{ - ngx_int_t index; - ngx_http_v3_session_t *h3c; - ngx_http_v3_uni_stream_t *us; - - h3c = ngx_http_v3_get_session(c); - - switch (type) { - - case NGX_HTTP_V3_STREAM_ENCODER: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 encoder stream"); - index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; - break; - - case NGX_HTTP_V3_STREAM_DECODER: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 decoder stream"); - index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; - break; - - case NGX_HTTP_V3_STREAM_CONTROL: - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 control stream"); - index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; - - break; - - default: - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 stream 0x%02xL", type); - - if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL - || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL - || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) - { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); - return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; - } - - index = -1; - } - - if (index >= 0) { - if (h3c->known_streams[index]) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); - return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; - } - - h3c->known_streams[index] = c; - - us = c->data; - us->index = index; - } - - return NGX_OK; -} - - -static void -ngx_http_v3_uni_read_handler(ngx_event_t *rev) -{ - u_char buf[128]; - ssize_t n; - ngx_buf_t b; - ngx_int_t rc; - ngx_connection_t *c; - ngx_http_v3_session_t *h3c; - ngx_http_v3_uni_stream_t *us; - - c = rev->data; - us = c->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); - - ngx_memzero(&b, sizeof(ngx_buf_t)); - - while (rev->ready) { - - n = c->recv(c, buf, sizeof(buf)); - - if (n == NGX_ERROR) { - rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; - goto failed; - } - - if (n == 0) { - if (us->index >= 0) { - rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; - goto failed; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); - ngx_http_v3_close_uni_stream(c); - return; - } - - if (n == NGX_AGAIN) { - break; - } - - b.pos = buf; - b.last = buf + n; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (ngx_http_v3_check_flood(c) != NGX_OK) { - ngx_http_v3_close_uni_stream(c); - return; - } - - rc = ngx_http_v3_parse_uni(c, &us->parse, &b); - - if (rc == NGX_DONE) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 read done"); - ngx_http_v3_close_uni_stream(c); - return; - } - - if (rc > 0) { - goto failed; - } - - if (rc != NGX_AGAIN) { - rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; - goto failed; - } - } - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; - goto failed; - } - - return; - -failed: - - ngx_http_v3_finalize_connection(c, rc, "stream error"); - ngx_http_v3_close_uni_stream(c); -} - - -static void -ngx_http_v3_dummy_write_handler(ngx_event_t *wev) -{ - ngx_connection_t *c; - - c = wev->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); - - if (ngx_handle_write_event(wev, 0) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - NULL); - ngx_http_v3_close_uni_stream(c); - } -} - - -/* XXX async & buffered stream writes */ - -ngx_connection_t * -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) -{ - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; - size_t n; - ngx_connection_t *sc; - ngx_pool_cleanup_t *cln; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 create push stream id:%uL", push_id); - - sc = ngx_quic_open_stream(c, 0); - if (sc == NULL) { - goto failed; - } - - p = buf; - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); - n = p - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (sc->send(sc, buf, n) != (ssize_t) n) { - goto failed; - } - - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); - if (cln == NULL) { - goto failed; - } - - h3c->npushing++; - - cln->handler = ngx_http_v3_push_cleanup; - - push = cln->data; - push->id = push_id; - push->connection = sc; - push->npushing = &h3c->npushing; - - ngx_queue_insert_tail(&h3c->pushing, &push->queue); - - return sc; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "failed to create push stream"); - if (sc) { - ngx_http_v3_close_uni_stream(sc); - } - - return NULL; -} - - -static void -ngx_http_v3_push_cleanup(void *data) -{ - ngx_http_v3_push_t *push = data; - - ngx_queue_remove(&push->queue); - (*push->npushing)--; -} - - -static ngx_connection_t * -ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) -{ - u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; - size_t n; - ngx_int_t index; - ngx_connection_t *sc; - ngx_http_v3_session_t *h3c; - ngx_http_v3_uni_stream_t *us; - - switch (type) { - case NGX_HTTP_V3_STREAM_ENCODER: - index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; - break; - case NGX_HTTP_V3_STREAM_DECODER: - index = NGX_HTTP_V3_STREAM_SERVER_DECODER; - break; - case NGX_HTTP_V3_STREAM_CONTROL: - index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; - break; - default: - index = -1; - } - - h3c = ngx_http_v3_get_session(c); - - if (index >= 0) { - if (h3c->known_streams[index]) { - return h3c->known_streams[index]; - } - } - - sc = ngx_quic_open_stream(c, 0); - if (sc == NULL) { - goto failed; - } - - sc->quic->cancelable = 1; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 create uni stream, type:%ui", type); - - us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); - if (us == NULL) { - goto failed; - } - - us->index = index; - - sc->data = us; - - sc->read->handler = ngx_http_v3_uni_read_handler; - sc->write->handler = ngx_http_v3_dummy_write_handler; - - if (index >= 0) { - h3c->known_streams[index] = sc; - } - - n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (sc->send(sc, buf, n) != (ssize_t) n) { - goto failed; - } - - return sc; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "failed to create server stream"); - if (sc) { - ngx_http_v3_close_uni_stream(sc); - } - - return NULL; -} - - -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; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); - - cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); - if (cc == NULL) { - return NGX_ERROR; - } - - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - - 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, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); - n += ngx_http_v3_encode_varlen_int(NULL, h3scf->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, - NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->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) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, - "failed to send settings"); - ngx_http_v3_close_uni_stream(cc); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) -{ - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; - size_t n; - ngx_connection_t *cc; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); - - cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); - if (cc == NULL) { - return NGX_ERROR; - } - - n = ngx_http_v3_encode_varlen_int(NULL, id); - p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); - n = p - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (cc->send(cc, buf, n) != (ssize_t) n) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, - "failed to send goaway"); - ngx_http_v3_close_uni_stream(cc); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 send section acknowledgement %ui", stream_id); - - dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); - if (dc == NULL) { - return NGX_ERROR; - } - - buf[0] = 0x80; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (dc->send(dc, buf, n) != (ssize_t) n) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "failed to send section acknowledgement"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, - "failed to send section acknowledgement"); - ngx_http_v3_close_uni_stream(dc); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 send stream cancellation %ui", stream_id); - - dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); - if (dc == NULL) { - return NGX_ERROR; - } - - buf[0] = 0x40; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (dc->send(dc, buf, n) != (ssize_t) n) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, - "failed to send stream cancellation"); - ngx_http_v3_close_uni_stream(dc); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) -{ - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 send insert count increment %ui", inc); - - dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); - if (dc == NULL) { - return NGX_ERROR; - } - - buf[0] = 0; - n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (dc->send(dc, buf, n) != (ssize_t) n) { - goto failed; - } - - return NGX_OK; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "failed to send insert count increment"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, - "failed to send insert count increment"); - ngx_http_v3_close_uni_stream(dc); - - return NGX_ERROR; -} - - -ngx_int_t -ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) -{ - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 MAX_PUSH_ID:%uL", max_push_id); - - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - h3c->max_push_id = max_push_id; - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) -{ - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); - - h3c->goaway_push_id = push_id; - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) -{ - ngx_queue_t *q; - ngx_http_request_t *r; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 CANCEL_PUSH:%uL", push_id); - - if (push_id >= h3c->next_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - for (q = ngx_queue_head(&h3c->pushing); - q != ngx_queue_sentinel(&h3c->pushing); - q = ngx_queue_next(&h3c->pushing)) - { - push = (ngx_http_v3_push_t *) q; - - if (push->id != push_id) { - continue; - } - - r = push->connection->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 cancel push"); - - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); - - break; - } - - return NGX_OK; -} - - -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, - "http3 cancel stream %ui", stream_id); - - /* we do not use dynamic tables */ - - return NGX_OK; -} diff --git a/src/http/v3/ngx_http_v3_streams.h b/src/http/v3/ngx_http_v3_streams.h deleted file mode 100644 index 42ff849be..000000000 --- a/src/http/v3/ngx_http_v3_streams.h +++ /dev/null @@ -1,38 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_HTTP_V3_STREAMS_H_INCLUDED_ -#define _NGX_HTTP_V3_STREAMS_H_INCLUDED_ - - -#include -#include -#include - - -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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, - uint64_t push_id); -ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, - uint64_t max_push_id); -ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); -ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); -ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); - -ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); -ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); -ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, - ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, - ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, - ngx_uint_t inc); - - -#endif /* _NGX_HTTP_V3_STREAMS_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c new file mode 100644 index 000000000..c6d543ac4 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.c @@ -0,0 +1,678 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) + + +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); +static void ngx_http_v3_unblock(void *data); +static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); + + +typedef struct { + ngx_queue_t queue; + ngx_connection_t *connection; + ngx_uint_t *nblocked; +} ngx_http_v3_block_t; + + +static ngx_http_v3_field_t ngx_http_v3_static_table[] = { + + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string("age"), ngx_string("0") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("0") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string(":method"), ngx_string("CONNECT") }, + { ngx_string(":method"), ngx_string("DELETE") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("HEAD") }, + { ngx_string(":method"), ngx_string("OPTIONS") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":method"), ngx_string("PUT") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("103") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("503") }, + { ngx_string("accept"), ngx_string("*/*") }, + { ngx_string("accept"), + ngx_string("application/dns-message") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, + { ngx_string("accept-ranges"), ngx_string("bytes") }, + { ngx_string("access-control-allow-headers"), + ngx_string("cache-control") }, + { ngx_string("access-control-allow-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-allow-origin"), + ngx_string("*") }, + { ngx_string("cache-control"), ngx_string("max-age=0") }, + { ngx_string("cache-control"), ngx_string("max-age=2592000") }, + { ngx_string("cache-control"), ngx_string("max-age=604800") }, + { ngx_string("cache-control"), ngx_string("no-cache") }, + { ngx_string("cache-control"), ngx_string("no-store") }, + { ngx_string("cache-control"), + ngx_string("public, max-age=31536000") }, + { ngx_string("content-encoding"), ngx_string("br") }, + { ngx_string("content-encoding"), ngx_string("gzip") }, + { ngx_string("content-type"), + ngx_string("application/dns-message") }, + { ngx_string("content-type"), + ngx_string("application/javascript") }, + { ngx_string("content-type"), ngx_string("application/json") }, + { ngx_string("content-type"), + ngx_string("application/x-www-form-urlencoded") }, + { ngx_string("content-type"), ngx_string("image/gif") }, + { ngx_string("content-type"), ngx_string("image/jpeg") }, + { ngx_string("content-type"), ngx_string("image/png") }, + { ngx_string("content-type"), ngx_string("text/css") }, + { ngx_string("content-type"), + ngx_string("text/html;charset=utf-8") }, + { ngx_string("content-type"), ngx_string("text/plain") }, + { ngx_string("content-type"), + ngx_string("text/plain;charset=utf-8") }, + { ngx_string("range"), ngx_string("bytes=0-") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains;preload") }, + { ngx_string("vary"), ngx_string("accept-encoding") }, + { ngx_string("vary"), ngx_string("origin") }, + { ngx_string("x-content-type-options"), + ngx_string("nosniff") }, + { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, + { ngx_string(":status"), ngx_string("100") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("302") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("403") }, + { ngx_string(":status"), ngx_string("421") }, + { ngx_string(":status"), ngx_string("425") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("FALSE") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("TRUE") }, + { ngx_string("access-control-allow-headers"), + ngx_string("*") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get, post, options") }, + { ngx_string("access-control-allow-methods"), + ngx_string("options") }, + { ngx_string("access-control-expose-headers"), + ngx_string("content-length") }, + { ngx_string("access-control-request-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-request-method"), + ngx_string("get") }, + { ngx_string("access-control-request-method"), + ngx_string("post") }, + { ngx_string("alt-svc"), ngx_string("clear") }, + { ngx_string("authorization"), ngx_string("") }, + { ngx_string("content-security-policy"), + ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, + { ngx_string("early-data"), ngx_string("1") }, + { ngx_string("expect-ct"), ngx_string("") }, + { ngx_string("forwarded"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("origin"), ngx_string("") }, + { ngx_string("purpose"), ngx_string("prefetch") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("timing-allow-origin"), ngx_string("*") }, + { ngx_string("upgrade-insecure-requests"), + ngx_string("1") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("x-forwarded-for"), ngx_string("") }, + { ngx_string("x-frame-options"), ngx_string("deny") }, + { ngx_string("x-frame-options"), ngx_string("sameorigin") } +}; + + +ngx_int_t +ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + ngx_str_t name; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + if (dynamic) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert dynamic[%ui] \"%V\"", index, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert static[%ui] \"%V\"", index, value); + + if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + } + + return ngx_http_v3_insert(c, &name, value); +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + size_t size; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + size = ngx_http_v3_table_entry_size(name, value); + + if (ngx_http_v3_evict(c, size) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert [%ui] \"%V\":\"%V\", size:%uz", + dt->base + dt->nelts, name, value, size); + + p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, + c->log); + if (p == NULL) { + return NGX_ERROR; + } + + field = (ngx_http_v3_field_t *) p; + + field->name.data = p + sizeof(ngx_http_v3_field_t); + field->name.len = name->len; + field->value.data = ngx_cpymem(field->name.data, name->data, name->len); + field->value.len = value->len; + ngx_memcpy(field->value.data, value->data, value->len); + + dt->elts[dt->nelts++] = field; + dt->size += size; + + /* TODO increment can be sent less often */ + + if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_new_entry(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +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) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_table_capacity limit"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + dt = &h3c->table; + + if (dt->size > capacity) { + if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + } + + max = capacity / 32; + prev_max = dt->capacity / 32; + + if (max > prev_max) { + elts = ngx_alloc(max * sizeof(void *), c->log); + if (elts == NULL) { + return NGX_ERROR; + } + + if (dt->elts) { + ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); + ngx_free(dt->elts); + } + + dt->elts = elts; + } + + dt->capacity = capacity; + + return NGX_OK; +} + + +void +ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) +{ + ngx_uint_t n; + ngx_http_v3_dynamic_table_t *dt; + + dt = &h3c->table; + + if (dt->elts == NULL) { + return; + } + + for (n = 0; n < dt->nelts; n++) { + ngx_free(dt->elts[n]); + } + + ngx_free(dt->elts); +} + + +static ngx_int_t +ngx_http_v3_evict(ngx_connection_t *c, size_t need) +{ + size_t size, target; + ngx_uint_t n; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (need > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); + return NGX_ERROR; + } + + target = dt->capacity - need; + n = 0; + + while (dt->size > target) { + field = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&field->name, &field->value); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 evict [%ui] \"%V\":\"%V\" size:%uz", + dt->base, &field->name, &field->value, size); + + ngx_free(field); + dt->size -= size; + } + + if (n) { + dt->nelts -= n; + dt->base += n; + ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_str_t name, value; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + return ngx_http_v3_insert(c, &name, &value); +} + + +ngx_int_t +ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ack section %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 increment insert count %ui", inc); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value) +{ + ngx_uint_t nelts; + ngx_http_v3_field_t *field; + + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); + + if (index >= nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup out of bounds: %ui", + index, nelts); + return NGX_ERROR; + } + + field = &ngx_http_v3_static_table[index]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (index < dt->base || index - dt->base >= dt->nelts) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", + index, dt->base, dt->base + dt->nelts); + return NGX_ERROR; + } + + field = dt->elts[index - dt->base]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +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; + + /* QPACK 4.5.1.1. Required Insert Count */ + + if (*insert_count == 0) { + return NGX_OK; + } + + 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; + full_range = 2 * max_entries; + + if (*insert_count > full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + max_value = dt->base + dt->nelts + max_entries; + max_wrapped = (max_value / full_range) * full_range; + req_insert_count = max_wrapped + *insert_count - 1; + + if (req_insert_count > max_value) { + if (req_insert_count <= full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + req_insert_count -= full_range; + } + + if (req_insert_count == 0) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decode insert_count %ui -> %ui", + *insert_count, req_insert_count); + + *insert_count = req_insert_count; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) +{ + size_t n; + 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); + dt = &h3c->table; + + n = dt->base + dt->nelts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 check insert count req:%ui, have:%ui", + insert_count, n); + + if (n >= insert_count) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); + + block = NULL; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_v3_unblock) { + block = cln->data; + break; + } + } + + if (block == NULL) { + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_unblock; + + block = cln->data; + block->queue.prev = NULL; + block->connection = c; + block->nblocked = &h3c->nblocked; + } + + 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) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_blocked_streams limit"); + + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, + "too many blocked streams"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + h3c->nblocked++; + ngx_queue_insert_tail(&h3c->blocked, &block->queue); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 blocked:%ui", h3c->nblocked); + + return NGX_BUSY; +} + + +static void +ngx_http_v3_unblock(void *data) +{ + ngx_http_v3_block_t *block = data; + + if (block->queue.prev) { + ngx_queue_remove(&block->queue); + block->queue.prev = NULL; + (*block->nblocked)--; + } +} + + +static ngx_int_t +ngx_http_v3_new_entry(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(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); + block = (ngx_http_v3_block_t *) q; + bc = block->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); + + ngx_http_v3_unblock(block); + ngx_post_event(bc->read, &ngx_posted_events); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) +{ + switch (id) { + + case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_BLOCKED_STREAMS:%uL", value); + break; + + default: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param #%uL:%uL", id, value); + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h new file mode 100644 index 000000000..6f88b35b9 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.h @@ -0,0 +1,53 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_ +#define _NGX_HTTP_V3_TABLE_H_INCLUDED_ + + +#include +#include +#include + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_field_t; + + +typedef struct { + ngx_http_v3_field_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; +} ngx_http_v3_dynamic_table_t; + + +void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); + + +#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_tables.c b/src/http/v3/ngx_http_v3_tables.c deleted file mode 100644 index c6d543ac4..000000000 --- a/src/http/v3/ngx_http_v3_tables.c +++ /dev/null @@ -1,678 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#include -#include -#include - - -#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) - - -static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); -static void ngx_http_v3_unblock(void *data); -static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); - - -typedef struct { - ngx_queue_t queue; - ngx_connection_t *connection; - ngx_uint_t *nblocked; -} ngx_http_v3_block_t; - - -static ngx_http_v3_field_t ngx_http_v3_static_table[] = { - - { ngx_string(":authority"), ngx_string("") }, - { ngx_string(":path"), ngx_string("/") }, - { ngx_string("age"), ngx_string("0") }, - { ngx_string("content-disposition"), ngx_string("") }, - { ngx_string("content-length"), ngx_string("0") }, - { ngx_string("cookie"), ngx_string("") }, - { ngx_string("date"), ngx_string("") }, - { ngx_string("etag"), ngx_string("") }, - { ngx_string("if-modified-since"), ngx_string("") }, - { ngx_string("if-none-match"), ngx_string("") }, - { ngx_string("last-modified"), ngx_string("") }, - { ngx_string("link"), ngx_string("") }, - { ngx_string("location"), ngx_string("") }, - { ngx_string("referer"), ngx_string("") }, - { ngx_string("set-cookie"), ngx_string("") }, - { ngx_string(":method"), ngx_string("CONNECT") }, - { ngx_string(":method"), ngx_string("DELETE") }, - { ngx_string(":method"), ngx_string("GET") }, - { ngx_string(":method"), ngx_string("HEAD") }, - { ngx_string(":method"), ngx_string("OPTIONS") }, - { ngx_string(":method"), ngx_string("POST") }, - { ngx_string(":method"), ngx_string("PUT") }, - { ngx_string(":scheme"), ngx_string("http") }, - { ngx_string(":scheme"), ngx_string("https") }, - { ngx_string(":status"), ngx_string("103") }, - { ngx_string(":status"), ngx_string("200") }, - { ngx_string(":status"), ngx_string("304") }, - { ngx_string(":status"), ngx_string("404") }, - { ngx_string(":status"), ngx_string("503") }, - { ngx_string("accept"), ngx_string("*/*") }, - { ngx_string("accept"), - ngx_string("application/dns-message") }, - { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, - { ngx_string("accept-ranges"), ngx_string("bytes") }, - { ngx_string("access-control-allow-headers"), - ngx_string("cache-control") }, - { ngx_string("access-control-allow-headers"), - ngx_string("content-type") }, - { ngx_string("access-control-allow-origin"), - ngx_string("*") }, - { ngx_string("cache-control"), ngx_string("max-age=0") }, - { ngx_string("cache-control"), ngx_string("max-age=2592000") }, - { ngx_string("cache-control"), ngx_string("max-age=604800") }, - { ngx_string("cache-control"), ngx_string("no-cache") }, - { ngx_string("cache-control"), ngx_string("no-store") }, - { ngx_string("cache-control"), - ngx_string("public, max-age=31536000") }, - { ngx_string("content-encoding"), ngx_string("br") }, - { ngx_string("content-encoding"), ngx_string("gzip") }, - { ngx_string("content-type"), - ngx_string("application/dns-message") }, - { ngx_string("content-type"), - ngx_string("application/javascript") }, - { ngx_string("content-type"), ngx_string("application/json") }, - { ngx_string("content-type"), - ngx_string("application/x-www-form-urlencoded") }, - { ngx_string("content-type"), ngx_string("image/gif") }, - { ngx_string("content-type"), ngx_string("image/jpeg") }, - { ngx_string("content-type"), ngx_string("image/png") }, - { ngx_string("content-type"), ngx_string("text/css") }, - { ngx_string("content-type"), - ngx_string("text/html;charset=utf-8") }, - { ngx_string("content-type"), ngx_string("text/plain") }, - { ngx_string("content-type"), - ngx_string("text/plain;charset=utf-8") }, - { ngx_string("range"), ngx_string("bytes=0-") }, - { ngx_string("strict-transport-security"), - ngx_string("max-age=31536000") }, - { ngx_string("strict-transport-security"), - ngx_string("max-age=31536000;includesubdomains") }, - { ngx_string("strict-transport-security"), - ngx_string("max-age=31536000;includesubdomains;preload") }, - { ngx_string("vary"), ngx_string("accept-encoding") }, - { ngx_string("vary"), ngx_string("origin") }, - { ngx_string("x-content-type-options"), - ngx_string("nosniff") }, - { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, - { ngx_string(":status"), ngx_string("100") }, - { ngx_string(":status"), ngx_string("204") }, - { ngx_string(":status"), ngx_string("206") }, - { ngx_string(":status"), ngx_string("302") }, - { ngx_string(":status"), ngx_string("400") }, - { ngx_string(":status"), ngx_string("403") }, - { ngx_string(":status"), ngx_string("421") }, - { ngx_string(":status"), ngx_string("425") }, - { ngx_string(":status"), ngx_string("500") }, - { ngx_string("accept-language"), ngx_string("") }, - { ngx_string("access-control-allow-credentials"), - ngx_string("FALSE") }, - { ngx_string("access-control-allow-credentials"), - ngx_string("TRUE") }, - { ngx_string("access-control-allow-headers"), - ngx_string("*") }, - { ngx_string("access-control-allow-methods"), - ngx_string("get") }, - { ngx_string("access-control-allow-methods"), - ngx_string("get, post, options") }, - { ngx_string("access-control-allow-methods"), - ngx_string("options") }, - { ngx_string("access-control-expose-headers"), - ngx_string("content-length") }, - { ngx_string("access-control-request-headers"), - ngx_string("content-type") }, - { ngx_string("access-control-request-method"), - ngx_string("get") }, - { ngx_string("access-control-request-method"), - ngx_string("post") }, - { ngx_string("alt-svc"), ngx_string("clear") }, - { ngx_string("authorization"), ngx_string("") }, - { ngx_string("content-security-policy"), - ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, - { ngx_string("early-data"), ngx_string("1") }, - { ngx_string("expect-ct"), ngx_string("") }, - { ngx_string("forwarded"), ngx_string("") }, - { ngx_string("if-range"), ngx_string("") }, - { ngx_string("origin"), ngx_string("") }, - { ngx_string("purpose"), ngx_string("prefetch") }, - { ngx_string("server"), ngx_string("") }, - { ngx_string("timing-allow-origin"), ngx_string("*") }, - { ngx_string("upgrade-insecure-requests"), - ngx_string("1") }, - { ngx_string("user-agent"), ngx_string("") }, - { ngx_string("x-forwarded-for"), ngx_string("") }, - { ngx_string("x-frame-options"), ngx_string("deny") }, - { ngx_string("x-frame-options"), ngx_string("sameorigin") } -}; - - -ngx_int_t -ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value) -{ - ngx_str_t name; - ngx_http_v3_session_t *h3c; - ngx_http_v3_dynamic_table_t *dt; - - if (dynamic) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 ref insert dynamic[%ui] \"%V\"", index, value); - - h3c = ngx_http_v3_get_session(c); - dt = &h3c->table; - - if (dt->base + dt->nelts <= index) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - index = dt->base + dt->nelts - 1 - index; - - if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - } else { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 ref insert static[%ui] \"%V\"", index, value); - - if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - } - - return ngx_http_v3_insert(c, &name, value); -} - - -ngx_int_t -ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) -{ - u_char *p; - size_t size; - ngx_http_v3_field_t *field; - ngx_http_v3_session_t *h3c; - ngx_http_v3_dynamic_table_t *dt; - - size = ngx_http_v3_table_entry_size(name, value); - - if (ngx_http_v3_evict(c, size) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - h3c = ngx_http_v3_get_session(c); - dt = &h3c->table; - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 insert [%ui] \"%V\":\"%V\", size:%uz", - dt->base + dt->nelts, name, value, size); - - p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, - c->log); - if (p == NULL) { - return NGX_ERROR; - } - - field = (ngx_http_v3_field_t *) p; - - field->name.data = p + sizeof(ngx_http_v3_field_t); - field->name.len = name->len; - field->value.data = ngx_cpymem(field->name.data, name->data, name->len); - field->value.len = value->len; - ngx_memcpy(field->value.data, value->data, value->len); - - dt->elts[dt->nelts++] = field; - dt->size += size; - - /* TODO increment can be sent less often */ - - if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_http_v3_new_entry(c) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -ngx_int_t -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) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client exceeded http3_max_table_capacity limit"); - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - dt = &h3c->table; - - if (dt->size > capacity) { - if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - } - - max = capacity / 32; - prev_max = dt->capacity / 32; - - if (max > prev_max) { - elts = ngx_alloc(max * sizeof(void *), c->log); - if (elts == NULL) { - return NGX_ERROR; - } - - if (dt->elts) { - ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); - ngx_free(dt->elts); - } - - dt->elts = elts; - } - - dt->capacity = capacity; - - return NGX_OK; -} - - -void -ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) -{ - ngx_uint_t n; - ngx_http_v3_dynamic_table_t *dt; - - dt = &h3c->table; - - if (dt->elts == NULL) { - return; - } - - for (n = 0; n < dt->nelts; n++) { - ngx_free(dt->elts[n]); - } - - ngx_free(dt->elts); -} - - -static ngx_int_t -ngx_http_v3_evict(ngx_connection_t *c, size_t need) -{ - size_t size, target; - ngx_uint_t n; - ngx_http_v3_field_t *field; - ngx_http_v3_session_t *h3c; - ngx_http_v3_dynamic_table_t *dt; - - h3c = ngx_http_v3_get_session(c); - dt = &h3c->table; - - if (need > dt->capacity) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "not enough dynamic table capacity"); - return NGX_ERROR; - } - - target = dt->capacity - need; - n = 0; - - while (dt->size > target) { - field = dt->elts[n++]; - size = ngx_http_v3_table_entry_size(&field->name, &field->value); - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 evict [%ui] \"%V\":\"%V\" size:%uz", - dt->base, &field->name, &field->value, size); - - ngx_free(field); - dt->size -= size; - } - - if (n) { - dt->nelts -= n; - dt->base += n; - ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); - } - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) -{ - ngx_str_t name, value; - ngx_http_v3_session_t *h3c; - ngx_http_v3_dynamic_table_t *dt; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); - - h3c = ngx_http_v3_get_session(c); - dt = &h3c->table; - - if (dt->base + dt->nelts <= index) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - index = dt->base + dt->nelts - 1 - index; - - if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - - return ngx_http_v3_insert(c, &name, &value); -} - - -ngx_int_t -ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) -{ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 ack section %ui", stream_id); - - /* we do not use dynamic tables */ - - return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; -} - - -ngx_int_t -ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) -{ - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 increment insert count %ui", inc); - - /* we do not use dynamic tables */ - - return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; -} - - -ngx_int_t -ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, - ngx_str_t *name, ngx_str_t *value) -{ - ngx_uint_t nelts; - ngx_http_v3_field_t *field; - - nelts = sizeof(ngx_http_v3_static_table) - / sizeof(ngx_http_v3_static_table[0]); - - if (index >= nelts) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 static[%ui] lookup out of bounds: %ui", - index, nelts); - return NGX_ERROR; - } - - field = &ngx_http_v3_static_table[index]; - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 static[%ui] lookup \"%V\":\"%V\"", - index, &field->name, &field->value); - - if (name) { - *name = field->name; - } - - if (value) { - *value = field->value; - } - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, - ngx_str_t *value) -{ - ngx_http_v3_field_t *field; - ngx_http_v3_session_t *h3c; - ngx_http_v3_dynamic_table_t *dt; - - h3c = ngx_http_v3_get_session(c); - dt = &h3c->table; - - if (index < dt->base || index - dt->base >= dt->nelts) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", - index, dt->base, dt->base + dt->nelts); - return NGX_ERROR; - } - - field = dt->elts[index - dt->base]; - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 dynamic[%ui] lookup \"%V\":\"%V\"", - index, &field->name, &field->value); - - if (name) { - *name = field->name; - } - - if (value) { - *value = field->value; - } - - return NGX_OK; -} - - -ngx_int_t -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; - - /* QPACK 4.5.1.1. Required Insert Count */ - - if (*insert_count == 0) { - return NGX_OK; - } - - 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; - full_range = 2 * max_entries; - - if (*insert_count > full_range) { - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - max_value = dt->base + dt->nelts + max_entries; - max_wrapped = (max_value / full_range) * full_range; - req_insert_count = max_wrapped + *insert_count - 1; - - if (req_insert_count > max_value) { - if (req_insert_count <= full_range) { - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - req_insert_count -= full_range; - } - - if (req_insert_count == 0) { - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 decode insert_count %ui -> %ui", - *insert_count, req_insert_count); - - *insert_count = req_insert_count; - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) -{ - size_t n; - 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); - dt = &h3c->table; - - n = dt->base + dt->nelts; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 check insert count req:%ui, have:%ui", - insert_count, n); - - if (n >= insert_count) { - return NGX_OK; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); - - block = NULL; - - for (cln = c->pool->cleanup; cln; cln = cln->next) { - if (cln->handler == ngx_http_v3_unblock) { - block = cln->data; - break; - } - } - - if (block == NULL) { - cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->handler = ngx_http_v3_unblock; - - block = cln->data; - block->queue.prev = NULL; - block->connection = c; - block->nblocked = &h3c->nblocked; - } - - 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) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client exceeded http3_max_blocked_streams limit"); - - ngx_http_v3_finalize_connection(c, - NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, - "too many blocked streams"); - return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; - } - - h3c->nblocked++; - ngx_queue_insert_tail(&h3c->blocked, &block->queue); - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 blocked:%ui", h3c->nblocked); - - return NGX_BUSY; -} - - -static void -ngx_http_v3_unblock(void *data) -{ - ngx_http_v3_block_t *block = data; - - if (block->queue.prev) { - ngx_queue_remove(&block->queue); - block->queue.prev = NULL; - (*block->nblocked)--; - } -} - - -static ngx_int_t -ngx_http_v3_new_entry(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_connection_t *bc; - ngx_http_v3_block_t *block; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(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); - block = (ngx_http_v3_block_t *) q; - bc = block->connection; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); - - ngx_http_v3_unblock(block); - ngx_post_event(bc->read, &ngx_posted_events); - } - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) -{ - switch (id) { - - case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); - break; - - case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE: - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value); - break; - - case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 param QPACK_BLOCKED_STREAMS:%uL", value); - break; - - default: - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 param #%uL:%uL", id, value); - } - - return NGX_OK; -} diff --git a/src/http/v3/ngx_http_v3_tables.h b/src/http/v3/ngx_http_v3_tables.h deleted file mode 100644 index 991cc12c9..000000000 --- a/src/http/v3/ngx_http_v3_tables.h +++ /dev/null @@ -1,53 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_HTTP_V3_TABLES_H_INCLUDED_ -#define _NGX_HTTP_V3_TABLES_H_INCLUDED_ - - -#include -#include -#include - - -typedef struct { - ngx_str_t name; - ngx_str_t value; -} ngx_http_v3_field_t; - - -typedef struct { - ngx_http_v3_field_t **elts; - ngx_uint_t nelts; - ngx_uint_t base; - size_t size; - size_t capacity; -} ngx_http_v3_dynamic_table_t; - - -void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); -ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, - ngx_uint_t index, ngx_str_t *value); -ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, - ngx_str_t *value); -ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); -ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); -ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); -ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); -ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, - ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, - ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, - ngx_uint_t *insert_count); -ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, - ngx_uint_t insert_count); -ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, - uint64_t value); - - -#endif /* _NGX_HTTP_V3_TABLES_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c new file mode 100644 index 000000000..f0a599655 --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.c @@ -0,0 +1,733 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_http_v3_parse_uni_t parse; + ngx_int_t index; +} ngx_http_v3_uni_stream_t; + + +typedef struct { + ngx_queue_t queue; + uint64_t id; + ngx_connection_t *connection; + ngx_uint_t *npushing; +} ngx_http_v3_push_t; + + +static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); +static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); +static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); +static void ngx_http_v3_push_cleanup(void *data); +static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, + ngx_uint_t type); + + +void +ngx_http_v3_init_uni_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + + n = c->quic->id >> 2; + + if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "reached maximum number of uni streams"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + c->quic->cancelable = 1; + + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == 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->index = -1; + + c->data = us; + + c->read->handler = ngx_http_v3_uni_read_handler; + c->write->handler = ngx_http_v3_dummy_write_handler; + + ngx_http_v3_uni_read_handler(c->read); +} + + +static void +ngx_http_v3_close_uni_stream(ngx_connection_t *c) +{ + ngx_pool_t *pool; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); + + us = c->data; + + if (us && us->index >= 0) { + h3c = ngx_http_v3_get_session(c); + h3c->known_streams[us->index] = NULL; + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +ngx_int_t +ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) +{ + ngx_int_t index; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + + switch (type) { + + case NGX_HTTP_V3_STREAM_ENCODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + break; + + case NGX_HTTP_V3_STREAM_CONTROL: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + + break; + + default: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xL", type); + + if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + index = -1; + } + + if (index >= 0) { + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + h3c->known_streams[index] = c; + + us = c->data; + us->index = index; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_uni_read_handler(ngx_event_t *rev) +{ + u_char buf[128]; + ssize_t n; + ngx_buf_t b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + c = rev->data; + us = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + while (rev->ready) { + + n = c->recv(c, buf, sizeof(buf)); + + if (n == NGX_ERROR) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + if (n == 0) { + if (us->index >= 0) { + rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + b.pos = buf; + b.last = buf + n; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); + + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rc > 0) { + goto failed; + } + + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + return; + +failed: + + ngx_http_v3_finalize_connection(c, rc, "stream error"); + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_dummy_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +/* XXX async & buffered stream writes */ + +ngx_connection_t * +ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; + size_t n; + ngx_connection_t *sc; + ngx_pool_cleanup_t *cln; + ngx_http_v3_push_t *push; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create push stream id:%uL", push_id); + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + goto failed; + } + + p = buf; + p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); + if (cln == NULL) { + goto failed; + } + + h3c->npushing++; + + cln->handler = ngx_http_v3_push_cleanup; + + push = cln->data; + push->id = push_id; + push->connection = sc; + push->npushing = &h3c->npushing; + + ngx_queue_insert_tail(&h3c->pushing, &push->queue); + + return sc; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create push stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } + + return NULL; +} + + +static void +ngx_http_v3_push_cleanup(void *data) +{ + ngx_http_v3_push_t *push = data; + + ngx_queue_remove(&push->queue); + (*push->npushing)--; +} + + +static ngx_connection_t * +ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) +{ + u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; + size_t n; + ngx_int_t index; + ngx_connection_t *sc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + switch (type) { + case NGX_HTTP_V3_STREAM_ENCODER: + index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + break; + case NGX_HTTP_V3_STREAM_DECODER: + index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + break; + case NGX_HTTP_V3_STREAM_CONTROL: + index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + break; + default: + index = -1; + } + + h3c = ngx_http_v3_get_session(c); + + if (index >= 0) { + if (h3c->known_streams[index]) { + return h3c->known_streams[index]; + } + } + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + goto failed; + } + + sc->quic->cancelable = 1; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create uni stream, type:%ui", type); + + us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + goto failed; + } + + us->index = index; + + sc->data = us; + + sc->read->handler = ngx_http_v3_uni_read_handler; + sc->write->handler = ngx_http_v3_dummy_write_handler; + + if (index >= 0) { + h3c->known_streams[index] = sc; + } + + n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + return sc; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create server stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } + + return NULL; +} + + +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; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + 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, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->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, + NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->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) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send settings"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + n = ngx_http_v3_encode_varlen_int(NULL, id); + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send goaway"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send section acknowledgement %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x80; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send section acknowledgement"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send section acknowledgement"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send stream cancellation %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send stream cancellation"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send insert count increment %ui", inc); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send insert count increment"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send insert count increment"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 MAX_PUSH_ID:%uL", max_push_id); + + if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + h3c->max_push_id = max_push_id; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); + + h3c->goaway_push_id = push_id; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) +{ + ngx_queue_t *q; + ngx_http_request_t *r; + ngx_http_v3_push_t *push; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 CANCEL_PUSH:%uL", push_id); + + if (push_id >= h3c->next_push_id) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + for (q = ngx_queue_head(&h3c->pushing); + q != ngx_queue_sentinel(&h3c->pushing); + q = ngx_queue_next(&h3c->pushing)) + { + push = (ngx_http_v3_push_t *) q; + + if (push->id != push_id) { + continue; + } + + r = push->connection->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 cancel push"); + + ngx_http_finalize_request(r, NGX_HTTP_CLOSE); + + break; + } + + return NGX_OK; +} + + +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, + "http3 cancel stream %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h new file mode 100644 index 000000000..8c1eb99d3 --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.h @@ -0,0 +1,38 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_ +#define _NGX_HTTP_V3_UNI_H_INCLUDED_ + + +#include +#include +#include + + +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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, + uint64_t push_id); +ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, + uint64_t max_push_id); +ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); +ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); +ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); + +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); +ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */ -- cgit v1.2.3 From 1bd3cae95948165f23c5a9b99bd6d1e16f0f5caa Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 7 Dec 2021 15:42:10 +0300 Subject: QUIC: converted ngx_quic_keys_set_encryption_secret() to NGX codes. While here, removed check for encryption level zero, redundant by its nature. --- src/event/quic/ngx_event_quic_protection.c | 13 +++++-------- src/event/quic/ngx_event_quic_protection.h | 7 ++++--- src/event/quic/ngx_event_quic_ssl.c | 24 ++++++++++++++++++------ 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index d193a7738..3db510eef 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -649,7 +649,8 @@ failed: } -int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, +ngx_int_t +ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) { @@ -667,11 +668,7 @@ int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, if (key_len == NGX_ERROR) { ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); - return 0; - } - - if (level == ssl_encryption_initial) { - return 0; + return NGX_ERROR; } peer_secret->secret.data = ngx_pnalloc(pool, secret_len); @@ -702,11 +699,11 @@ int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, seq[i].secret, secret_len) != NGX_OK) { - return 0; + return NGX_ERROR; } } - return 1; + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 7a1604c6c..92491f02a 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -20,9 +20,10 @@ ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version); -int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, - ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, - const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, + ngx_uint_t is_write, ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level); void ngx_quic_keys_discard(ngx_quic_keys_t *keys, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 9fc5c3985..fb4b1af85 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -75,7 +75,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, cipher, rsecret, secret_len) - != 1) + != NGX_OK) { return 0; } @@ -109,8 +109,14 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, secret_len, wsecret); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); + if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; } #else @@ -139,7 +145,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, cipher, rsecret, secret_len) - != 1) + != NGX_OK) { return 0; } @@ -158,8 +164,14 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, secret_len, wsecret); #endif - return ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, - cipher, wsecret, secret_len); + if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; } #endif -- cgit v1.2.3 From 9860a82b195afb9c8d7a98536c752927b677736b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 7 Dec 2021 15:49:30 +0300 Subject: HTTP/3: avoid sending stream cancellation for pushed streams. --- src/http/v3/ngx_http_v3_request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e103a7eca..6faa3ee0b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -405,7 +405,9 @@ ngx_http_v3_reset_connection(ngx_connection_t *c) } #endif - if (h3scf->max_table_capacity > 0 && !c->read->eof) { + if (h3scf->max_table_capacity > 0 && !c->read->eof + && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); } -- cgit v1.2.3 From a42a62fc583ad311d06a90e3bcd12d63df0701a4 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 7 Dec 2021 15:49:51 +0300 Subject: QUIC: clear SSL_OP_ENABLE_MIDDLEBOX_COMPAT on SSL context switch. The SSL_OP_ENABLE_MIDDLEBOX_COMPAT option is provided by QuicTLS and enabled by default in the newly created SSL contexts. SSL_set_quic_method() is used to clear it, which is required for SSL handshake to work on QUIC connections. Switching context in the ngx_http_ssl_servername() SNI callback overrides SSL options from the new SSL context. This results in the option set again. Fix is to explicitly clear it when switching to another SSL context. Initially reported here (in Russian): http://mailman.nginx.org/pipermail/nginx-ru/2021-November/063989.html --- src/http/ngx_http_request.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index e8907c100..32a26379c 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -953,6 +953,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #ifdef SSL_OP_NO_RENEGOTIATION SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#if (NGX_HTTP_QUIC) + if (c->listening->quic) { + SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + } +#endif #endif } -- cgit v1.2.3 From 09286ac7d9a3de8fe8eee00648245fe8c64c964a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 7 Dec 2021 16:07:47 +0300 Subject: QUIC: updated README. The ngx_http_quic_module is merged to ngx_http_v3_module. The $quic variable no longer exists, it is replaced with $http3 variable. --- README | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README b/README index 5fd38cbf2..7ff4c0fb4 100644 --- a/README +++ b/README @@ -84,7 +84,6 @@ Experimental QUIC support for nginx following new configuration options: --with-http_v3_module - enable QUIC and HTTP/3 - --with-http_quic_module - enable QUIC for older HTTP versions --with-stream_quic_module - enable QUIC in Stream 3. Configuration @@ -148,7 +147,7 @@ Example configuration: http { log_format quic '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$quic"'; + '"$http_referer" "$http_user_agent" "$http3"'; access_log logs/access.log quic; -- cgit v1.2.3 From dea9a208c308ba29135ed1dab4eb1768e98b8b57 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 8 Dec 2021 17:04:56 +0300 Subject: HTTP/3: cleanup after "listen .. quic" removal in be08b858086a. --- src/http/ngx_http_core_module.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 822c7d264..6ee05f321 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -238,7 +238,6 @@ struct ngx_http_addr_conf_s { ngx_http_virtual_names_t *virtual_names; unsigned ssl:1; - unsigned quic:1; unsigned http2:1; unsigned http3:1; unsigned proxy_protocol:1; -- cgit v1.2.3 From 702a0986f393390d25368180e619e40160fe92da Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 9 Dec 2021 11:15:25 +0300 Subject: QUIC: fixed e06283038ec8 mis-merge. The NGX_HTTP_QUIC macro was removed in 33226ac61076. --- src/http/ngx_http_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 32a26379c..12a8cd144 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -956,7 +956,7 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #endif #ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT -#if (NGX_HTTP_QUIC) +#if (NGX_HTTP_V3) if (c->listening->quic) { SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); } -- cgit v1.2.3 From b61447d436bc0d774c0fb85de82a887a3ac13ccc Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 9 Dec 2021 12:40:14 +0300 Subject: QUIC: refactored ngx_quic_frame_sendto() function. The function now takes path as an argument to deal with associated restrictions and update sent counter. --- src/event/quic/ngx_event_quic_migration.c | 39 +++---------------------------- src/event/quic/ngx_event_quic_output.c | 31 ++++++++++++++++++------ src/event/quic/ngx_event_quic_output.h | 4 ++-- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index c3758ad4f..55997cbd3 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -24,8 +24,6 @@ ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, ngx_quic_path_challenge_frame_t *f) { - off_t max, pad; - ssize_t sent; ngx_quic_path_t *path; ngx_quic_frame_t frame, *fp; ngx_quic_socket_t *qsock; @@ -49,26 +47,11 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, /* * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame * to at least the smallest allowed maximum datagram size of 1200 bytes. - * ... - * An endpoint MUST NOT expand the datagram containing the PATH_RESPONSE - * if the resulting data exceeds the anti-amplification limit. */ - if (path->state != NGX_QUIC_PATH_VALIDATED) { - max = path->received * 3; - max = (path->sent >= max) ? 0 : max - path->sent; - pad = ngx_min(1200, max); - - } else { - pad = 1200; - } - - sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent < 0) { + if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { return NGX_ERROR; } - path->sent += sent; - if (qsock == qc->socket) { /* * RFC 9000, 9.3.3. Off-Path Packet Forwarding @@ -535,8 +518,6 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_socket_t *qsock) static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) { - off_t max, pad; - ssize_t sent; ngx_quic_frame_t frame; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -558,30 +539,16 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) */ /* same applies to PATH_RESPONSE frames */ - - max = path->received * 3; - max = (path->sent >= max) ? 0 : max - path->sent; - pad = ngx_min(1200, max); - - sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent < 0) { + if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { return NGX_ERROR; } - path->sent += sent; - ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8); - max = (path->sent >= max) ? 0 : max - path->sent; - pad = ngx_min(1200, max); - - sent = ngx_quic_frame_sendto(c, &frame, pad, path->sockaddr, path->socklen); - if (sent < 0) { + if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { return NGX_ERROR; } - path->sent += sent; - return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 4d97626a9..3d75e9c39 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1208,12 +1208,13 @@ ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } -ssize_t +ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, - size_t min, struct sockaddr *sockaddr, socklen_t socklen) + size_t min, ngx_quic_path_t *path) { + off_t max; size_t min_payload, pad; - ssize_t len; + ssize_t len, sent; ngx_str_t res; ngx_quic_header_t pkt; ngx_quic_send_ctx_t *ctx; @@ -1227,6 +1228,15 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ngx_quic_init_packet(c, ctx, qc->socket, &pkt); + /* account for anti-amplification limit: expand to allowed size */ + if (path->state != NGX_QUIC_PATH_VALIDATED) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + if ((off_t) min > max) { + min = max; + } + } + min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; pad = 4 - pkt.num_len; @@ -1234,14 +1244,14 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, len = ngx_quic_create_frame(NULL, frame); if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { - return -1; + return NGX_ERROR; } ngx_quic_log_frame(c->log, frame, 1); len = ngx_quic_create_frame(src, frame); if (len == -1) { - return -1; + return NGX_ERROR; } if (len < (ssize_t) min_payload) { @@ -1255,10 +1265,17 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, res.data = dst; if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { - return -1; + return NGX_ERROR; } ctx->pnum++; - return ngx_quic_send(c, res.data, res.len, sockaddr, socklen); + sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); + if (sent < 0) { + return NGX_ERROR; + } + + path->sent += sent; + + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h index 66b7d12ff..c19f14bf1 100644 --- a/src/event/quic/ngx_event_quic_output.h +++ b/src/event/quic/ngx_event_quic_output.h @@ -34,7 +34,7 @@ ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); -ssize_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, - size_t min, struct sockaddr *sockaddr, socklen_t socklen); +ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path); #endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ -- cgit v1.2.3 From 3ab900cbd973af51ba5b0c54f8e31e2ca997b283 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Dec 2021 11:04:55 +0300 Subject: QUIC: added missing frame initialization. Currently, all used fields are initialized, but usage may change in future. --- src/event/quic/ngx_event_quic_migration.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 55997cbd3..f2a9f5bfd 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -31,6 +31,8 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, qc = ngx_quic_get_connection(c); + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + frame.level = ssl_encryption_application; frame.type = NGX_QUIC_FT_PATH_RESPONSE; frame.u.path_response = *f; @@ -524,6 +526,8 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) "quic path #%uL send path challenge tries:%ui", path->seqnum, path->tries); + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + frame.level = ssl_encryption_application; frame.type = NGX_QUIC_FT_PATH_CHALLENGE; -- cgit v1.2.3 From d06f60250e0a27b0e849e8da87f21ce3280ec568 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Thu, 9 Dec 2021 15:30:01 +0300 Subject: QUIC: configure cleanup. Renamed and removed some macros. --- auto/modules | 2 +- auto/options | 5 ++--- auto/os/linux | 70 +++++++++++++++++++++++++---------------------------------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/auto/modules b/auto/modules index bc1b9bafd..95c237c80 100644 --- a/auto/modules +++ b/auto/modules @@ -1365,7 +1365,7 @@ if [ $USE_OPENSSL_QUIC = YES ]; then . auto/module - if [ $NGX_QUIC_BPF$BPF_FOUND$SO_COOKIE_FOUND = YESYESYES ]; then + if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_quic_bpf_module ngx_module_incs= diff --git a/auto/options b/auto/options index 51387f412..d9873f462 100644 --- a/auto/options +++ b/auto/options @@ -45,7 +45,7 @@ USE_THREADS=NO NGX_FILE_AIO=NO -NGX_QUIC_BPF=YES +QUIC_BPF=NO HTTP=YES @@ -170,7 +170,6 @@ USE_GEOIP=NO NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO -BPF_FOUND=NO SO_COOKIE_FOUND=NO NGX_LIBATOMIC=NO @@ -218,7 +217,7 @@ do --with-file-aio) NGX_FILE_AIO=YES ;; - --without-quic_bpf_module) NGX_QUIC_BPF=NO ;; + --without-quic_bpf_module) QUIC_BPF=NONE ;; --with-ipv6) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG diff --git a/auto/os/linux b/auto/os/linux index 3b9c28419..8bb25190a 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -235,33 +235,45 @@ ngx_include="sys/vfs.h"; . auto/include CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64" -# (E)BPF +# BPF sockhash -ngx_feature="BPF support" +ngx_feature="BPF sockhash" ngx_feature_name="NGX_HAVE_BPF" ngx_feature_run=no ngx_feature_incs="#include #include " ngx_feature_path= ngx_feature_libs= -ngx_feature_test=" - union bpf_attr attr = { 0 }; - /* only declare BPF support if all required features found */ - attr.map_flags = 0; - attr.map_type = BPF_MAP_TYPE_SOCKHASH; - syscall(__NR_bpf, 0, &attr, 0);" +ngx_feature_test="union bpf_attr attr = { 0 }; + attr.map_flags = 0; + attr.map_type = BPF_MAP_TYPE_SOCKHASH; + + syscall(__NR_bpf, 0, &attr, 0);" . auto/feature if [ $ngx_found = yes ]; then - BPF_FOUND=YES - CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" -fi + # quic bpf module uses sockhash to select socket from reuseport group, + # support appeared in Linux 5.7: + # + # commit: 9fed9000c5c6cacfcaaa48aff74818072ae294cc + # bpf: Allow selecting reuseport socket from a SOCKMAP/SOCKHASH + + if [ $QUIC_BPF != NONE ]; then + echo $ngx_n "checking for BPF sockhash support in kernel ...$ngx_c" + if [ $version -lt 329472 ]; then + echo " not found (at least 5.7 is required)" + QUIC_BPF=NO + else + echo " found" + QUIC_BPF=YES + fi + fi +fi -# SO_COOKIE socket option ngx_feature="SO_COOKIE" ngx_feature_name="NGX_HAVE_SO_COOKIE" @@ -271,17 +283,16 @@ ngx_feature_incs="#include ngx_feature_path= ngx_feature_libs= ngx_feature_test="socklen_t optlen = sizeof(uint64_t); - uint64_t cookie; - getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" + uint64_t cookie; + getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" . auto/feature if [ $ngx_found = yes ]; then SO_COOKIE_FOUND=YES - have=NGX_HAVE_SO_COOKIE . auto/have fi -# UDP_SEGMENT socket option is used for segmentation offloading +# UDP segmentation offloading ngx_feature="UDP_SEGMENT" ngx_feature_name="NGX_HAVE_UDP_SEGMENT" @@ -292,29 +303,6 @@ ngx_feature_incs="#include ngx_feature_path= ngx_feature_libs= ngx_feature_test="socklen_t optlen = sizeof(int); - int val; - getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)" + int val; + getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)" . auto/feature - -if [ $ngx_found = yes ]; then - UDP_SEGMENT_FOUND=YES - have=NGX_HAVE_UDP_SEGMENT . auto/have -fi - - -# ngx_quic_bpf module uses sockhash to select socket from reuseport group, -# support appeared in Linux-5.7: -# -# commit: 9fed9000c5c6cacfcaaa48aff74818072ae294cc -# bpf: Allow selecting reuseport socket from a SOCKMAP/SOCKHASH -# -if [ $NGX_QUIC_BPF$BPF_FOUND = YESYES ]; then - echo $ngx_n "checking for kernel with reuseport/BPF support...$ngx_c" - if [ $version -lt 329472 ]; then - echo " not found (at least 5.7 is required)" - NGX_QUIC_BPF=NO - else - echo " found" - fi -fi - -- cgit v1.2.3 From 4374cbfb1e8a86bc28b1aba651f84d3ba5b809a6 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Thu, 9 Dec 2021 15:30:50 +0300 Subject: QUIC: removed configure time test for BPF sockhash. The test verifies kernel version on a build machine, but actually used kernel may be different. --- auto/os/linux | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/auto/os/linux b/auto/os/linux index 8bb25190a..f60809c7a 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -256,21 +256,8 @@ if [ $ngx_found = yes ]; then CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" - # quic bpf module uses sockhash to select socket from reuseport group, - # support appeared in Linux 5.7: - # - # commit: 9fed9000c5c6cacfcaaa48aff74818072ae294cc - # bpf: Allow selecting reuseport socket from a SOCKMAP/SOCKHASH - if [ $QUIC_BPF != NONE ]; then - echo $ngx_n "checking for BPF sockhash support in kernel ...$ngx_c" - if [ $version -lt 329472 ]; then - echo " not found (at least 5.7 is required)" - QUIC_BPF=NO - else - echo " found" - QUIC_BPF=YES - fi + QUIC_BPF=YES fi fi -- cgit v1.2.3 From 59312ddac1afe8acc28a3cfc4786d42d057b4934 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 23 Nov 2021 21:39:51 +0300 Subject: QUIC: post stream events instead of calling their handlers. This potentially reduces the number of handler calls. --- src/event/quic/ngx_event_quic_streams.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 60e693bbd..bfbe05c26 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1120,7 +1120,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, rev->ready = 1; if (rev->active) { - rev->handler(rev); + ngx_post_event(rev, &ngx_posted_events); } } @@ -1367,7 +1367,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, } if (rev->active) { - rev->handler(rev); + ngx_post_event(rev, &ngx_posted_events); } return NGX_OK; @@ -1436,7 +1436,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, wev = qs->connection->write; if (wev->active) { - wev->handler(wev); + ngx_post_event(wev, &ngx_posted_events); } return NGX_OK; -- cgit v1.2.3 From 6ea39f03ae182e5455e2fbf0a39aabc9c150c377 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 10 Dec 2021 19:43:50 +0300 Subject: QUIC: simplified stream initialization. After creation, a client stream is added to qc->streams.uninitialized queue. After initialization it's removed from the queue. If a stream is never initialized, it is freed in ngx_quic_close_streams(). Stream initializer is now set as read event handler in stream connection. Previously qc->streams.uninitialized was used only for delayed stream initialization. The change makes it possible not to handle separately the case of a new stream in stream-related frame handlers. It makes these handlers simpler since new streams and existing streams are now handled by the same code. --- src/event/quic/ngx_event_quic_streams.c | 234 +++++++++----------------------- 1 file changed, 62 insertions(+), 172 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index bfbe05c26..f68dfc9b7 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -13,10 +13,9 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 -static ngx_quic_stream_t *ngx_quic_create_client_stream(ngx_connection_t *c, - uint64_t id); +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 ngx_int_t ngx_quic_init_stream(ngx_quic_stream_t *qs); +static void ngx_quic_init_stream_handler(ngx_event_t *ev); static void ngx_quic_init_streams_handler(ngx_connection_t *c); static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, uint64_t id); @@ -306,21 +305,28 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) static ngx_quic_stream_t * -ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) +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; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL is new", id); - qc = ngx_quic_get_connection(c); + qs = ngx_quic_find_stream(&qc->streams.tree, id); + + if (qs) { + return qs; + } + if (qc->shutdown || qc->closing) { return NGX_QUIC_STREAM_GONE; } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is missing", id); + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { @@ -377,7 +383,11 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) * streams of that type with lower-numbered stream IDs also being opened. */ - for ( /* void */ ; min_id < id; min_id += 0x04) { +#if (NGX_SUPPRESS_WARN) + qs = NULL; +#endif + + for ( /* void */ ; min_id <= id; min_id += 0x04) { qs = ngx_quic_create_stream(c, min_id); @@ -389,22 +399,17 @@ ngx_quic_create_client_stream(ngx_connection_t *c, uint64_t id) continue; } - if (ngx_quic_init_stream(qs) != NGX_OK) { - return NULL; - } + ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); - if (qc->shutdown || qc->closing) { - return NGX_QUIC_STREAM_GONE; + rev = qs->connection->read; + rev->handler = ngx_quic_init_stream_handler; + + if (qc->streams.initialized) { + ngx_post_event(rev, &ngx_posted_events); } } - qs = ngx_quic_create_stream(c, id); - if (qs == NULL) { - if (ngx_quic_reject_stream(c, id) != NGX_OK) { - return NULL; - } - return NGX_QUIC_STREAM_GONE; } @@ -461,29 +466,20 @@ ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) } -static ngx_int_t -ngx_quic_init_stream(ngx_quic_stream_t *qs) +static void +ngx_quic_init_stream_handler(ngx_event_t *ev) { - ngx_connection_t *c; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(qs->parent); - - c = qs->connection; - - if (!qc->streams.initialized) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic postpone stream init"); + ngx_connection_t *c; + ngx_quic_stream_t *qs; - ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); - return NGX_OK; - } + c = ev->data; + qs = c->quic; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); - c->listening->handler(c); + ngx_queue_remove(&qs->queue); - return NGX_OK; + c->listening->handler(c); } @@ -527,16 +523,12 @@ ngx_quic_init_streams_handler(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - while (!ngx_queue_empty(&qc->streams.uninitialized)) { - q = ngx_queue_head(&qc->streams.uninitialized); - ngx_queue_remove(q); - + 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_log_debug0(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0, - "quic init postponed stream"); - - qs->connection->listening->handler(qs->connection); + ngx_post_event(qs->connection->read, &ngx_posted_events); } qc->streams.initialized = 1; @@ -1013,7 +1005,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { uint64_t last; - ngx_pool_t *pool; ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -1033,39 +1024,14 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, /* no overflow since both values are 62-bit */ last = f->offset + f->length; - qs = ngx_quic_find_stream(&qc->streams.tree, f->stream_id); + qs = ngx_quic_get_stream(c, f->stream_id); if (qs == NULL) { - qs = ngx_quic_create_client_stream(c, f->stream_id); - - if (qs == NULL) { - return NGX_ERROR; - } - - if (qs == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = qs->connection; - - if (ngx_quic_control_flow(sc, last) != NGX_OK) { - goto cleanup; - } - - if (f->fin) { - sc->read->pending_eof = 1; - qs->final_size = last; - } - - if (f->offset == 0) { - sc->read->ready = 1; - } - - if (ngx_quic_order_bufs(c, &qs->in, frame->data, f->offset) != NGX_OK) { - goto cleanup; - } + return NGX_ERROR; + } - return ngx_quic_init_stream(qs); + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } sc = qs->connection; @@ -1125,15 +1091,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } return NGX_OK; - -cleanup: - - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; } @@ -1210,20 +1167,14 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, return NGX_ERROR; } - qs = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_get_stream(c, f->id); if (qs == NULL) { - qs = ngx_quic_create_client_stream(c, f->id); - - if (qs == NULL) { - return NGX_ERROR; - } - - if (qs == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } + return NGX_ERROR; + } - return ngx_quic_init_stream(qs); + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } return ngx_quic_update_max_stream_data(qs->connection); @@ -1248,24 +1199,14 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, return NGX_ERROR; } - qs = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_get_stream(c, f->id); if (qs == NULL) { - qs = ngx_quic_create_client_stream(c, f->id); - - if (qs == NULL) { - return NGX_ERROR; - } - - if (qs == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - if (f->limit > qs->send_max_data) { - qs->send_max_data = f->limit; - } + return NGX_ERROR; + } - return ngx_quic_init_stream(qs); + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } if (f->limit <= qs->send_max_data) { @@ -1293,7 +1234,6 @@ ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { - ngx_pool_t *pool; ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -1308,36 +1248,14 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } - qs = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_get_stream(c, f->id); if (qs == NULL) { - qs = ngx_quic_create_client_stream(c, f->id); - - if (qs == NULL) { - return NGX_ERROR; - } - - if (qs == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = qs->connection; - - rev = sc->read; - rev->error = 1; - rev->ready = 1; - - if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { - goto cleanup; - } - - qs->final_size = f->final_size; - - if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) { - goto cleanup; - } + return NGX_ERROR; + } - return ngx_quic_init_stream(qs); + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } sc = qs->connection; @@ -1371,15 +1289,6 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, } return NGX_OK; - -cleanup: - - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; } @@ -1387,9 +1296,7 @@ ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) { - ngx_pool_t *pool; ngx_event_t *wev; - ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1402,31 +1309,14 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_ERROR; } - qs = ngx_quic_find_stream(&qc->streams.tree, f->id); + qs = ngx_quic_get_stream(c, f->id); if (qs == NULL) { - qs = ngx_quic_create_client_stream(c, f->id); - - if (qs == NULL) { - return NGX_ERROR; - } - - if (qs == NGX_QUIC_STREAM_GONE) { - return NGX_OK; - } - - sc = qs->connection; - - if (ngx_quic_reset_stream(sc, f->error_code) != NGX_OK) { - pool = sc->pool; - - ngx_close_connection(sc); - ngx_destroy_pool(pool); - - return NGX_ERROR; - } + return NGX_ERROR; + } - return ngx_quic_init_stream(qs); + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; } if (ngx_quic_reset_stream(qs->connection, f->error_code) != NGX_OK) { -- cgit v1.2.3 From 6e7f19280423056bf06fcd5055db3fcabb842c76 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Dec 2021 14:49:42 +0300 Subject: QUIC: write and full stream shutdown support. Full stream shutdown is now called from stream cleanup handler instead of explicitly sending frames. --- src/event/quic/ngx_event_quic_streams.c | 135 +++++++++++++++++++------------- src/os/unix/ngx_socket.h | 2 + 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index f68dfc9b7..295aa54aa 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -13,6 +13,8 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +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); @@ -256,6 +258,37 @@ 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) +{ + ngx_quic_stream_t *qs; + + qs = c->quic; + + if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) + || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { + return NGX_ERROR; + } + } + } + + if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 + || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_shutdown_stream_send(ngx_connection_t *c) { ngx_event_t *wev; ngx_connection_t *pc; @@ -263,10 +296,6 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - if (how != NGX_WRITE_SHUTDOWN) { - return NGX_OK; - } - wev = c->write; if (wev->error) { @@ -283,7 +312,7 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL shutdown", qs->id); + "quic stream id:0x%xL send shutdown", qs->id); frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM; @@ -297,13 +326,56 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) ngx_quic_queue_frame(qc, frame); - wev->ready = 1; wev->error = 1; return NGX_OK; } +static ngx_int_t +ngx_quic_shutdown_stream_recv(ngx_connection_t *c) +{ + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + rev = c->read; + + if (rev->pending_eof || rev->error) { + return NGX_OK; + } + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->conf->stream_close_code == 0) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv shutdown", qs->id); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = qc->conf->stream_close_code; + + ngx_quic_queue_frame(qc, frame); + + rev->error = 1; + + return NGX_OK; +} + + static ngx_quic_stream_t * ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) { @@ -916,30 +988,10 @@ ngx_quic_stream_cleanup_handler(void *data) goto done; } - c->read->pending_eof = 1; + (void) ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN); (void) ngx_quic_update_flow(c, qs->recv_last); - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 - || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) - { - if (!c->read->pending_eof && !c->read->error - && qc->conf->stream_close_code) - { - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - goto done; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STOP_SENDING; - frame->u.stop_sending.id = qs->id; - frame->u.stop_sending.error_code = qc->conf->stream_close_code; - - ngx_quic_queue_frame(qc, frame); - } - } - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { @@ -959,37 +1011,8 @@ ngx_quic_stream_cleanup_handler(void *data) } ngx_quic_queue_frame(qc, frame); - - if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - /* do not send fin for client unidirectional streams */ - goto done; - } - } - - if (c->write->error) { - goto done; - } - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL send fin", qs->id); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - goto done; } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM; - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 1; - - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = 0; - - ngx_quic_queue_frame(qc, frame); - done: (void) ngx_quic_output(pc); diff --git a/src/os/unix/ngx_socket.h b/src/os/unix/ngx_socket.h index ec66a6f83..4480adca2 100644 --- a/src/os/unix/ngx_socket.h +++ b/src/os/unix/ngx_socket.h @@ -13,6 +13,8 @@ #define NGX_WRITE_SHUTDOWN SHUT_WR +#define NGX_READ_SHUTDOWN SHUT_RD +#define NGX_RDWR_SHUTDOWN SHUT_RDWR typedef int ngx_socket_t; -- cgit v1.2.3 From a31745499bcf35fac236bdc5f3d0d0a6d679b4e0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Dec 2021 17:27:29 +0300 Subject: QUIC: improved path validation. Previously, path was considered valid during arbitrary selected 10m timeout since validation. This is quite not what RFC 9000 says; the relevant part is: An endpoint MAY skip validation of a peer address if that address has been seen recently. The patch considers a path to be 'recently seen' if packets were received during idle timeout. If a packet is received from the path that was seen not so recently, such path is considered new, and anti-amplification restrictions apply. --- src/event/quic/ngx_event_quic_connection.h | 2 +- src/event/quic/ngx_event_quic_migration.c | 38 +++++++++++------------------- src/event/quic/ngx_event_quic_migration.h | 2 -- src/event/quic/ngx_event_quic_socket.c | 1 - 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index ee80342fa..7b6afb123 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -86,6 +86,7 @@ struct ngx_quic_path_s { socklen_t socklen; ngx_uint_t state; ngx_msec_t expires; + ngx_msec_t last_seen; ngx_uint_t tries; off_t sent; off_t received; @@ -93,7 +94,6 @@ struct ngx_quic_path_s { u_char challenge2[8]; ngx_uint_t refcnt; uint64_t seqnum; - time_t validated_at; ngx_str_t addr_text; u_char text[NGX_SOCKADDR_STRLEN]; }; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index f2a9f5bfd..0bfee6058 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -158,7 +158,6 @@ valid: "quic path #%uL successfully validated", path->seqnum); path->state = NGX_QUIC_PATH_VALIDATED; - path->validated_at = ngx_time(); return NGX_OK; } @@ -217,6 +216,7 @@ ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, } path->seqnum = qc->path_seqnum++; + path->last_seen = ngx_current_msec; path->socklen = socklen; ngx_memcpy(path->sockaddr, sockaddr, socklen); @@ -272,6 +272,7 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; + qc = ngx_quic_get_connection(c); qsock = ngx_quic_get_socket(c); if (c->udp->dgram == NULL) { @@ -313,7 +314,6 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) cid = ngx_quic_used_client_id(c, path); if (cid == NULL) { - qc = ngx_quic_get_connection(c); qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; qc->error_reason = "no available client ids for new path"; @@ -328,6 +328,17 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) update: + if (path->state != NGX_QUIC_PATH_NEW) { + /* force limits/revalidation for paths that were not seen recently */ + if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) { + path->state = NGX_QUIC_PATH_NEW; + path->sent = 0; + path->received = 0; + } + } + + path->last_seen = ngx_current_msec; + len = pkt->raw->last - pkt->raw->start; /* TODO: this may be too late in some cases; @@ -396,31 +407,10 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum, ngx_quic_path_state_str(next)); - switch (next->state) { - case NGX_QUIC_PATH_NEW: + if (next->state == NGX_QUIC_PATH_NEW) { if (ngx_quic_validate_path(c, qsock) != NGX_OK) { return NGX_ERROR; } - break; - - /* migration to previously known path */ - - case NGX_QUIC_PATH_VALIDATING: - /* alredy validating, nothing to do */ - break; - - case NGX_QUIC_PATH_VALIDATED: - /* if path is old enough, revalidate */ - if (ngx_time() - next->validated_at > NGX_QUIC_PATH_VALID_TIME) { - - next->state = NGX_QUIC_PATH_NEW; - - if (ngx_quic_validate_path(c, qsock) != NGX_OK) { - return NGX_ERROR; - } - } - - break; } ctx = ngx_quic_get_send_ctx(qc, pkt->level); diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 4ad3213b6..4bb5a2a1a 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -17,8 +17,6 @@ #define NGX_QUIC_PATH_VALIDATING 1 #define NGX_QUIC_PATH_VALIDATED 2 -#define NGX_QUIC_PATH_VALID_TIME 600 /* seconds */ - #define ngx_quic_path_state_str(p) \ ((p)->state == NGX_QUIC_PATH_NEW) ? "new" : \ diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 4a9fb232d..2b9b0fed3 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -82,7 +82,6 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, if (pkt->validated) { path->state = NGX_QUIC_PATH_VALIDATED; - path->validated_at = ngx_time(); } /* now bind socket to client and path */ -- cgit v1.2.3 From 10fd8be86d657839fbc6218891bfe9046ab5b592 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 13 Dec 2021 09:48:33 +0300 Subject: QUIC: decoupled path state and limitation status. The path validation status and anti-amplification limit status is actually two different variables. It is possible that validating path should not be limited (for example, when re-validating former path). --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic_connection.h | 1 + src/event/quic/ngx_event_quic_migration.c | 12 +++++++++--- src/event/quic/ngx_event_quic_output.c | 6 +++--- src/event/quic/ngx_event_quic_socket.c | 1 + 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index cb71a16b1..4a9526d61 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1013,6 +1013,7 @@ ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) if (qc->socket->path->state != NGX_QUIC_PATH_VALIDATED) { qc->socket->path->state = NGX_QUIC_PATH_VALIDATED; + qc->socket->path->limited = 0; ngx_post_event(&qc->push, &ngx_posted_events); } } diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 7b6afb123..73268ea0a 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -85,6 +85,7 @@ struct ngx_quic_path_s { struct sockaddr *sockaddr; socklen_t socklen; ngx_uint_t state; + ngx_uint_t limited; /* unsigned limited:1; */ ngx_msec_t expires; ngx_msec_t last_seen; ngx_uint_t tries; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 0bfee6058..9b1ccafba 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -158,6 +158,7 @@ valid: "quic path #%uL successfully validated", path->seqnum); path->state = NGX_QUIC_PATH_VALIDATED; + path->limited = 0; return NGX_OK; } @@ -215,6 +216,9 @@ ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, return NULL; } + path->state = NGX_QUIC_PATH_NEW; + path->limited = 1; + path->seqnum = qc->path_seqnum++; path->last_seen = ngx_current_msec; @@ -332,6 +336,7 @@ update: /* force limits/revalidation for paths that were not seen recently */ if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) { path->state = NGX_QUIC_PATH_NEW; + path->limited = 1; path->sent = 0; path->received = 0; } @@ -350,11 +355,11 @@ update: */ path->received += len; - ngx_log_debug6(NGX_LOG_DEBUG_EVENT, c->log, 0, + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet via #%uL:%uL:%uL" - " size:%O path recvd:%O sent:%O", + " size:%O path recvd:%O sent:%O limited:%ui", qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum, - len, path->received, path->sent); + len, path->received, path->sent, path->limited); return NGX_OK; } @@ -607,6 +612,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) /* found expired path */ path->state = NGX_QUIC_PATH_NEW; + path->limited = 1; /* * RFC 9000, 9.4. Loss Detection and Congestion Control diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 3d75e9c39..3963ef006 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -160,7 +160,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) len = ngx_min(qc->ctp.max_udp_payload_size, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (path->state != NGX_QUIC_PATH_VALIDATED) { + if (path->limited) { max = path->received * 3; max = (path->sent >= max) ? 0 : max - path->sent; @@ -294,7 +294,7 @@ ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) return 0; } - if (qsock->path->state != NGX_QUIC_PATH_VALIDATED) { + if (qsock->path->limited) { /* don't even try to be faster on non-validated paths */ return 0; } @@ -1229,7 +1229,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ngx_quic_init_packet(c, ctx, qc->socket, &pkt); /* account for anti-amplification limit: expand to allowed size */ - if (path->state != NGX_QUIC_PATH_VALIDATED) { + if (path->limited) { max = path->received * 3; max = (path->sent >= max) ? 0 : max - path->sent; if ((off_t) min > max) { diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 2b9b0fed3..a04ad6202 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -82,6 +82,7 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, if (pkt->validated) { path->state = NGX_QUIC_PATH_VALIDATED; + path->limited = 0; } /* now bind socket to client and path */ -- cgit v1.2.3 From 3341a850763eec48263a4ab2fd3a35008b11da3e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 14 Dec 2021 16:24:20 +0300 Subject: QUIC: added path limiting function ngx_quic_path_limit(). --- src/event/quic/ngx_event_quic_output.c | 40 ++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 3963ef006..6d1859512 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -63,6 +63,8 @@ static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen); static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx); +static size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, + size_t size); size_t @@ -137,7 +139,6 @@ ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) { - off_t max; size_t len, min; ssize_t n; u_char *p; @@ -160,12 +161,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) len = ngx_min(qc->ctp.max_udp_payload_size, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - if (path->limited) { - max = path->received * 3; - max = (path->sent >= max) ? 0 : max - path->sent; - - len = ngx_min(len, (size_t) max); - } + len = ngx_quic_path_limit(c, path, len); pad = ngx_quic_get_padding_level(c); @@ -1212,7 +1208,6 @@ ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, size_t min, ngx_quic_path_t *path) { - off_t max; size_t min_payload, pad; ssize_t len, sent; ngx_str_t res; @@ -1228,14 +1223,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, ngx_quic_init_packet(c, ctx, qc->socket, &pkt); - /* account for anti-amplification limit: expand to allowed size */ - if (path->limited) { - max = path->received * 3; - max = (path->sent >= max) ? 0 : max - path->sent; - if ((off_t) min > max) { - min = max; - } - } + min = ngx_quic_path_limit(c, path, min); min_payload = min ? ngx_quic_payload_size(&pkt, min) : 0; @@ -1279,3 +1267,23 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, return NGX_OK; } + + +static size_t +ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) +{ + off_t max; + + if (path->limited) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + + if ((off_t) size > max) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path limit %uz - %O", size, max); + return max; + } + } + + return size; +} -- cgit v1.2.3 From 93230cd8cfbdb4dd8ed293210df73c3227d80bbd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Dec 2021 11:42:28 +0300 Subject: QUIC: added missing check for backup path existence. --- src/event/quic/ngx_event_quic_migration.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 9b1ccafba..592671093 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -81,6 +81,7 @@ ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_path_challenge_frame_t *f) { + ngx_uint_t rst; ngx_queue_t *q; ngx_quic_path_t *path, *prev; ngx_quic_connection_t *qc; @@ -127,13 +128,21 @@ valid: * unless the only change in the peer's address is its port number. */ - prev = qc->backup->path; + rst = 1; - if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, - path->sockaddr, path->socklen, 0) - != NGX_OK) - { - /* address has changed */ + if (qc->backup) { + prev = qc->backup->path; + + if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, + path->sockaddr, path->socklen, 0) + == NGX_OK) + { + /* address did not change */ + rst = 0; + } + } + + if (rst) { ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, -- cgit v1.2.3 From cb273ddf91ef2bac9366c9b7568277945d2ba1bd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 16 Dec 2021 11:49:08 +0300 Subject: QUIC: refactored ngx_quic_validate_path(). The function now accepts path argument, as suggested by the name. Socket is not really needed inside. --- src/event/quic/ngx_event_quic_migration.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 592671093..ac787e31d 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -13,7 +13,7 @@ static void ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, - ngx_quic_socket_t *qsock); + ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c); @@ -422,7 +422,7 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_path_state_str(next)); if (next->state == NGX_QUIC_PATH_NEW) { - if (ngx_quic_validate_path(c, qsock) != NGX_OK) { + if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) { return NGX_ERROR; } } @@ -478,17 +478,14 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) static ngx_int_t -ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_socket_t *qsock) +ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) { ngx_msec_t pto; - ngx_quic_path_t *path; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - path = qsock->path; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic initiated validation of new path #%uL", path->seqnum); -- cgit v1.2.3 From 541ec50c42ca5190dd101dc27964ee8f9a8af8cf Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 27 Dec 2021 13:52:57 +0300 Subject: QUIC: got rid of excessive "qsock" argument in ngx_quic_output.c. The output is always sent to the active path, which is stored in the quic connection. There is no need to pass it in arguments. When output has to be send to to a specific path (in rare cases, such as path probing), a separate method exists (ngx_quic_frame_sendto()). --- src/event/quic/ngx_event_quic_output.c | 75 +++++++++++++--------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 6d1859512..57e124228 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -38,26 +38,20 @@ #define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ -static ngx_int_t ngx_quic_socket_output(ngx_connection_t *c, - ngx_quic_socket_t *qsock); -static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c, - ngx_quic_socket_t *qsock); +static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c); static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, uint64_t pnum); #if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) -static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c, - ngx_quic_socket_t *qsock); -static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c, - ngx_quic_socket_t *qsock); +static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c); +static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c); static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); #endif static ssize_t ngx_quic_output_packet(ngx_connection_t *c, - ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min, - ngx_quic_socket_t *qsock); + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_quic_socket_t *qsock, ngx_quic_header_t *pkt); + ngx_quic_header_t *pkt); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen); @@ -84,23 +78,6 @@ ngx_quic_max_udp_payload(ngx_connection_t *c) ngx_int_t ngx_quic_output(ngx_connection_t *c) -{ - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (ngx_quic_socket_output(c, qc->socket) != NGX_OK) { - return NGX_ERROR; - } - - ngx_quic_set_lost_timer(c); - - return NGX_OK; -} - - -static ngx_int_t -ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) { size_t in_flight; ngx_int_t rc; @@ -115,12 +92,12 @@ ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) in_flight = cg->in_flight; #if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) - if (ngx_quic_allow_segmentation(c, qsock)) { - rc = ngx_quic_create_segments(c, qsock); + if (ngx_quic_allow_segmentation(c)) { + rc = ngx_quic_create_segments(c); } else #endif { - rc = ngx_quic_create_datagrams(c, qsock); + rc = ngx_quic_create_datagrams(c); } if (rc != NGX_OK) { @@ -132,12 +109,14 @@ ngx_quic_socket_output(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_add_timer(c->read, qc->tp.max_idle_timeout); } + ngx_quic_set_lost_timer(c); + return NGX_OK; } static ngx_int_t -ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) +ngx_quic_create_datagrams(ngx_connection_t *c) { size_t len, min; ssize_t n; @@ -152,7 +131,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) qc = ngx_quic_get_connection(c); cg = &qc->congestion; - path = qsock->path; + path = qc->socket->path; while (cg->in_flight < cg->window) { @@ -182,7 +161,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c, ngx_quic_socket_t *qsock) continue; } - n = ngx_quic_output_packet(c, ctx, p, len, min, qsock); + n = ngx_quic_output_packet(c, ctx, p, len, min); if (n == NGX_ERROR) { return NGX_ERROR; } @@ -276,7 +255,7 @@ ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, #if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) static ngx_uint_t -ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) +ngx_quic_allow_segmentation(ngx_connection_t *c) { size_t bytes, len; ngx_queue_t *q; @@ -290,7 +269,7 @@ ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) return 0; } - if (qsock->path->limited) { + if (qc->socket->path->limited) { /* don't even try to be faster on non-validated paths */ return 0; } @@ -331,7 +310,7 @@ ngx_quic_allow_segmentation(ngx_connection_t *c, ngx_quic_socket_t *qsock) static ngx_int_t -ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) +ngx_quic_create_segments(ngx_connection_t *c) { size_t len, segsize; ssize_t n; @@ -346,7 +325,7 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) qc = ngx_quic_get_connection(c); cg = &qc->congestion; - path = qsock->path; + path = qc->socket->path; ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); @@ -369,7 +348,7 @@ ngx_quic_create_segments(ngx_connection_t *c, ngx_quic_socket_t *qsock) if (len && cg->in_flight < cg->window) { - n = ngx_quic_output_packet(c, ctx, p, len, len, qsock); + n = ngx_quic_output_packet(c, ctx, p, len, len); if (n == NGX_ERROR) { return NGX_ERROR; } @@ -524,7 +503,7 @@ ngx_quic_get_padding_level(ngx_connection_t *c) static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - u_char *data, size_t max, size_t min, ngx_quic_socket_t *qsock) + u_char *data, size_t max, size_t min) { size_t len, pad, min_payload, max_payload; u_char *p; @@ -542,12 +521,11 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, return 0; } - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic output sock #%uL %s packet max:%uz min:%uz", - qsock->sid.seqnum, ngx_quic_level_name(ctx->level), - max, min); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); - ngx_quic_init_packet(c, ctx, qsock, &pkt); + ngx_quic_init_packet(c, ctx, &pkt); min_payload = ngx_quic_payload_size(&pkt, min); max_payload = ngx_quic_payload_size(&pkt, max); @@ -690,12 +668,15 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_quic_socket_t *qsock, ngx_quic_header_t *pkt) + ngx_quic_header_t *pkt) { + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); + qsock = qc->socket; + ngx_memzero(pkt, sizeof(ngx_quic_header_t)); pkt->flags = NGX_QUIC_PKT_FIXED_BIT; @@ -1221,7 +1202,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - ngx_quic_init_packet(c, ctx, qc->socket, &pkt); + ngx_quic_init_packet(c, ctx, &pkt); min = ngx_quic_path_limit(c, path, min); -- cgit v1.2.3 From 97b34a01e268b2d4946375b806b7e6364e765d70 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 24 Dec 2021 18:13:51 +0300 Subject: QUIC: avoid excessive buffer allocations in stream output. Previously, when a few bytes were send to a QUIC stream by the application, a 4K buffer was allocated for these bytes. Then a STREAM frame was created and that entire buffer was used as data for that frame. The frame with the buffer were in use up until the frame was acked by client. Meanwhile, when more bytes were send to the stream, more buffers were allocated and assigned as data to newer STREAM frames. In this scenario most buffer memory is unused. Now the unused part of the stream output buffer is available for further stream output while earlier parts of the buffer are waiting to be acked. This is achieved by splitting the output buffer. --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_frames.c | 10 ++++++---- src/event/quic/ngx_event_quic_frames.h | 2 +- src/event/quic/ngx_event_quic_ssl.c | 2 +- src/event/quic/ngx_event_quic_streams.c | 33 ++++++++++++++++++++++++--------- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 8ae7bf643..9481fef62 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -63,6 +63,7 @@ struct ngx_quic_stream_s { uint64_t recv_last; uint64_t final_size; ngx_chain_t *in; + ngx_chain_t *out; ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 4a3902f7f..71ed981e6 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -482,14 +482,14 @@ done: ngx_int_t ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, - size_t offset) + off_t limit, off_t offset) { + off_t n; u_char *p; - size_t n; ngx_buf_t *b; ngx_chain_t *cl, *sl; - while (in) { + while (in && limit) { cl = *out; if (cl == NULL) { @@ -523,8 +523,9 @@ ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, continue; } - for (p = b->pos + offset; p != b->last && in; /* void */ ) { + for (p = b->pos + offset; p != b->last && in && limit; /* void */ ) { n = ngx_min(b->last - p, in->buf->last - in->buf->pos); + n = ngx_min(n, limit); if (b->sync) { ngx_memcpy(p, in->buf->pos, n); @@ -533,6 +534,7 @@ ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, p += n; in->buf->pos += n; offset += n; + limit -= n; if (in->buf->pos == in->buf->last) { in = in->next; diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index f4c147682..1310f6dfd 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -31,7 +31,7 @@ ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, void ngx_quic_trim_bufs(ngx_chain_t *in, size_t size); void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); ngx_int_t ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, - ngx_chain_t *in, size_t offset); + ngx_chain_t *in, off_t limit, off_t offset); #if (NGX_DEBUG) void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index fb4b1af85..d2f1237a5 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -369,7 +369,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->offset > ctx->crypto_received) { - return ngx_quic_order_bufs(c, &ctx->crypto, frame->data, + return ngx_quic_order_bufs(c, &ctx->crypto, frame->data, f->length, f->offset - ctx->crypto_received); } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 295aa54aa..d5facc270 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -838,8 +838,9 @@ static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { size_t n, flow; + ngx_buf_t *b; ngx_event_t *wev; - ngx_chain_t *cl; + ngx_chain_t *out, **ll; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -862,18 +863,30 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { + if (ngx_quic_order_bufs(pc, &qs->out, in, n, 0) != NGX_OK) { return NGX_CHAIN_ERROR; } - frame->data = ngx_quic_copy_chain(pc, in, n); - if (frame->data == NGX_CHAIN_ERROR) { - return NGX_CHAIN_ERROR; + n = 0; + out = qs->out; + + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; + + if (b->sync) { + /* hole */ + break; + } + + n += b->last - b->pos; } - for (n = 0, cl = frame->data; cl; cl = cl->next) { - n += ngx_buf_size(cl->buf); + qs->out = *ll; + *ll = NULL; + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_CHAIN_ERROR; } while (in && ngx_buf_size(in->buf) == 0) { @@ -882,6 +895,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM; + frame->data = out; frame->u.stream.off = 1; frame->u.stream.len = 1; frame->u.stream.fin = 0; @@ -977,6 +991,7 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_bufs(pc, qs->in); + ngx_quic_free_bufs(pc, qs->out); if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ @@ -1098,7 +1113,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->final_size = last; } - if (ngx_quic_order_bufs(c, &qs->in, frame->data, + if (ngx_quic_order_bufs(c, &qs->in, frame->data, f->length, f->offset - qs->recv_offset) != NGX_OK) { -- cgit v1.2.3 From baea97bc543d68ea2cc3a5dc96363777204f99bd Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 24 Dec 2021 18:17:23 +0300 Subject: QUIC: refactored ngx_quic_order_bufs() and ngx_quic_split_bufs(). They are replaced with ngx_quic_write_chain() and ngx_quic_read_chain(). These functions represent the API to data buffering. The first function adds data of given size at given offset to the buffer. Now it returns the unwritten part of the chain similar to c->send_chain(). The second function returns data of given size from the beginning of the buffer. Its second argument and return value are swapped compared to ngx_quic_split_bufs() to better match ngx_quic_write_chain(). Added, returned and stored data are regular ngx_chain_t/ngx_buf_t chains. Missing data is marked with b->sync flag. The functions are now used in both send and recv data chains in QUIC streams. --- src/event/quic/ngx_event_quic_frames.c | 116 ++++++++++++++++++++------------ src/event/quic/ngx_event_quic_frames.h | 4 +- src/event/quic/ngx_event_quic_ssl.c | 10 ++- src/event/quic/ngx_event_quic_streams.c | 83 ++++++++++------------- 4 files changed, 117 insertions(+), 96 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 71ed981e6..fe6d98043 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -13,10 +13,6 @@ #define NGX_QUIC_BUFFER_SIZE 4096 -static ngx_chain_t *ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, - size_t len); - - ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c) { @@ -243,8 +239,8 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) onf->length = shrink; nf->len = ngx_quic_create_frame(NULL, nf); - nf->data = ngx_quic_split_bufs(c, f->data, of->length); - if (nf->data == NGX_CHAIN_ERROR) { + f->data = ngx_quic_read_chain(c, &nf->data, of->length); + if (f->data == NGX_CHAIN_ERROR) { return NGX_ERROR; } @@ -254,37 +250,48 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) } -static ngx_chain_t * -ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) +ngx_chain_t * +ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) { - size_t n; + off_t n; ngx_buf_t *b; - ngx_chain_t *out; + ngx_chain_t *out, *in, *cl, **ll; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - while (in) { - n = ngx_buf_size(in->buf); + out = *chain; + + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; - if (n == len) { - out = in->next; - in->next = NULL; - return out; + if (b->sync) { + /* hole */ + break; } - if (n > len) { + if (limit == 0) { break; } - len -= n; - in = in->next; - } + n = b->last - b->pos; - if (in == NULL) { - return NULL; + if (n > limit) { + goto split; + } + + limit -= n; } + *chain = *ll; + *ll = NULL; + + return out; + +split: + + in = *ll; + /* split in->buf by creating shadow bufs which reference it */ if (in->buf->shadow == NULL) { @@ -305,8 +312,8 @@ ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) in->buf = b; } - out = ngx_alloc_chain_link(c->pool); - if (out == NULL) { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { return NGX_CHAIN_ERROR; } @@ -317,21 +324,23 @@ ngx_quic_split_bufs(ngx_connection_t *c, ngx_chain_t *in, size_t len) } else { b = ngx_alloc_buf(c->pool); if (b == NULL) { - ngx_free_chain(c->pool, out); + ngx_free_chain(c->pool, cl); return NGX_CHAIN_ERROR; } } - out->buf = b; - out->next = in->next; + cl->buf = b; + cl->next = in->next; in->next = NULL; + *chain = cl; *b = *in->buf; b->last_shadow = 0; - b->pos = b->pos + len; + b->pos += limit; in->buf->shadow = b; - in->buf->last = in->buf->pos + len; + in->buf->last = b->pos; + in->buf->last_buf = 0; return out; } @@ -480,8 +489,8 @@ done: } -ngx_int_t -ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, +ngx_chain_t * +ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, off_t limit, off_t offset) { off_t n; @@ -490,18 +499,18 @@ ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, ngx_chain_t *cl, *sl; while (in && limit) { - cl = *out; + cl = *chain; if (cl == NULL) { cl = ngx_quic_alloc_buf(c); if (cl == NULL) { - return NGX_ERROR; + return NGX_CHAIN_ERROR; } cl->buf->last = cl->buf->end; cl->buf->sync = 1; /* hole */ cl->next = NULL; - *out = cl; + *chain = cl; } b = cl->buf; @@ -509,17 +518,25 @@ ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, if (n <= offset) { offset -= n; - out = &cl->next; + chain = &cl->next; continue; } if (b->sync && offset > 0) { - sl = ngx_quic_split_bufs(c, cl, offset); - if (sl == NGX_CHAIN_ERROR) { - return NGX_ERROR; + /* split hole at offset */ + + b->sync = 0; + + sl = ngx_quic_read_chain(c, &cl, offset); + if (cl == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; } - cl->next = sl; + sl->buf->sync = 1; + cl->buf->sync = 1; + + *chain = sl; + sl->next = cl; continue; } @@ -541,18 +558,29 @@ ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, ngx_chain_t *in, } } + if (b->sync && p == b->last) { + b->sync = 0; + continue; + } + if (b->sync && p != b->pos) { - sl = ngx_quic_split_bufs(c, cl, p - b->pos); + /* split hole at p - b->pos */ + + b->sync = 0; + + sl = ngx_quic_read_chain(c, &cl, p - b->pos); if (sl == NGX_CHAIN_ERROR) { - return NGX_ERROR; + return NGX_CHAIN_ERROR; } - cl->next = sl; - cl->buf->sync = 0; + cl->buf->sync = 1; + + *chain = sl; + sl->next = cl; } } - return NGX_OK; + return in; } diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 1310f6dfd..19a5c1668 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -30,7 +30,9 @@ ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit); void ngx_quic_trim_bufs(ngx_chain_t *in, size_t size); void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); -ngx_int_t ngx_quic_order_bufs(ngx_connection_t *c, ngx_chain_t **out, +ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, + off_t limit); +ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, off_t limit, off_t offset); #if (NGX_DEBUG) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index d2f1237a5..dc87db41c 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -369,8 +369,14 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->offset > ctx->crypto_received) { - return ngx_quic_order_bufs(c, &ctx->crypto, frame->data, f->length, - f->offset - ctx->crypto_received); + if (ngx_quic_write_chain(c, &ctx->crypto, frame->data, f->length, + f->offset - ctx->crypto_received) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + + return NGX_OK; } ngx_quic_trim_bufs(frame->data, ctx->crypto_received - f->offset); diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index d5facc270..d2984a345 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -732,9 +732,9 @@ ngx_quic_empty_handler(ngx_event_t *ev) static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) { - ssize_t len, n; + ssize_t len; ngx_buf_t *b; - ngx_chain_t *cl, **ll; + ngx_chain_t *cl, *in; ngx_event_t *rev; ngx_connection_t *pc; ngx_quic_stream_t *qs; @@ -764,33 +764,20 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_AGAIN; } - len = 0; - cl = qs->in; - - for (ll = &cl; *ll; ll = &(*ll)->next) { - b = (*ll)->buf; - - if (b->sync) { - /* hole */ - break; - } - - n = ngx_min(b->last - b->pos, (ssize_t) size); - buf = ngx_cpymem(buf, b->pos, n); + in = ngx_quic_read_chain(pc, &qs->in, size); + if (in == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } - len += n; - size -= n; - b->pos += n; + len = 0; - if (b->pos != b->last) { - break; - } + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = ngx_cpymem(buf, b->pos, b->last - b->pos); } - qs->in = *ll; - *ll = NULL; - - ngx_quic_free_bufs(pc, cl); + ngx_quic_free_bufs(pc, in); if (qs->in == NULL) { rev->ready = rev->pending_eof; @@ -837,10 +824,9 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - size_t n, flow; - ngx_buf_t *b; + off_t n, flow; ngx_event_t *wev; - ngx_chain_t *out, **ll; + ngx_chain_t *out, *cl; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -861,38 +847,35 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) return in; } - n = (limit && (size_t) limit < flow) ? (size_t) limit : flow; - - if (ngx_quic_order_bufs(pc, &qs->out, in, n, 0) != NGX_OK) { - return NGX_CHAIN_ERROR; + if (limit == 0 || limit > flow) { + limit = flow; } n = 0; - out = qs->out; - - for (ll = &out; *ll; ll = &(*ll)->next) { - b = (*ll)->buf; - if (b->sync) { - /* hole */ + for (cl = in; cl; cl = cl->next) { + n += cl->buf->last - cl->buf->pos; + if (n >= limit) { + n = limit; break; } + } - n += b->last - b->pos; + in = ngx_quic_write_chain(pc, &qs->out, in, n, 0); + if (in == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; } - qs->out = *ll; - *ll = NULL; + out = ngx_quic_read_chain(pc, &qs->out, n); + if (out == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_CHAIN_ERROR; } - while (in && ngx_buf_size(in->buf) == 0) { - in = in->next; - } - frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_STREAM; frame->data = out; @@ -909,7 +892,9 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) ngx_quic_queue_frame(qc, frame); - wev->ready = (n < flow) ? 1 : 0; + if (in) { + wev->ready = 0; + } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send_chain sent:%uz", n); @@ -1113,9 +1098,9 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->final_size = last; } - if (ngx_quic_order_bufs(c, &qs->in, frame->data, f->length, - f->offset - qs->recv_offset) - != NGX_OK) + if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, + f->offset - qs->recv_offset) + == NGX_CHAIN_ERROR) { return NGX_ERROR; } -- cgit v1.2.3 From 4d79f94221db9959a4bb3fee6fb3fc21d52266e8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 16 Dec 2021 17:06:35 +0300 Subject: QUIC: renamed buffer-related functions. ngx_quic_alloc_buf() -> ngx_quic_alloc_chain(), ngx_quic_free_bufs() -> ngx_quic_free_chain(), ngx_quic_trim_bufs() -> ngx_quic_trim_chain() --- src/event/quic/ngx_event_quic.c | 2 +- src/event/quic/ngx_event_quic_frames.c | 16 ++++++++-------- src/event/quic/ngx_event_quic_frames.h | 6 +++--- src/event/quic/ngx_event_quic_output.c | 2 +- src/event/quic/ngx_event_quic_ssl.c | 6 +++--- src/event/quic/ngx_event_quic_streams.c | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 4a9526d61..f03e9a898 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1085,7 +1085,7 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ctx = ngx_quic_get_send_ctx(qc, level); - ngx_quic_free_bufs(c, ctx->crypto); + ngx_quic_free_chain(c, ctx->crypto); while (!ngx_queue_empty(&ctx->sent)) { q = ngx_queue_head(&ctx->sent); diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index fe6d98043..d4b0cf1c0 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -66,7 +66,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) qc = ngx_quic_get_connection(c); if (frame->data) { - ngx_quic_free_bufs(c, frame->data); + ngx_quic_free_chain(c, frame->data); } ngx_queue_insert_head(&qc->free_frames, &frame->queue); @@ -79,7 +79,7 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) void -ngx_quic_trim_bufs(ngx_chain_t *in, size_t size) +ngx_quic_trim_chain(ngx_chain_t *in, size_t size) { size_t n; ngx_buf_t *b; @@ -99,7 +99,7 @@ ngx_quic_trim_bufs(ngx_chain_t *in, size_t size) void -ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in) +ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) { ngx_buf_t *b, *shadow; ngx_chain_t *cl; @@ -347,7 +347,7 @@ split: ngx_chain_t * -ngx_quic_alloc_buf(ngx_connection_t *c) +ngx_quic_alloc_chain(ngx_connection_t *c) { ngx_buf_t *b; ngx_chain_t *cl; @@ -381,7 +381,7 @@ ngx_quic_alloc_buf(ngx_connection_t *c) return NULL; } - b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_chain; cl->buf = b; @@ -407,7 +407,7 @@ ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) ll = &out; while (len) { - cl = ngx_quic_alloc_buf(c); + cl = ngx_quic_alloc_chain(c); if (cl == NULL) { return NGX_CHAIN_ERROR; } @@ -446,7 +446,7 @@ ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) continue; } - cl = ngx_quic_alloc_buf(c); + cl = ngx_quic_alloc_chain(c); if (cl == NULL) { return NGX_CHAIN_ERROR; } @@ -502,7 +502,7 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, cl = *chain; if (cl == NULL) { - cl = ngx_quic_alloc_buf(c); + cl = ngx_quic_alloc_chain(c); if (cl == NULL) { return NGX_CHAIN_ERROR; } diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 19a5c1668..671b6e649 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -23,13 +23,13 @@ void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len); -ngx_chain_t *ngx_quic_alloc_buf(ngx_connection_t *c); +ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit); -void ngx_quic_trim_bufs(ngx_chain_t *in, size_t size); -void ngx_quic_free_bufs(ngx_connection_t *c, ngx_chain_t *in); +void ngx_quic_trim_chain(ngx_chain_t *in, size_t size); +void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit); ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 57e124228..5599cdeb1 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1121,7 +1121,7 @@ ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) left = b ? b->end - b->last : 0; if (left < len) { - cl = ngx_quic_alloc_buf(c); + cl = ngx_quic_alloc_chain(c); if (cl == NULL) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index dc87db41c..5cf579cb1 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -379,13 +379,13 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - ngx_quic_trim_bufs(frame->data, ctx->crypto_received - f->offset); + ngx_quic_trim_chain(frame->data, ctx->crypto_received - f->offset); if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { return NGX_ERROR; } - ngx_quic_trim_bufs(ctx->crypto, last - ctx->crypto_received); + ngx_quic_trim_chain(ctx->crypto, last - ctx->crypto_received); ctx->crypto_received = last; cl = ctx->crypto; @@ -413,7 +413,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - ngx_quic_free_bufs(c, cl); + ngx_quic_free_chain(c, cl); } return NGX_OK; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index d2984a345..6b843302d 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -777,7 +777,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) buf = ngx_cpymem(buf, b->pos, b->last - b->pos); } - ngx_quic_free_bufs(pc, in); + ngx_quic_free_chain(pc, in); if (qs->in == NULL) { rev->ready = rev->pending_eof; @@ -975,8 +975,8 @@ ngx_quic_stream_cleanup_handler(void *data) "quic stream id:0x%xL cleanup", qs->id); ngx_rbtree_delete(&qc->streams.tree, &qs->node); - ngx_quic_free_bufs(pc, qs->in); - ngx_quic_free_bufs(pc, qs->out); + ngx_quic_free_chain(pc, qs->in); + ngx_quic_free_chain(pc, qs->out); if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ @@ -1079,7 +1079,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->offset < qs->recv_offset) { - ngx_quic_trim_bufs(frame->data, qs->recv_offset - f->offset); + ngx_quic_trim_chain(frame->data, qs->recv_offset - f->offset); f->offset = qs->recv_offset; } -- cgit v1.2.3 From 703be8c8f6db385b76c0c8b394cc3b2220639616 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 16 Dec 2021 17:07:11 +0300 Subject: QUIC: removed ngx_quic_copy_chain(). The function is unused. --- src/event/quic/ngx_event_quic_frames.c | 59 ---------------------------------- src/event/quic/ngx_event_quic_frames.h | 2 -- 2 files changed, 61 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index d4b0cf1c0..98400ea87 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -430,65 +430,6 @@ ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) } -ngx_chain_t * -ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, size_t limit) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (in) { - if (!ngx_buf_in_memory(in->buf) || ngx_buf_size(in->buf) == 0) { - in = in->next; - continue; - } - - cl = ngx_quic_alloc_chain(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - *ll = cl; - ll = &cl->next; - - b = cl->buf; - - while (in && b->last != b->end) { - - n = ngx_min(in->buf->last - in->buf->pos, b->end - b->last); - - if (limit > 0 && n > limit) { - n = limit; - } - - b->last = ngx_cpymem(b->last, in->buf->pos, n); - - in->buf->pos += n; - if (in->buf->pos == in->buf->last) { - in = in->next; - } - - if (limit > 0) { - if (limit == n) { - goto done; - } - - limit -= n; - } - } - } - -done: - - *ll = NULL; - - return out; -} - - ngx_chain_t * ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, off_t limit, off_t offset) diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 671b6e649..45505601f 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -26,8 +26,6 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); -ngx_chain_t *ngx_quic_copy_chain(ngx_connection_t *c, ngx_chain_t *in, - size_t limit); void ngx_quic_trim_chain(ngx_chain_t *in, size_t size); void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, -- cgit v1.2.3 From cd278da5e76682bee2126354e0ad7bbb66db4aa8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 24 Dec 2021 18:39:22 +0300 Subject: QUIC: refactored buffer allocation, spliting and freeing. Previously, buffer lists was used to track used buffers. Now reference counter is used instead. The new implementation is simpler and faster with many buffer clones. --- src/event/quic/ngx_event_quic_connection.h | 3 +- src/event/quic/ngx_event_quic_frames.c | 284 +++++++++++++++++------------ 2 files changed, 167 insertions(+), 120 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 73268ea0a..dfd29fce5 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -225,12 +225,13 @@ struct ngx_quic_connection_s { ngx_uint_t pto_count; ngx_queue_t free_frames; - ngx_chain_t *free_bufs; + ngx_buf_t *free_bufs; ngx_buf_t *free_shadow_bufs; ngx_uint_t nframes; #ifdef NGX_QUIC_DEBUG_ALLOC ngx_uint_t nbufs; + ngx_uint_t nshadowbufs; #endif ngx_quic_streams_t streams; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 98400ea87..ac9e38b76 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -12,6 +12,152 @@ #define NGX_QUIC_BUFFER_SIZE 4096 +#define ngx_quic_buf_refs(b) (b)->shadow->num +#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++ +#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)-- +#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v + + +static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); + + +static ngx_buf_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + u_char *p; + ngx_buf_t *b; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + b = qc->free_bufs; + + if (b) { + qc->free_bufs = b->shadow; + p = b->start; + + } else { + b = qc->free_shadow_bufs; + + if (b) { + qc->free_shadow_bufs = b->shadow; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic use shadow buffer n:%ui %ui", + ++qc->nbufs, --qc->nshadowbufs); +#endif + + } else { + b = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new buffer n:%ui", ++qc->nbufs); +#endif + } + + p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE); + if (p == NULL) { + return NULL; + } + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); +#endif + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + b->temporary = 1; + b->shadow = b; + + b->start = p; + b->pos = p; + b->last = p; + b->end = p + NGX_QUIC_BUFFER_SIZE; + + ngx_quic_buf_set_refs(b, 1); + + return b; +} + + +static void +ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *shadow; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_quic_buf_dec_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer %p r:%ui", + b, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + shadow = b->shadow; + + if (ngx_quic_buf_refs(b) == 0) { + shadow->shadow = qc->free_bufs; + qc->free_bufs = shadow; + } + + if (b != shadow) { + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + } + +} + + +static ngx_buf_t * +ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *nb; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + nb = qc->free_shadow_bufs; + + if (nb) { + qc->free_shadow_bufs = nb->shadow; + + } else { + nb = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (nb == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new shadow buffer n:%ui", ++qc->nshadowbufs); +#endif + } + + *nb = *b; + + ngx_quic_buf_inc_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clone buffer %p %p r:%ui", + b, nb, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + return nb; +} + ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c) @@ -101,47 +247,14 @@ ngx_quic_trim_chain(ngx_chain_t *in, size_t size) void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) { - ngx_buf_t *b, *shadow; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); + ngx_chain_t *cl; while (in) { -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic free buffer n:%ui", qc->nbufs); -#endif - cl = in; in = in->next; - b = cl->buf; - if (b->shadow) { - if (!b->last_shadow) { - b->recycled = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - do { - shadow = b->shadow; - b->shadow = qc->free_shadow_bufs; - qc->free_shadow_bufs = b; - b = shadow; - } while (b->recycled); - - if (b->shadow) { - b->last_shadow = 1; - ngx_free_chain(c->pool, cl); - continue; - } - - cl->buf = b; - } - - cl->next = qc->free_bufs; - qc->free_bufs = cl; + ngx_quic_free_buf(c, cl->buf); + ngx_free_chain(c->pool, cl); } } @@ -253,12 +366,9 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) ngx_chain_t * ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) { - off_t n; - ngx_buf_t *b; - ngx_chain_t *out, *in, *cl, **ll; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); + off_t n; + ngx_buf_t *b; + ngx_chain_t *out, *cl, **ll; out = *chain; @@ -290,57 +400,24 @@ ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) split: - in = *ll; - - /* split in->buf by creating shadow bufs which reference it */ - - if (in->buf->shadow == NULL) { - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - return NGX_CHAIN_ERROR; - } - } - - *b = *in->buf; - b->shadow = in->buf; - b->last_shadow = 1; - in->buf = b; - } - cl = ngx_alloc_chain_link(c->pool); if (cl == NULL) { return NGX_CHAIN_ERROR; } - if (qc->free_shadow_bufs) { - b = qc->free_shadow_bufs; - qc->free_shadow_bufs = b->shadow; - - } else { - b = ngx_alloc_buf(c->pool); - if (b == NULL) { - ngx_free_chain(c->pool, cl); - return NGX_CHAIN_ERROR; - } + cl->buf = ngx_quic_clone_buf(c, b); + if (cl->buf == NULL) { + return NGX_CHAIN_ERROR; } - cl->buf = b; - cl->next = in->next; - in->next = NULL; - *chain = cl; - - *b = *in->buf; - b->last_shadow = 0; - b->pos += limit; + cl->buf->pos += limit; + b->last = cl->buf->pos; + b->last_buf = 0; - in->buf->shadow = b; - in->buf->last = b->pos; - in->buf->last_buf = 0; + ll = &(*ll)->next; + cl->next = *ll; + *ll = NULL; + *chain = cl; return out; } @@ -349,49 +426,18 @@ split: ngx_chain_t * ngx_quic_alloc_chain(ngx_connection_t *c) { - ngx_buf_t *b; - ngx_chain_t *cl; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - if (qc->free_bufs) { - cl = qc->free_bufs; - qc->free_bufs = cl->next; - - b = cl->buf; - b->pos = b->start; - b->last = b->start; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic reuse buffer n:%ui", qc->nbufs); -#endif - - return cl; - } + ngx_chain_t *cl; cl = ngx_alloc_chain_link(c->pool); if (cl == NULL) { return NULL; } - b = ngx_create_temp_buf(c->pool, NGX_QUIC_BUFFER_SIZE); - if (b == NULL) { + cl->buf = ngx_quic_alloc_buf(c); + if (cl->buf == NULL) { return NULL; } - b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_chain; - - cl->buf = b; - -#ifdef NGX_QUIC_DEBUG_ALLOC - ++qc->nbufs; - - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic alloc buffer n:%ui", qc->nbufs); -#endif - return cl; } -- cgit v1.2.3 From bef80e70f6c4d4e7ace9f41eef0ff17a3a9120d0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 28 Dec 2021 13:24:58 +0300 Subject: QUIC: fixed config test with bpf (ticket #2292). The SO_REUSEPORT socket option is not set during configuration testing, thus making the further module initialization impossible and meaningless. --- src/event/quic/ngx_event_quic_bpf.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c index 848a64d59..ab024ad56 100644 --- a/src/event/quic/ngx_event_quic_bpf.c +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -130,6 +130,14 @@ ngx_quic_bpf_module_init(ngx_cycle_t *cycle) ngx_pool_cleanup_t *cln; ngx_quic_bpf_conf_t *bcf; + if (ngx_test_config) { + /* + * during config test, SO_REUSEPORT socket option is + * not set, thus making further processing meaningless + */ + return NGX_OK; + } + ccf = ngx_core_get_conf(cycle); bcf = ngx_quic_bpf_get_conf(cycle); -- cgit v1.2.3 From cf96432910430b4be66d333d17a32c0f481cd942 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 28 Dec 2021 13:50:01 +0300 Subject: QUIC: fixed format specifier after 6ccf3867959a. --- src/event/quic/ngx_event_quic_streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 6b843302d..989c33119 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -897,7 +897,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz", n); + "quic send_chain sent:%O", n); return in; } -- cgit v1.2.3 From 3662e0c83b41f0dceb0d7deac344cb9e38460476 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Dec 2021 16:15:28 +0300 Subject: QUIC: renamed input handling functions. Now these functions have names ngx_quic_handle_XXX(): - ngx_quic_process_stateless_reset() -> ngx_quic_handle_stateless_reset() - ngx_quic_input() -> ngx_quic_handle_datagram() - ngx_quic_process_packet() -> ngx_quic_handle_packet() - ngx_quic_process_payload() -> ngx_quic_handle_payload() --- src/event/quic/ngx_event_quic.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index f03e9a898..96488c6a7 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -12,18 +12,18 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, +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 ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); static void ngx_quic_close_timer_handler(ngx_event_t *ev); -static ngx_int_t ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, +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_process_packet(ngx_connection_t *c, +static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); -static ngx_int_t ngx_quic_process_payload(ngx_connection_t *c, +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, ngx_quic_header_t *pkt); @@ -208,7 +208,7 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); - rc = ngx_quic_input(c, c->buffer, conf); + rc = ngx_quic_handle_datagram(c, c->buffer, conf); if (rc != NGX_OK) { ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); return; @@ -350,7 +350,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, static ngx_int_t -ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *tail, ch; ngx_uint_t i; @@ -437,7 +437,7 @@ ngx_quic_input_handler(ngx_event_t *rev) b = c->udp->dgram->buffer; - rc = ngx_quic_input(c, b, NULL); + rc = ngx_quic_handle_datagram(c, b, NULL); if (rc == NGX_ERROR) { ngx_quic_close_connection(c, NGX_ERROR); @@ -666,7 +666,8 @@ ngx_quic_close_timer_handler(ngx_event_t *ev) static ngx_int_t -ngx_quic_input(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, + ngx_quic_conf_t *conf) { size_t size; u_char *p, *start; @@ -692,7 +693,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) pkt.flags = p[0]; pkt.raw->pos++; - rc = ngx_quic_process_packet(c, conf, &pkt); + rc = ngx_quic_handle_packet(c, conf, &pkt); #if (NGX_DEBUG) if (pkt.parsed) { @@ -769,7 +770,7 @@ ngx_quic_input(ngx_connection_t *c, ngx_buf_t *b, ngx_quic_conf_t *conf) static ngx_int_t -ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, +ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; @@ -841,10 +842,10 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } - rc = ngx_quic_process_payload(c, pkt); + rc = ngx_quic_handle_payload(c, pkt); if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { - if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) { + if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic stateless reset packet detected"); @@ -935,12 +936,12 @@ ngx_quic_process_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, return NGX_ERROR; } - return ngx_quic_process_payload(c, pkt); + return ngx_quic_handle_payload(c, pkt); } static ngx_int_t -ngx_quic_process_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) { ngx_int_t rc; ngx_quic_send_ctx_t *ctx; -- cgit v1.2.3 From 05a32b5ec44e8fb54ea1976ec025a6ea650c36f9 Mon Sep 17 00:00:00 2001 From: Ruslan Ermilov Date: Tue, 28 Dec 2021 15:01:02 +0300 Subject: Fixed a mismerge in 5c86189a1c1b. --- src/http/ngx_http.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index a5bdeaadd..91731c812 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -176,6 +176,7 @@ ngx_int_t ngx_http_set_default_types(ngx_conf_t *cf, ngx_array_t **types, ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #endif + #if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); @@ -183,6 +184,7 @@ size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, ngx_uint_t lower); #endif + extern ngx_module_t ngx_http_module; extern ngx_str_t ngx_http_html_default_types[]; -- cgit v1.2.3 From fa21bf0cc7ba2d94f66a061d644163547d79e6a2 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 27 Dec 2021 13:49:56 +0300 Subject: QUIC: got rid of ngx_quic_create_temp_socket(). It was mostly copy of the ngx_quic_listen(). Now ngx_quic_listen() no longer generates server id and increments seqnum. Instead, the server id is generated when the socket is created. The ngx_quic_alloc_socket() function is renamed to ngx_quic_create_socket(). --- src/event/quic/ngx_event_quic_connid.c | 2 +- src/event/quic/ngx_event_quic_socket.c | 88 +++++++++++----------------------- src/event/quic/ngx_event_quic_socket.h | 2 +- 3 files changed, 29 insertions(+), 63 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index d87948021..9f2f7ab33 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -497,7 +497,7 @@ ngx_quic_create_sockets(ngx_connection_t *c) while (qc->nsockets < n) { - qsock = ngx_quic_alloc_socket(c, qc); + qsock = ngx_quic_create_socket(c, qc); if (qsock == NULL) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index a04ad6202..426e1ebae 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -10,17 +10,12 @@ #include -static ngx_int_t ngx_quic_create_temp_socket(ngx_connection_t *c, - ngx_quic_connection_t *qc, ngx_str_t *dcid, ngx_quic_path_t *path, - ngx_quic_client_id_t *cid); - - ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { ngx_quic_path_t *path; - ngx_quic_socket_t *qsock; + ngx_quic_socket_t *qsock, *tmp; ngx_quic_client_id_t *cid; /* @@ -45,13 +40,13 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, return NGX_ERROR; } - /* socket to use for further processing */ - qsock = ngx_quic_alloc_socket(c, qc); + /* 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 (autogenerated) */ + /* socket is listening at new server id */ if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { return NGX_ERROR; } @@ -88,10 +83,22 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, /* now bind socket to client and path */ ngx_quic_connect(c, qsock, path, cid); - if (ngx_quic_create_temp_socket(c, qc, &pkt->odcid, path, cid) != NGX_OK) { + tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (tmp == NULL) { + goto failed; + } + + tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ + + ngx_memcpy(tmp->sid.id, pkt->odcid.data, pkt->odcid.len); + tmp->sid.len = pkt->odcid.len; + + if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { goto failed; } + ngx_quic_connect(c, tmp, path, cid); + /* use this socket as default destination */ qc->socket = qsock; @@ -111,48 +118,8 @@ failed: } -static ngx_int_t -ngx_quic_create_temp_socket(ngx_connection_t *c, ngx_quic_connection_t *qc, - ngx_str_t *dcid, ngx_quic_path_t *path, ngx_quic_client_id_t *cid) -{ - ngx_str_t id; - ngx_quic_socket_t *qsock; - ngx_quic_server_id_t *sid; - - qsock = ngx_quic_alloc_socket(c, qc); - if (qsock == NULL) { - return NGX_ERROR; - } - - sid = &qsock->sid; - - sid->seqnum = NGX_QUIC_UNSET_PN; /* mark socket as temporary */ - - sid->len = dcid->len; - ngx_memcpy(sid->id, dcid->data, dcid->len); - - id.len = sid->len; - id.data = sid->id; - - ngx_insert_udp_connection(c, &qsock->udp, &id); - - 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 #%L listening at sid:%xV nsock:%ui", - (int64_t) sid->seqnum, &id, qc->nsockets); - - ngx_quic_connect(c, qsock, path, cid); - - return NGX_OK; -} - - ngx_quic_socket_t * -ngx_quic_alloc_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) +ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) { ngx_queue_t *q; ngx_quic_socket_t *sock; @@ -174,6 +141,13 @@ ngx_quic_alloc_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) } } + sock->sid.len = NGX_QUIC_SERVER_CID_LEN; + if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { + return NULL; + } + + sock->sid.seqnum = qc->server_seqnum++; + return sock; } @@ -236,14 +210,6 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, sid = &qsock->sid; - sid->len = NGX_QUIC_SERVER_CID_LEN; - - if (ngx_quic_create_server_id(c, sid->id) != NGX_OK) { - return NGX_ERROR; - } - - sid->seqnum = qc->server_seqnum++; - id.data = sid->id; id.len = sid->len; @@ -255,8 +221,8 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, qsock->quic = qc; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket #%uL listening at sid:%xV nsock:%ui", - sid->seqnum, &id, qc->nsockets); + "quic socket #%L listening at sid:%xV nsock:%ui", + (int64_t) sid->seqnum, &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 72ed67ad0..51f01a1fa 100644 --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -16,7 +16,7 @@ ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); void ngx_quic_close_sockets(ngx_connection_t *c); -ngx_quic_socket_t *ngx_quic_alloc_socket(ngx_connection_t *c, +ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc); ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_socket_t *qsock); -- cgit v1.2.3 From 7f0fdd4e149380c9439e63bea7625c5071781af6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 29 Dec 2021 15:33:51 +0300 Subject: Style. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6faa3ee0b..b97caec95 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -628,7 +628,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } } - if (name->len && name->data[0] == ':') { + if (name->len && name->data[0] == ':') { return ngx_http_v3_process_pseudo_header(r, name, value); } -- cgit v1.2.3 From b1356ade078a8e4092943a2b7b5d3f92dd4def93 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 30 Dec 2021 12:59:32 +0300 Subject: HTTP/3: improved processing of multiple Cookie field lines. As per draft-ietf-quic-http, 4.1.1.2, and similar to HTTP/2 specification, they ought to be concatenated. This closely follows ngx_http_v2_module. --- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_request.c | 165 ++++++++++++++++++++++++++++++++++---- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index b7951e9bb..956c824a2 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -123,6 +123,7 @@ struct ngx_http_v3_parse_s { size_t header_limit; ngx_http_v3_parse_headers_t headers; ngx_http_v3_parse_data_t body; + ngx_array_t *cookies; }; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b97caec95..4dbda3596 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -25,6 +25,8 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, @@ -601,6 +603,8 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + static ngx_str_t cookie = ngx_string("cookie"); + len = name->len + value->len; if (len > r->v3_parse->header_limit) { @@ -636,24 +640,34 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_ERROR; } - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } + 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; + } - h->key = *name; - h->value = *value; - h->lowcase_key = h->key.data; - h->hash = ngx_hash_key(h->key.data, h->key.len); + } 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; + } - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -981,6 +995,10 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return NGX_ERROR; + } + if (r->headers_in.server.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent neither \":authority\" nor \"Host\" header"); @@ -1056,6 +1074,125 @@ failed: } +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->v3_parse->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + *val = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r) { -- cgit v1.2.3 From 109166e4fa41cb35a91107b0be075aec22a567eb Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 12 Jan 2022 11:54:39 +0300 Subject: QUIC: modified HTTP version test. The new condition produces smaller diff to the default branch and is similar to HTTP/2 case. --- src/http/ngx_http_request_body.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index d2bec7820..afb042395 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -942,7 +942,14 @@ ngx_http_test_expect(ngx_http_request_t *r) if (r->expect_tested || r->headers_in.expect == NULL - || r->http_version != NGX_HTTP_VERSION_11) + || r->http_version < NGX_HTTP_VERSION_11 +#if (NGX_HTTP_V2) + || r->stream != NULL +#endif +#if (NGX_HTTP_V3) + || r->connection->quic != NULL +#endif + ) { return NGX_OK; } -- cgit v1.2.3 From 5ab94d4219fb2119770d024731a53dba6871b25c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 12 Jan 2022 11:57:06 +0300 Subject: HTTP/3: simplified code. --- src/http/ngx_http_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 12a8cd144..fd3e880f8 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2970,7 +2970,7 @@ ngx_http_test_reading(ngx_http_request_t *r) #if (NGX_HTTP_V3) if (c->quic) { - if (c->read->error) { + if (rev->error) { err = 0; goto closed; } -- cgit v1.2.3 From 38cfe35779e4a8a6288d61bdf4a2e892dc0ce046 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 12 Jan 2022 11:57:46 +0300 Subject: HTTP/3: set c->error on read error in ngx_http_test_reading(). Similar to other error/eof cases. --- src/http/ngx_http_request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index fd3e880f8..bc6c077db 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2971,6 +2971,7 @@ ngx_http_test_reading(ngx_http_request_t *r) if (c->quic) { if (rev->error) { + c->error = 1; err = 0; goto closed; } -- cgit v1.2.3 From a6120a9bc5a50326889755d740be457f379bb215 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 11 Jan 2022 18:57:02 +0300 Subject: QUIC: fixed handling STREAM FIN. Previously, when a STREAM FIN frame with no data bytes was received after all prior stream data were already read by the application layer, the frame was ignored and eof was not reported to the application. --- src/event/quic/ngx_event_quic_streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 989c33119..a74a43c43 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1074,7 +1074,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - if (last <= qs->recv_offset) { + if (last < qs->recv_offset) { return NGX_OK; } -- cgit v1.2.3 From 35fc2eb247cfb199a19dd1085c693c9cdad41cce Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 13 Jan 2022 11:23:53 +0300 Subject: QUIC: fixed handling stream input buffers. Previously, ngx_quic_write_chain() treated each input buffer as a memory buffer, which is not always the case. Special buffers were not skipped, which is especially important when hitting the input byte limit. The issue manifested itself with ngx_quic_write_chain() returning a non-empty chain consisting of a special last_buf buffer when called from QUIC stream send_chain(). In order for this to happen, input byte limit should be equal to the chain length, and the input chain should end with an empty last_buf buffer. An easy way to achieve this is the following: location /empty { return 200; } When this non-empty chain was returned from send_chain(), it signalled to the caller that input was blocked, while in fact it wasn't. This prevented HTTP request from finalization, which prevented QUIC from sending STREAM FIN to the client. The QUIC stream was then reset after a timeout. Now special buffers are skipped and send_chain() returns NULL in the case above, which signals to the caller a successful operation. Also, original byte limit is now passed to ngx_quic_write_chain() from send_chain() instead of actual chain length to make sure it's never zero. --- src/event/quic/ngx_event_quic_frames.c | 16 +++++++++++----- src/event/quic/ngx_event_quic_streams.c | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index ac9e38b76..89bd6d236 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -527,7 +527,17 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, continue; } - for (p = b->pos + offset; p != b->last && in && limit; /* void */ ) { + for (p = b->pos + offset; p != b->last && in; /* void */ ) { + + if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { + in = in->next; + continue; + } + + if (limit == 0) { + break; + } + n = ngx_min(b->last - p, in->buf->last - in->buf->pos); n = ngx_min(n, limit); @@ -539,10 +549,6 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, in->buf->pos += n; offset += n; limit -= n; - - if (in->buf->pos == in->buf->last) { - in = in->next; - } } if (b->sync && p == b->last) { diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index a74a43c43..6f6ab5f9e 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -861,7 +861,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) } } - in = ngx_quic_write_chain(pc, &qs->out, in, n, 0); + in = ngx_quic_write_chain(pc, &qs->out, in, limit, 0); if (in == NGX_CHAIN_ERROR) { return NGX_CHAIN_ERROR; } -- cgit v1.2.3 From a7a3a8cc1778fee8597b34ce5265453ceda81b08 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 13 Jan 2022 15:57:15 +0300 Subject: HTTP/3: removed useless warning regarding OpenSSL library. After 0e6528551f26, it became impossible to run into this path. --- src/http/ngx_http.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 4886a88bf..082a849a2 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1360,17 +1360,6 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, &lsopt->addr_text); } -#endif - -#if (NGX_HTTP_V3 && !defined NGX_QUIC) - - if (lsopt->http3) { - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "nginx was built with OpenSSL that lacks QUIC " - "support, HTTP/3 is not enabled for %V", - &lsopt->addr_text); - } - #endif addr = ngx_array_push(&port->addrs); -- cgit v1.2.3 From bd4a26c164bdc8a5707827915f4d16cd7ff35891 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 13 Jan 2022 15:57:21 +0300 Subject: QUIC: removed ngx_send_lowat() check for QUIC connections. After 9ae239d2547d, ngx_quic_handle_write_event() no longer runs into ngx_send_lowat() for QUIC connections, so the check became excessive. It is assumed that external modules operating with SO_SNDLOWAT (I'm not aware of any) should do this check on their own. --- src/event/ngx_event.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index aa47ccf9f..5b246c015 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -935,12 +935,6 @@ ngx_send_lowat(ngx_connection_t *c, size_t lowat) { int sndlowat; -#if (NGX_QUIC) - if (c->quic) { - return NGX_OK; - } -#endif - #if (NGX_HAVE_LOWAT_EVENT) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { -- cgit v1.2.3 From 2f28342e088b61e1605391ada79db703f047c5f2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 13 Jan 2022 16:56:07 +0300 Subject: README: documented QuicTLS support. --- README | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README b/README index 7ff4c0fb4..cf36dfceb 100644 --- a/README +++ b/README @@ -74,12 +74,18 @@ Experimental QUIC support for nginx $ hg clone -b quic https://hg.nginx.org/nginx-quic $ cd nginx-quic - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../boringssl/include" \ - --with-ld-opt="-L../boringssl/build/ssl \ + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../boringssl/include" \ + --with-ld-opt="-L../boringssl/build/ssl \ -L../boringssl/build/crypto" $ make + Alternatively, nginx can be configured with QuicTLS [9] + + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../quictls/build/include" \ + --with-ld-opt="-L../quictls/build/lib" + When configuring nginx, you can enable QUIC and HTTP/3 using the following new configuration options: @@ -252,3 +258,4 @@ Example configuration: [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf + [9] https://github.com/quictls/openssl -- cgit v1.2.3 From 2ba20e3451ca5f31ad7a9811081b85d92472082c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 13 Jan 2022 11:34:42 +0300 Subject: QUIC: return written size from ngx_quic_write_chain(). This allows to escape calculating it before calling the function. --- src/event/quic/ngx_event_quic_frames.c | 10 +++++++++- src/event/quic/ngx_event_quic_frames.h | 2 +- src/event/quic/ngx_event_quic_ssl.c | 2 +- src/event/quic/ngx_event_quic_streams.c | 19 +++++-------------- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 89bd6d236..55e58d329 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -478,13 +478,17 @@ ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) ngx_chain_t * ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, - off_t limit, off_t offset) + off_t limit, off_t offset, size_t *size) { off_t n; u_char *p; ngx_buf_t *b; ngx_chain_t *cl, *sl; + if (size) { + *size = 0; + } + while (in && limit) { cl = *chain; @@ -549,6 +553,10 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, in->buf->pos += n; offset += n; limit -= n; + + if (size) { + *size += n; + } } if (b->sync && p == b->last) { diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 45505601f..b06575d4e 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -31,7 +31,7 @@ void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit); ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, - ngx_chain_t *in, off_t limit, off_t offset); + ngx_chain_t *in, off_t limit, off_t offset, size_t *size); #if (NGX_DEBUG) void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 5cf579cb1..e5e3ffcab 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -370,7 +370,7 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, if (f->offset > ctx->crypto_received) { if (ngx_quic_write_chain(c, &ctx->crypto, frame->data, f->length, - f->offset - ctx->crypto_received) + f->offset - ctx->crypto_received, NULL) == NGX_CHAIN_ERROR) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 6f6ab5f9e..5863265a7 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -824,9 +824,10 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - off_t n, flow; + off_t flow; + size_t n; ngx_event_t *wev; - ngx_chain_t *out, *cl; + ngx_chain_t *out; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; @@ -851,17 +852,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) limit = flow; } - n = 0; - - for (cl = in; cl; cl = cl->next) { - n += cl->buf->last - cl->buf->pos; - if (n >= limit) { - n = limit; - break; - } - } - - in = ngx_quic_write_chain(pc, &qs->out, in, limit, 0); + in = ngx_quic_write_chain(pc, &qs->out, in, limit, 0, &n); if (in == NGX_CHAIN_ERROR) { return NGX_CHAIN_ERROR; } @@ -1099,7 +1090,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, - f->offset - qs->recv_offset) + f->offset - qs->recv_offset, NULL) == NGX_CHAIN_ERROR) { return NGX_ERROR; -- cgit v1.2.3 From 6844eeb9c9d9d978ec58c48a7fffc99b21bd7b20 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 16 Jan 2022 00:28:13 +0300 Subject: QUIC: fixed format specifier after 3789f4a56d65. --- src/event/quic/ngx_event_quic_streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 5863265a7..2016c62a1 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -888,7 +888,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%O", n); + "quic send_chain sent:%uz", n); return in; } -- cgit v1.2.3 From 8a67a56091a04055edee4d551e78beacaf79c17a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 17 Jan 2022 14:39:04 +0300 Subject: QUIC: introduced function ngx_quic_split_chain(). The function splits a buffer at given offset. The function is now called from ngx_quic_read_chain() and ngx_quic_write_chain(), which simplifies both functions. --- src/event/quic/ngx_event_quic_frames.c | 91 ++++++++++++++++------------------ 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 55e58d329..851af4643 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -21,6 +21,8 @@ static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, + off_t offset); static ngx_buf_t * @@ -159,6 +161,38 @@ ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) } +static ngx_int_t +ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset) +{ + ngx_buf_t *b, *tb; + ngx_chain_t *tail; + + b = cl->buf; + + tail = ngx_alloc_chain_link(c->pool); + if (tail == NULL) { + return NGX_ERROR; + } + + tb = ngx_quic_clone_buf(c, b); + if (tb == NULL) { + return NGX_ERROR; + } + + tail->buf = tb; + + tb->pos += offset; + + b->last = tb->pos; + b->last_buf = 0; + + tail->next = cl->next; + cl->next = tail; + + return NGX_OK; +} + + ngx_quic_frame_t * ngx_quic_alloc_frame(ngx_connection_t *c) { @@ -368,7 +402,7 @@ ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) { off_t n; ngx_buf_t *b; - ngx_chain_t *out, *cl, **ll; + ngx_chain_t *out, **ll; out = *chain; @@ -387,7 +421,11 @@ ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) n = b->last - b->pos; if (n > limit) { - goto split; + if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + n = limit; } limit -= n; @@ -396,29 +434,6 @@ ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) *chain = *ll; *ll = NULL; - return out; - -split: - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - cl->buf = ngx_quic_clone_buf(c, b); - if (cl->buf == NULL) { - return NGX_CHAIN_ERROR; - } - - cl->buf->pos += limit; - b->last = cl->buf->pos; - b->last_buf = 0; - - ll = &(*ll)->next; - cl->next = *ll; - *ll = NULL; - *chain = cl; - return out; } @@ -483,7 +498,7 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, off_t n; u_char *p; ngx_buf_t *b; - ngx_chain_t *cl, *sl; + ngx_chain_t *cl; if (size) { *size = 0; @@ -514,20 +529,10 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, } if (b->sync && offset > 0) { - /* split hole at offset */ - - b->sync = 0; - - sl = ngx_quic_read_chain(c, &cl, offset); - if (cl == NGX_CHAIN_ERROR) { + if (ngx_quic_split_chain(c, cl, offset) != NGX_OK) { return NGX_CHAIN_ERROR; } - sl->buf->sync = 1; - cl->buf->sync = 1; - - *chain = sl; - sl->next = cl; continue; } @@ -565,19 +570,11 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, } if (b->sync && p != b->pos) { - /* split hole at p - b->pos */ - - b->sync = 0; - - sl = ngx_quic_read_chain(c, &cl, p - b->pos); - if (sl == NGX_CHAIN_ERROR) { + if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) { return NGX_CHAIN_ERROR; } - cl->buf->sync = 1; - - *chain = sl; - sl->next = cl; + b->sync = 0; } } -- cgit v1.2.3 From 1f97aa71ecaa75dfd646495a13534b10405b500c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 18 Jan 2022 12:49:55 +0300 Subject: QUIC: the "quic_active_connection_id_limit" directive. The directive sets corresponding transport parameter and limits number of created client ids. --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_transport.c | 2 +- src/http/v3/ngx_http_v3_module.c | 12 ++++++++++++ src/stream/ngx_stream_quic_module.c | 13 +++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 9481fef62..195184754 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -40,6 +40,7 @@ typedef struct { size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; ngx_uint_t max_concurrent_streams_uni; + ngx_uint_t active_connection_id_limit; ngx_int_t stream_close_code; ngx_int_t stream_reject_code_uni; ngx_int_t stream_reject_code_bidi; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 0ff42de2d..949d2691b 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1974,7 +1974,7 @@ ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; - tp->active_connection_id_limit = 2; + tp->active_connection_id_limit = qcf->active_connection_id_limit; tp->disable_active_migration = qcf->disable_active_migration; return NGX_OK; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 455b613e1..d274a3bf2 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -104,6 +104,13 @@ static ngx_command_t ngx_http_v3_commands[] = { 0, NULL }, + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), + NULL }, + ngx_null_command }; @@ -240,6 +247,7 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->quic.gso_enabled = NGX_CONF_UNSET; h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; + h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; return h3scf; } @@ -280,6 +288,10 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); + ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, + prev->quic.active_connection_id_limit, + 2); + if (conf->quic.host_key.len == 0) { conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 34f1e18ef..0505df501 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -67,6 +67,13 @@ static ngx_command_t ngx_stream_quic_commands[] = { 0, NULL }, + { ngx_string("quic_active_connection_id_limit"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_quic_conf_t, active_connection_id_limit), + NULL }, + ngx_null_command }; @@ -176,6 +183,8 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) conf->retry = NGX_CONF_UNSET; conf->gso_enabled = NGX_CONF_UNSET; + conf->active_connection_id_limit = NGX_CONF_UNSET_UINT; + return conf; } @@ -204,6 +213,10 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); + ngx_conf_merge_uint_value(conf->active_connection_id_limit, + conf->active_connection_id_limit, + 2); + if (conf->host_key.len == 0) { conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; -- cgit v1.2.3 From 8a4a267d74fa31e4693691a1a8788b0773329481 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 19 Jan 2022 22:39:24 +0300 Subject: QUIC: reworked migration handling. The quic connection now holds active, backup and probe paths instead of sockets. The number of migration paths is now limited and cannot be inflated by a bad client or an attacker. The client id is now associated with path rather than socket. This allows to simplify processing of output and connection ids handling. New migration abandons any previously started migrations. This allows to free consumed client ids and request new for use in future migrations and make progress in case when connection id limit is hit during migration. A path now can be revalidated without losing its state. The patch also fixes various issues with NAT rebinding case handling: - paths are now validated (previously, there was no validation and paths were left in limited state) - attempt to reuse id on different path is now again verified (this was broken in 40445fc7c403) - former path is now validated in case of apparent migration --- src/event/quic/ngx_event_quic.c | 66 ++--- src/event/quic/ngx_event_quic_connection.h | 20 +- src/event/quic/ngx_event_quic_connid.c | 231 +++++------------ src/event/quic/ngx_event_quic_connid.h | 5 +- src/event/quic/ngx_event_quic_migration.c | 402 ++++++++++++++--------------- src/event/quic/ngx_event_quic_migration.h | 30 +-- src/event/quic/ngx_event_quic_output.c | 45 ++-- src/event/quic/ngx_event_quic_socket.c | 101 +------- src/event/quic/ngx_event_quic_socket.h | 4 - src/event/quic/ngx_event_quic_ssl.c | 9 +- src/event/quic/ngx_event_quic_transport.h | 2 + 11 files changed, 351 insertions(+), 564 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 96488c6a7..d47d0bc4e 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -131,8 +131,8 @@ ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) qc = ngx_quic_get_connection(c); - scid.data = qc->socket->cid->id; - scid.len = qc->socket->cid->len; + scid.data = qc->path->cid->id; + scid.len = qc->path->cid->len; if (scid.len != ctp->initial_scid.len || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) @@ -373,7 +373,7 @@ ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - if (cid->seqnum == 0 || cid->refcnt == 0) { + if (cid->seqnum == 0 || !cid->used) { /* * No stateless reset token in initial connection id. * Don't accept a token from an unused connection id. @@ -673,10 +673,12 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, u_char *p, *start; ngx_int_t rc; ngx_uint_t good; + ngx_quic_path_t *path; ngx_quic_header_t pkt; ngx_quic_connection_t *qc; good = 0; + path = NULL; size = b->last - b->pos; @@ -690,6 +692,7 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, pkt.len = b->last - p; pkt.log = c->log; pkt.first = (p == start) ? 1 : 0; + pkt.path = path; pkt.flags = p[0]; pkt.raw->pos++; @@ -720,6 +723,8 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, good = 1; } + path = pkt.path; /* preserve packet path from 1st packet */ + /* NGX_OK || NGX_DECLINED */ /* @@ -825,14 +830,15 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->first) { - if (ngx_quic_find_path(c, c->udp->dgram->sockaddr, - c->udp->dgram->socklen) - == NULL) + if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, + c->udp->dgram->socklen, + qc->path->sockaddr, qc->path->socklen, 1) + != NGX_OK) { /* packet comes from unknown path, possibly migration */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic too early migration attempt"); - return NGX_DECLINED; + return NGX_DONE; } } @@ -991,9 +997,12 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) pkt->decrypted = 1; - if (pkt->first) { - if (ngx_quic_update_paths(c, pkt) != NGX_OK) { - return NGX_ERROR; + c->log->action = "handling decrypted packet"; + + if (pkt->path == NULL) { + rc = ngx_quic_set_path(c, pkt); + if (rc != NGX_OK) { + return rc; } } @@ -1012,9 +1021,10 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) */ ngx_quic_discard_ctx(c, ssl_encryption_initial); - if (qc->socket->path->state != NGX_QUIC_PATH_VALIDATED) { - qc->socket->path->state = NGX_QUIC_PATH_VALIDATED; - qc->socket->path->limited = 0; + if (!qc->path->validated) { + qc->path->validated = 1; + qc->path->limited = 0; + ngx_quic_path_dbg(c, "in handshake", qc->path); ngx_post_event(&qc->push, &ngx_posted_events); } } @@ -1153,7 +1163,6 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_uint_t do_close, nonprobing; ngx_chain_t chain; ngx_quic_frame_t frame; - ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -1335,7 +1344,8 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) case NGX_QUIC_FT_PATH_CHALLENGE: - if (ngx_quic_handle_path_challenge_frame(c, &frame.u.path_challenge) + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) != NGX_OK) { return NGX_ERROR; @@ -1394,26 +1404,18 @@ ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_quic_close_connection(c, NGX_OK); } - qsock = ngx_quic_get_socket(c); + if (pkt->path != qc->path && nonprobing) { - if (qsock != qc->socket) { - - if (qsock->path != qc->socket->path && nonprobing) { - /* - * RFC 9000, 9.2. Initiating Connection Migration - * - * An endpoint can migrate a connection to a new local - * address by sending packets containing non-probing frames - * from that address. - */ - if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { - return NGX_ERROR; - } - } /* - * else: packet arrived via non-default socket; - * no reason to change active path + * RFC 9000, 9.2. Initiating Connection Migration + * + * An endpoint can migrate a connection to a new local + * address by sending packets containing non-probing frames + * from that address. */ + if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { + return NGX_ERROR; + } } if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index dfd29fce5..173af10d1 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -69,7 +69,7 @@ struct ngx_quic_client_id_s { size_t len; u_char id[NGX_QUIC_CID_LEN_MAX]; u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; - ngx_uint_t refcnt; + ngx_uint_t used; /* unsigned used:1; */ }; @@ -83,20 +83,22 @@ struct ngx_quic_server_id_s { struct ngx_quic_path_s { ngx_queue_t queue; struct sockaddr *sockaddr; + ngx_sockaddr_t sa; socklen_t socklen; - ngx_uint_t state; - ngx_uint_t limited; /* unsigned limited:1; */ + ngx_quic_client_id_t *cid; ngx_msec_t expires; - ngx_msec_t last_seen; ngx_uint_t tries; + ngx_uint_t tag; off_t sent; off_t received; u_char challenge1[8]; u_char challenge2[8]; - ngx_uint_t refcnt; uint64_t seqnum; ngx_str_t addr_text; u_char text[NGX_SOCKADDR_STRLEN]; + unsigned validated:1; + unsigned validating:1; + unsigned limited:1; }; @@ -104,11 +106,8 @@ struct ngx_quic_socket_s { ngx_udp_connection_t udp; ngx_quic_connection_t *quic; ngx_queue_t queue; - ngx_quic_server_id_t sid; - - ngx_quic_path_t *path; - ngx_quic_client_id_t *cid; + ngx_uint_t used; /* unsigned used:1; */ }; @@ -184,8 +183,7 @@ struct ngx_quic_send_ctx_s { struct ngx_quic_connection_s { uint32_t version; - ngx_quic_socket_t *socket; - ngx_quic_socket_t *backup; + ngx_quic_path_t *path; ngx_queue_t sockets; ngx_queue_t paths; diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 9f2f7ab33..32926a022 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -15,13 +15,10 @@ #if (NGX_QUIC_BPF) static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); #endif -static ngx_int_t ngx_quic_send_retire_connection_id(ngx_connection_t *c, - uint64_t seqnum); - +static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc); -static ngx_int_t ngx_quic_replace_retired_client_id(ngx_connection_t *c, - ngx_quic_client_id_t *retired_cid); static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid); @@ -77,9 +74,9 @@ ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, ngx_quic_new_conn_id_frame_t *f) { - uint64_t seq; ngx_str_t id; ngx_queue_t *q; + ngx_quic_frame_t *frame; ngx_quic_client_id_t *cid, *item; ngx_quic_connection_t *qc; @@ -97,10 +94,17 @@ ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, * done so for that sequence number. */ - if (ngx_quic_send_retire_connection_id(c, f->seqnum) != NGX_OK) { + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { return NGX_ERROR; } + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = f->seqnum; + + ngx_quic_queue_frame(qc, frame); + goto retire; } @@ -173,20 +177,7 @@ retire: continue; } - /* this connection id must be retired */ - seq = cid->seqnum; - - if (cid->refcnt) { - /* we are going to retire client id which is in use */ - if (ngx_quic_replace_retired_client_id(c, cid) != NGX_OK) { - return NGX_ERROR; - } - - } else { - ngx_quic_unref_client_id(c, cid); - } - - if (ngx_quic_send_retire_connection_id(c, seq) != NGX_OK) { + if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { return NGX_ERROR; } } @@ -213,25 +204,47 @@ done: static ngx_int_t -ngx_quic_send_retire_connection_id(ngx_connection_t *c, uint64_t seqnum) +ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) { - ngx_quic_frame_t *frame; + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_client_id_t *new_cid; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; + if (!cid->used) { + return ngx_quic_free_client_id(c, cid); } - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; - frame->u.retire_cid.sequence_number = seqnum; + /* we are going to retire client id which is in use */ - ngx_quic_queue_frame(qc, frame); + q = ngx_queue_head(&qc->paths); - /* we are no longer going to use this client id */ + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->cid != cid) { + continue; + } + + if (path == qc->path) { + /* this is the active path: update it with new CID */ + new_cid = ngx_quic_next_client_id(c); + if (new_cid == NULL) { + return NGX_ERROR; + } + + qc->path->cid = new_cid; + new_cid->used = 1; + + return ngx_quic_free_client_id(c, cid); + } + + return ngx_quic_free_path(c, path); + } return NGX_OK; } @@ -318,7 +331,7 @@ ngx_quic_next_client_id(ngx_connection_t *c) { cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); - if (cid->refcnt == 0) { + if (!cid->used) { return cid; } } @@ -327,42 +340,11 @@ ngx_quic_next_client_id(ngx_connection_t *c) } -ngx_quic_client_id_t * -ngx_quic_used_client_id(ngx_connection_t *c, ngx_quic_path_t *path) -{ - ngx_queue_t *q; - ngx_quic_socket_t *qsock; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - /* best guess: cid used by active path is good for us */ - if (qc->socket->path == path) { - return qc->socket->cid; - } - - for (q = ngx_queue_head(&qc->sockets); - q != ngx_queue_sentinel(&qc->sockets); - q = ngx_queue_next(q)) - { - qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); - - if (qsock->path && qsock->path == path) { - return qsock->cid; - } - } - - return NULL; -} - - ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ngx_quic_retire_cid_frame_t *f) { - ngx_quic_path_t *path; - ngx_quic_socket_t *qsock, **tmp; - ngx_quic_client_id_t *cid; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -408,76 +390,14 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic socket #%uL is retired", qsock->sid.seqnum); - /* check if client is willing to retire sid we have in use */ - if (qsock->sid.seqnum == qc->socket->sid.seqnum) { - tmp = &qc->socket; - - } else if (qc->backup && qsock->sid.seqnum == qc->backup->sid.seqnum) { - tmp = &qc->backup; - - } else { - - ngx_quic_close_socket(c, qsock); - - /* restore socket count up to a limit after deletion */ - if (ngx_quic_create_sockets(c) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; - } - - /* preserve path/cid from retired socket */ - path = qsock->path; - cid = qsock->cid; - - /* ensure that closing_socket will not drop path and cid */ - path->refcnt++; - cid->refcnt++; - ngx_quic_close_socket(c, qsock); - /* restore original values */ - path->refcnt--; - cid->refcnt--; - /* restore socket count up to a limit after deletion */ if (ngx_quic_create_sockets(c) != NGX_OK) { - goto failed; - } - - qsock = ngx_quic_get_unconnected_socket(c); - if (qsock == NULL) { - qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; - qc->error_reason = "not enough server IDs"; - goto failed; + return NGX_ERROR; } - ngx_quic_connect(c, qsock, path, cid); - - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic %s socket is now #%uL:%uL:%uL (%s)", - (*tmp) == qc->socket ? "active" : "backup", - qsock->sid.seqnum, qsock->cid->seqnum, - qsock->path->seqnum, - ngx_quic_path_state_str(qsock->path)); - - /* restore active/backup pointer in quic connection */ - *tmp = qsock; - return NGX_OK; - -failed: - - /* - * socket was closed, path and cid were preserved artifically - * to be reused, but it didn't happen, thus unref here - */ - - ngx_quic_unref_path(c, path); - ngx_quic_unref_client_id(c, cid); - - return NGX_ERROR; } @@ -552,62 +472,31 @@ ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) } -static ngx_int_t -ngx_quic_replace_retired_client_id(ngx_connection_t *c, - ngx_quic_client_id_t *retired_cid) +ngx_int_t +ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) { - ngx_queue_t *q; - ngx_quic_socket_t *qsock; - ngx_quic_client_id_t *cid; + ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - for (q = ngx_queue_head(&qc->sockets); - q != ngx_queue_sentinel(&qc->sockets); - q = ngx_queue_next(q)) - { - qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); - - if (qsock->cid == retired_cid) { - - cid = ngx_quic_next_client_id(c); - if (cid == NULL) { - return NGX_ERROR; - } - - qsock->cid = cid; - cid->refcnt++; - - ngx_quic_unref_client_id(c, retired_cid); - - if (retired_cid->refcnt == 0) { - return NGX_OK; - } - } + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; } - return NGX_OK; -} - - -void -ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) -{ - ngx_quic_connection_t *qc; - - if (cid->refcnt) { - cid->refcnt--; - } /* else: unused client id */ + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = cid->seqnum; - if (cid->refcnt) { - return; - } + ngx_quic_queue_frame(qc, frame); - qc = ngx_quic_get_connection(c); + /* we are no longer going to use this client id */ ngx_queue_remove(&cid->queue); ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); qc->nclient_ids--; + + return NGX_OK; } diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h index f823e84dc..33e9c65b9 100644 --- a/src/event/quic/ngx_event_quic_connid.h +++ b/src/event/quic/ngx_event_quic_connid.h @@ -23,8 +23,7 @@ ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, uint64_t seqnum, u_char *token); ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); -ngx_quic_client_id_t *ngx_quic_used_client_id(ngx_connection_t *c, - ngx_quic_path_t *path); -void ngx_quic_unref_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid); +ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); #endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index ac787e31d..e66a402c8 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -16,17 +16,14 @@ static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); -static ngx_int_t ngx_quic_path_restore(ngx_connection_t *c); -static ngx_quic_path_t *ngx_quic_alloc_path(ngx_connection_t *c); +static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_path_challenge_frame_t *f) + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) { - ngx_quic_path_t *path; ngx_quic_frame_t frame, *fp; - ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -43,18 +40,16 @@ ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, * A PATH_RESPONSE frame MUST be sent on the network path where the * PATH_CHALLENGE frame was received. */ - qsock = ngx_quic_get_socket(c); - path = qsock->path; /* * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame * to at least the smallest allowed maximum datagram size of 1200 bytes. */ - if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) { + if (ngx_quic_frame_sendto(c, &frame, 1200, pkt->path) != NGX_OK) { return NGX_ERROR; } - if (qsock == qc->socket) { + if (pkt->path == qc->path) { /* * RFC 9000, 9.3.3. Off-Path Packet Forwarding * @@ -101,7 +96,7 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c, { path = ngx_queue_data(q, ngx_quic_path_t, queue); - if (path->state != NGX_QUIC_PATH_VALIDATING) { + if (!path->validating) { continue; } @@ -112,7 +107,7 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c, } } - ngx_log_error(NGX_LOG_INFO, c->log, 0, + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stale PATH_RESPONSE ignored"); return NGX_OK; @@ -130,8 +125,9 @@ valid: rst = 1; - if (qc->backup) { - prev = qc->backup->path; + prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (prev != NULL) { if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, path->sockaddr, path->socklen, 0) @@ -164,20 +160,24 @@ valid: } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic path #%uL successfully validated", path->seqnum); + "quic path #%uL addr:%V successfully validated", + path->seqnum, &path->addr_text); + + ngx_quic_path_dbg(c, "is validated", path); - path->state = NGX_QUIC_PATH_VALIDATED; + path->validated = 1; + path->validating = 0; path->limited = 0; return NGX_OK; } -static ngx_quic_path_t * -ngx_quic_alloc_path(ngx_connection_t *c) +ngx_quic_path_t * +ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) { ngx_queue_t *q; - struct sockaddr *sa; ngx_quic_path_t *path; ngx_quic_connection_t *qc; @@ -190,9 +190,7 @@ ngx_quic_alloc_path(ngx_connection_t *c) ngx_queue_remove(&path->queue); - sa = path->sockaddr; ngx_memzero(path, sizeof(ngx_quic_path_t)); - path->sockaddr = sa; } else { @@ -200,37 +198,18 @@ ngx_quic_alloc_path(ngx_connection_t *c) if (path == NULL) { return NULL; } - - path->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); - if (path->sockaddr == NULL) { - return NULL; - } } - return path; -} - - -ngx_quic_path_t * -ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, - socklen_t socklen) -{ - ngx_quic_path_t *path; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); + ngx_queue_insert_tail(&qc->paths, &path->queue); - path = ngx_quic_alloc_path(c); - if (path == NULL) { - return NULL; - } + path->cid = cid; + cid->used = 1; - path->state = NGX_QUIC_PATH_NEW; path->limited = 1; path->seqnum = qc->path_seqnum++; - path->last_seen = ngx_current_msec; + path->sockaddr = &path->sa.sockaddr; path->socklen = socklen; ngx_memcpy(path->sockaddr, sockaddr, socklen); @@ -238,19 +217,15 @@ ngx_quic_add_path(ngx_connection_t *c, struct sockaddr *sockaddr, path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, NGX_SOCKADDR_STRLEN, 1); - ngx_queue_insert_tail(&qc->paths, &path->queue); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL created src:%V", + "quic path #%uL created addr:%V", path->seqnum, &path->addr_text); - return path; } -ngx_quic_path_t * -ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, - socklen_t socklen) +static ngx_quic_path_t * +ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) { ngx_queue_t *q; ngx_quic_path_t *path; @@ -264,10 +239,7 @@ ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, { path = ngx_queue_data(q, ngx_quic_path_t, queue); - if (ngx_cmp_sockaddr(sockaddr, socklen, - path->sockaddr, path->socklen, 1) - == NGX_OK) - { + if (path->tag == tag) { return path; } } @@ -277,10 +249,11 @@ ngx_quic_find_path(ngx_connection_t *c, struct sockaddr *sockaddr, ngx_int_t -ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) +ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) { off_t len; - ngx_quic_path_t *path; + ngx_queue_t *q; + ngx_quic_path_t *path, *probe; ngx_quic_socket_t *qsock; ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; @@ -288,72 +261,69 @@ ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); qsock = ngx_quic_get_socket(c); + len = pkt->raw->last - pkt->raw->start; + if (c->udp->dgram == NULL) { - /* 1st ever packet in connection, path already exists */ - path = qsock->path; + /* first ever packet in connection, path already exists */ + path = qc->path; goto update; } - path = ngx_quic_find_path(c, c->udp->dgram->sockaddr, - c->udp->dgram->socklen); - - if (path == NULL) { - path = ngx_quic_add_path(c, c->udp->dgram->sockaddr, - c->udp->dgram->socklen); - if (path == NULL) { - return NGX_ERROR; - } - - if (qsock->path) { - /* NAT rebinding case: packet to same CID, but from new address */ + probe = NULL; - ngx_quic_unref_path(c, qsock->path); - - qsock->path = path; - path->refcnt++; + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, c->udp->dgram->socklen, + path->sockaddr, path->socklen, 1) + == NGX_OK) + { goto update; } - } else if (qsock->path) { - goto update; + if (path->tag == NGX_QUIC_PATH_PROBE) { + probe = path; + } } - /* prefer unused client IDs if available */ - cid = ngx_quic_next_client_id(c); - if (cid == NULL) { - - /* try to reuse connection ID used on the same path */ - cid = ngx_quic_used_client_id(c, path); - if (cid == NULL) { - - qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; - qc->error_reason = "no available client ids for new path"; + /* packet from new path, drop current probe, if any */ - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "no available client ids for new path"); + if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { + return NGX_ERROR; + } - return NGX_ERROR; - } + /* new path requires new client id */ + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic no available client ids for new path"); + /* stop processing of this datagram */ + return NGX_DONE; } - ngx_quic_connect(c, qsock, path, cid); + path = ngx_quic_new_path(c, c->udp->dgram->sockaddr, + c->udp->dgram->socklen, cid); + if (path == NULL) { + return NGX_ERROR; + } -update: + path->tag = NGX_QUIC_PATH_PROBE; - if (path->state != NGX_QUIC_PATH_NEW) { - /* force limits/revalidation for paths that were not seen recently */ - if (ngx_current_msec - path->last_seen > qc->tp.max_idle_timeout) { - path->state = NGX_QUIC_PATH_NEW; - path->limited = 1; - path->sent = 0; - path->received = 0; - } + /* + * client arrived using new path and previously seen DCID, + * this indicates NAT rebinding (or bad client) + */ + if (qsock->used) { + pkt->rebound = 1; } - path->last_seen = ngx_current_msec; +update: - len = pkt->raw->last - pkt->raw->start; + qsock->used = 1; + pkt->path = path; /* TODO: this may be too late in some cases; * for example, if error happens during decrypt(), we cannot @@ -364,11 +334,38 @@ update: */ path->received += len; - ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet via #%uL:%uL:%uL" - " size:%O path recvd:%O sent:%O limited:%ui", - qsock->sid.seqnum, qsock->cid->seqnum, path->seqnum, - len, path->received, path->sent, path->limited); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet len:%O via sock#%uL path#%uL", + len, qsock->sid.seqnum, path->seqnum); + ngx_quic_path_dbg(c, "status", path); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&path->queue); + ngx_queue_insert_head(&qc->free_paths, &path->queue); + + /* + * invalidate CID that is no longer usable for any other path; + * this also requests new CIDs from client + */ + if (path->cid) { + if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path #%uL addr:%V retired", + path->seqnum, &path->addr_text); return NGX_OK; } @@ -398,35 +395,14 @@ ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) { - ngx_quic_path_t *next; - ngx_quic_socket_t *qsock; + ngx_quic_path_t *next, *bkp; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - /* got non-probing packet via non-active socket with different path */ + /* got non-probing packet via non-active path */ qc = ngx_quic_get_connection(c); - /* current socket, different from active */ - qsock = ngx_quic_get_socket(c); - - next = qsock->path; /* going to migrate to this path... */ - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic migration from #%uL:%uL:%uL (%s)" - " to #%uL:%uL:%uL (%s)", - qc->socket->sid.seqnum, qc->socket->cid->seqnum, - qc->socket->path->seqnum, - ngx_quic_path_state_str(qc->socket->path), - qsock->sid.seqnum, qsock->cid->seqnum, next->seqnum, - ngx_quic_path_state_str(next)); - - if (next->state == NGX_QUIC_PATH_NEW) { - if (ngx_quic_validate_path(c, qsock->path) != NGX_OK) { - return NGX_ERROR; - } - } - ctx = ngx_quic_get_send_ctx(qc, pkt->level); /* @@ -439,39 +415,59 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_OK; } - /* switching connection to new path */ - - ngx_quic_set_connection_path(c, next); + next = pkt->path; /* - * RFC 9000, 9.5. Privacy Implications of Connection Migration + * RFC 9000, 9.3.3: * - * An endpoint MUST NOT reuse a connection ID when sending to - * more than one destination address. + * In response to an apparent migration, endpoints MUST validate the + * previously active path using a PATH_CHALLENGE frame. */ + if (pkt->rebound) { + + /* NAT rebinding: client uses new path with old SID */ + if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + if (qc->path->validated) { - /* preserve valid path we are migrating from */ - if (qc->socket->path->state == NGX_QUIC_PATH_VALIDATED) { + if (next->tag != NGX_QUIC_PATH_BACKUP) { + /* can delete backup path, if any */ + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); - if (qc->backup) { - ngx_quic_close_socket(c, qc->backup); + if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { + return NGX_ERROR; + } } - qc->backup = qc->socket; + qc->path->tag = NGX_QUIC_PATH_BACKUP; + ngx_quic_path_dbg(c, "is now backup", qc->path); - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic backup socket is now #%uL:%uL:%uL (%s)", - qc->backup->sid.seqnum, qc->backup->cid->seqnum, - qc->backup->path->seqnum, - ngx_quic_path_state_str(qc->backup->path)); + } else { + if (ngx_quic_free_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } } - qc->socket = qsock; + /* switch active path to migrated */ + qc->path = next; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, next); + + if (!next->validated && !next->validating) { + if (ngx_quic_validate_path(c, next) != NGX_OK) { + return NGX_ERROR; + } + } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic active socket is now #%uL:%uL:%uL (%s)", - qsock->sid.seqnum, qsock->cid->seqnum, - qsock->path->seqnum, ngx_quic_path_state_str(qsock->path)); + "quic migrated to path#%uL addr:%V", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is now active", qc->path); return NGX_OK; } @@ -487,10 +483,9 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) qc = ngx_quic_get_connection(c); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic initiated validation of new path #%uL", - path->seqnum); + "quic initiated validation of path #%uL", path->seqnum); - path->state = NGX_QUIC_PATH_VALIDATING; + path->validating = 1; if (RAND_bytes(path->challenge1, 8) != 1) { return NGX_ERROR; @@ -524,7 +519,7 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) ngx_quic_frame_t frame; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL send path challenge tries:%ui", + "quic path #%uL send path_challenge tries:%ui", path->seqnum, path->tries); ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); @@ -564,7 +559,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) ngx_msec_t now; ngx_queue_t *q; ngx_msec_int_t left, next, pto; - ngx_quic_path_t *path; + ngx_quic_path_t *path, *bkp; ngx_connection_t *c; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; @@ -578,13 +573,14 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) next = -1; now = ngx_current_msec; - for (q = ngx_queue_head(&qc->paths); - q != ngx_queue_sentinel(&qc->paths); - q = ngx_queue_next(q)) - { + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); - if (path->state != NGX_QUIC_PATH_VALIDATING) { + if (!path->validating) { continue; } @@ -593,7 +589,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) if (left > 0) { if (next == -1 || left < next) { - next = path->expires; + next = left; } continue; @@ -617,26 +613,43 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) /* found expired path */ - path->state = NGX_QUIC_PATH_NEW; + path->validated = 0; + path->validating = 0; path->limited = 1; - /* - * RFC 9000, 9.4. Loss Detection and Congestion Control + + /* RFC 9000, 9.3.2. On-Path Address Spoofing * - * If the timer fires before the PATH_RESPONSE is received, the - * endpoint might send a new PATH_CHALLENGE and restart the timer for - * a longer period of time. This timer SHOULD be set as described in - * Section 6.2.1 of [QUIC-RECOVERY] and MUST NOT be more aggressive. + * To protect the connection from failing due to such a spurious + * migration, an endpoint MUST revert to using the last validated + * peer address when validation of a new peer address fails. */ - if (qc->socket->path != path) { - /* the path was not actually used */ - continue; + if (qc->path == path) { + /* active path validation failed */ + + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp == NULL) { + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; + qc->error_reason = "no viable path"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + qc->path = bkp; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, qc->path); + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path #%uL addr:%V is restored from backup", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is active", qc->path); } - if (ngx_quic_path_restore(c) != NGX_OK) { - qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; - qc->error_reason = "no viable path"; + if (ngx_quic_free_path(c, path) != NGX_OK) { ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -646,44 +659,3 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) ngx_add_timer(&qc->path_validation, next); } } - - -static ngx_int_t -ngx_quic_path_restore(ngx_connection_t *c) -{ - ngx_quic_socket_t *qsock; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - /* - * RFC 9000, 9.1. Probing a New Path - * - * Failure to validate a path does not cause the connection to end - * - * RFC 9000, 9.3.2. On-Path Address Spoofing - * - * To protect the connection from failing due to such a spurious - * migration, an endpoint MUST revert to using the last validated - * peer address when validation of a new peer address fails. - */ - - if (qc->backup == NULL) { - return NGX_ERROR; - } - - qc->socket = qc->backup; - qc->backup = NULL; - - qsock = qc->socket; - - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic active socket is restored to #%uL:%uL:%uL" - " (%s), no backup", - qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, - ngx_quic_path_state_str(qsock->path)); - - ngx_quic_set_connection_path(c, qsock->path); - - return NGX_OK; -} diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index 4bb5a2a1a..a446d10a5 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -11,29 +11,29 @@ #include #include -#define NGX_QUIC_PATH_RETRIES 3 +#define NGX_QUIC_PATH_RETRIES 3 -#define NGX_QUIC_PATH_NEW 0 -#define NGX_QUIC_PATH_VALIDATING 1 -#define NGX_QUIC_PATH_VALIDATED 2 - - -#define ngx_quic_path_state_str(p) \ - ((p)->state == NGX_QUIC_PATH_NEW) ? "new" : \ - (((p)->state == NGX_QUIC_PATH_VALIDATED) ? "validated" : "validating") +#define NGX_QUIC_PATH_PROBE 0 +#define NGX_QUIC_PATH_ACTIVE 1 +#define NGX_QUIC_PATH_BACKUP 2 +#define ngx_quic_path_dbg(c, msg, path) \ + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ + "quic path#%uL %s sent:%O recvd:%O state:%s%s%s", \ + path->seqnum, msg, path->sent, path->received, \ + path->limited ? "L" : "", path->validated ? "V": "N", \ + path->validating ? "R": ""); ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, - ngx_quic_path_challenge_frame_t *f); + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, ngx_quic_path_challenge_frame_t *f); -ngx_quic_path_t *ngx_quic_find_path(ngx_connection_t *c, - struct sockaddr *sockaddr, socklen_t socklen); -ngx_quic_path_t *ngx_quic_add_path(ngx_connection_t *c, - struct sockaddr *sockaddr, socklen_t socklen); +ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid); +ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path); -ngx_int_t ngx_quic_update_paths(ngx_connection_t *c, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt); ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt); diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 5599cdeb1..d4b079125 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -51,7 +51,7 @@ static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_quic_header_t *pkt); + ngx_quic_header_t *pkt, ngx_quic_path_t *path); static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, struct sockaddr *sockaddr, socklen_t socklen); @@ -131,7 +131,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c) qc = ngx_quic_get_connection(c); cg = &qc->congestion; - path = qc->socket->path; + path = qc->path; while (cg->in_flight < cg->window) { @@ -269,7 +269,7 @@ ngx_quic_allow_segmentation(ngx_connection_t *c) return 0; } - if (qc->socket->path->limited) { + if (qc->path->limited) { /* don't even try to be faster on non-validated paths */ return 0; } @@ -325,7 +325,7 @@ ngx_quic_create_segments(ngx_connection_t *c) qc = ngx_quic_get_connection(c); cg = &qc->congestion; - path = qc->socket->path; + path = qc->path; ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); @@ -505,17 +505,18 @@ static ssize_t ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min) { - size_t len, pad, min_payload, max_payload; - u_char *p; - ssize_t flen; - ngx_str_t res; - ngx_int_t rc; - ngx_uint_t nframes, expand; - ngx_msec_t now; - ngx_queue_t *q; - ngx_quic_frame_t *f; - ngx_quic_header_t pkt; - static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + size_t len, pad, min_payload, max_payload; + u_char *p; + ssize_t flen; + ngx_str_t res; + ngx_int_t rc; + ngx_uint_t nframes, expand; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; if (ngx_queue_empty(&ctx->frames)) { return 0; @@ -525,7 +526,9 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, "quic output %s packet max:%uz min:%uz", ngx_quic_level_name(ctx->level), max, min); - ngx_quic_init_packet(c, ctx, &pkt); + qc = ngx_quic_get_connection(c); + + ngx_quic_init_packet(c, ctx, &pkt, qc->path); min_payload = ngx_quic_payload_size(&pkt, min); max_payload = ngx_quic_payload_size(&pkt, max); @@ -668,14 +671,14 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, - ngx_quic_header_t *pkt) + ngx_quic_header_t *pkt, ngx_quic_path_t *path) { ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - qsock = qc->socket; + qsock = ngx_quic_get_socket(c); ngx_memzero(pkt, sizeof(ngx_quic_header_t)); @@ -693,8 +696,8 @@ ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, } } - pkt->dcid.data = qsock->cid->id; - pkt->dcid.len = qsock->cid->len; + pkt->dcid.data = path->cid->id; + pkt->dcid.len = path->cid->len; pkt->scid.data = qsock->sid.id; pkt->scid.len = qsock->sid.len; @@ -1202,7 +1205,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - ngx_quic_init_packet(c, ctx, &pkt); + ngx_quic_init_packet(c, ctx, &pkt, path); min = ngx_quic_path_limit(c, path, min); diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 426e1ebae..b5d168a7a 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -14,11 +14,12 @@ ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) { - ngx_quic_path_t *path; ngx_quic_socket_t *qsock, *tmp; ngx_quic_client_id_t *cid; /* + * qc->path = NULL + * * qc->nclient_ids = 0 * qc->nsockets = 0 * qc->max_retired_seqnum = 0 @@ -51,6 +52,8 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, 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) { @@ -69,19 +72,20 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, goto failed; } - /* the client arrived from this path */ - path = ngx_quic_add_path(c, c->sockaddr, c->socklen); - if (path == NULL) { + /* 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; } + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + if (pkt->validated) { - path->state = NGX_QUIC_PATH_VALIDATED; - path->limited = 0; + qc->path->validated = 1; + qc->path->limited = 0; } - /* now bind socket to client and path */ - ngx_quic_connect(c, qsock, path, cid); + ngx_quic_path_dbg(c, "set active", qc->path); tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); if (tmp == NULL) { @@ -97,16 +101,6 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, goto failed; } - ngx_quic_connect(c, tmp, path, cid); - - /* use this socket as default destination */ - qc->socket = qsock; - - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic active socket is #%uL:%uL:%uL (%s)", - qsock->sid.seqnum, qsock->cid->seqnum, qsock->path->seqnum, - ngx_quic_path_state_str(qsock->path)); - return NGX_OK; failed: @@ -165,42 +159,12 @@ ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); qc->nsockets--; - if (qsock->path) { - ngx_quic_unref_path(c, qsock->path); - } - - if (qsock->cid) { - ngx_quic_unref_client_id(c, qsock->cid); - } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic socket #%L closed nsock:%ui", (int64_t) qsock->sid.seqnum, qc->nsockets); } -void -ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path) -{ - ngx_quic_connection_t *qc; - - path->refcnt--; - - if (path->refcnt) { - return; - } - - qc = ngx_quic_get_connection(c); - - ngx_queue_remove(&path->queue); - ngx_queue_insert_head(&qc->free_paths, &path->queue); - - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL addr:%V removed", - path->seqnum, &path->addr_text); -} - - ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_socket_t *qsock) @@ -228,23 +192,6 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, } -void -ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *sock, - ngx_quic_path_t *path, ngx_quic_client_id_t *cid) -{ - sock->path = path; - path->refcnt++; - - sock->cid = cid; - cid->refcnt++; - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket #%L connected to cid #%uL path:%uL", - (int64_t) sock->sid.seqnum, - sock->cid->seqnum, path->seqnum); -} - - void ngx_quic_close_sockets(ngx_connection_t *c) { @@ -285,27 +232,3 @@ ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) return NULL; } - - -ngx_quic_socket_t * -ngx_quic_get_unconnected_socket(ngx_connection_t *c) -{ - ngx_queue_t *q; - ngx_quic_socket_t *sock; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - for (q = ngx_queue_head(&qc->sockets); - q != ngx_queue_sentinel(&qc->sockets); - q = ngx_queue_next(q)) - { - sock = ngx_queue_data(q, ngx_quic_socket_t, queue); - - if (sock->cid == NULL) { - return sock; - } - } - - return NULL; -} diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h index 51f01a1fa..e15996883 100644 --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -22,10 +22,6 @@ ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, ngx_quic_socket_t *qsock); void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); -void ngx_quic_unref_path(ngx_connection_t *c, ngx_quic_path_t *path); -void ngx_quic_connect(ngx_connection_t *c, ngx_quic_socket_t *qsock, - ngx_quic_path_t *path, ngx_quic_client_id_t *cid); - ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); ngx_quic_socket_t *ngx_quic_get_unconnected_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 e5e3ffcab..41806b252 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -497,7 +497,7 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) ngx_quic_queue_frame(qc, frame); if (qc->conf->retry) { - if (ngx_quic_send_new_token(c, qc->socket->path) != NGX_OK) { + if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { return NGX_ERROR; } } @@ -541,6 +541,7 @@ ngx_quic_init_connection(ngx_connection_t *c) ssize_t len; ngx_str_t dcid; ngx_ssl_conn_t *ssl_conn; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -569,8 +570,10 @@ ngx_quic_init_connection(ngx_connection_t *c) SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); #endif - dcid.data = qc->socket->sid.id; - dcid.len = qc->socket->sid.len; + qsock = ngx_quic_get_socket(c); + + dcid.data = qsock->sid.id; + dcid.len = qsock->sid.len; if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) != NGX_OK) diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 64ebfa979..a512ad802 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -300,6 +300,7 @@ struct ngx_quic_frame_s { typedef struct { ngx_log_t *log; + ngx_quic_path_t *path; ngx_quic_keys_t *keys; @@ -335,6 +336,7 @@ typedef struct { unsigned validated:1; unsigned retried:1; unsigned first:1; + unsigned rebound:1; } ngx_quic_header_t; -- cgit v1.2.3 From a816af6e1be93ad026b179f8c35c720b891b1e65 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 20 Jan 2022 22:00:25 +0300 Subject: QUIC: additional limit for probing packets. RFC 9000, 9.3. Responding to Connection Migration: An endpoint only changes the address to which it sends packets in response to the highest-numbered non-probing packet. The patch extends this requirement to probing packets. Although it may seem excessive, it helps with mitigation of reply attacks (when an off-path attacker has copied packet with PATH_CHALLENGE and uses different addresses to exhaust available connection ids). --- src/event/quic/ngx_event_quic_migration.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index e66a402c8..d1a5cf7a0 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -255,6 +255,7 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) ngx_queue_t *q; ngx_quic_path_t *path, *probe; ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; ngx_quic_client_id_t *cid; ngx_quic_connection_t *qc; @@ -291,6 +292,16 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) /* packet from new path, drop current probe, if any */ + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * only accept highest-numbered packets to prevent connection id + * exhaustion by excessive probing packets from unknown paths + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_DONE; + } + if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 67b9f081d0510fe1132c61561a0d1405506c11c0 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Sun, 23 Jan 2022 21:29:36 +0300 Subject: QUIC: avoid logging error in case of version negotiation. Previously, "early error" message was logged in this case. --- src/event/quic/ngx_event_quic_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index d4b079125..bf43f7883 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -813,7 +813,7 @@ ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); - return NGX_ERROR; + return NGX_DONE; } -- cgit v1.2.3 From a0aa287d13ef8d9b6916d3b52e9bfa2aa30871fe Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 21 Jan 2022 11:41:39 +0300 Subject: QUIC: removed stale declaration. The ngx_quic_get_unconnected_socket() was removed in 1e2f4e9c8195. --- src/event/quic/ngx_event_quic_socket.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h index e15996883..68ecc063d 100644 --- a/src/event/quic/ngx_event_quic_socket.h +++ b/src/event/quic/ngx_event_quic_socket.h @@ -23,7 +23,6 @@ ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); -ngx_quic_socket_t *ngx_quic_get_unconnected_socket(ngx_connection_t *c); #endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ -- cgit v1.2.3 From c40382267432d741d9ef24aecdec55d1768ae448 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Jan 2022 09:45:50 +0300 Subject: QUIC: fixed chain returned from ngx_quic_write_chain(). Previously, when input ended on a QUIC buffer boundary, input chain was not advanced to the next buffer. As a result, ngx_quic_write_chain() returned a chain with an empty buffer instead of NULL. This broke HTTP write filter, preventing it from closing the HTTP request and eventually timing out. Now input chain is always advanced to a buffer that has data, before checking QUIC buffer boundary condition. --- src/event/quic/ngx_event_quic_frames.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 851af4643..951b6e8a2 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -536,14 +536,16 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, continue; } - for (p = b->pos + offset; p != b->last && in; /* void */ ) { + p = b->pos + offset; + + while (in) { if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { in = in->next; continue; } - if (limit == 0) { + if (p == b->last || limit == 0) { break; } -- cgit v1.2.3 From 264dd955cb81dff66908556b5ed8ebbd5cac8ef8 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 25 Jan 2022 15:48:05 +0300 Subject: QUIC: fixed macro style. --- src/event/quic/ngx_event_quic_output.c | 8 ++++---- src/os/unix/ngx_udp_sendmsg_chain.c | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index bf43f7883..da75aa3de 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -405,7 +405,7 @@ ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, struct msghdr msg; struct cmsghdr *cmsg; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) char msg_control[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #else @@ -438,7 +438,7 @@ ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, valp = (void *) CMSG_DATA(cmsg); *valp = segment; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { cmsg = CMSG_NXTHDR(&msg, cmsg); clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); @@ -719,7 +719,7 @@ ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, ssize_t n; struct iovec iov; struct msghdr msg; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) struct cmsghdr *cmsg; char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif @@ -735,7 +735,7 @@ ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, msg.msg_name = sockaddr; msg.msg_namelen = socklen; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { msg.msg_control = msg_control; diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index b29b8d318..e9e6f7091 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -208,7 +208,7 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) { struct msghdr msg; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) struct cmsghdr *cmsg; u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif @@ -223,7 +223,7 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) msg.msg_iov = vec->iovs; msg.msg_iovlen = vec->count; -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { msg.msg_control = msg_control; @@ -240,7 +240,7 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) } -#if defined(NGX_HAVE_ADDRINFO_CMSG) +#if (NGX_HAVE_ADDRINFO_CMSG) size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) -- cgit v1.2.3 From 281afc950da01f21f380c9cff8a24dc0706eec31 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 21 Jan 2022 11:20:18 +0300 Subject: QUIC: changed debug message. --- src/event/quic/ngx_event_quic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index d47d0bc4e..a7061624e 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1431,7 +1431,7 @@ ngx_quic_push_handler(ngx_event_t *ev) { ngx_connection_t *c; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push timer"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); c = ev->data; -- cgit v1.2.3 From 5efdec71581f9402dd7132267d68240ab2f8870b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: HTTP/3: removed draft versions support in ALPN. --- src/event/quic/ngx_event_quic.c | 12 ------------ src/event/quic/ngx_event_quic.h | 1 - src/http/modules/ngx_http_ssl_module.c | 15 --------------- src/http/v3/ngx_http_v3.h | 3 --- 4 files changed, 31 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index a7061624e..544cbd3cb 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1477,15 +1477,3 @@ ngx_quic_shutdown_quic(ngx_connection_t *c) } -uint32_t -ngx_quic_version(ngx_connection_t *c) -{ - uint32_t version; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - - version = qc->version; - - return (version & 0xff000000) == 0xff000000 ? version & 0xff : version; -} diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 195184754..9179611e7 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -77,7 +77,6 @@ void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); 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); -uint32_t ngx_quic_version(ngx_connection_t *c); ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 1ecbe439f..64ad9ee2f 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -419,9 +419,6 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { -#if (NGX_HTTP_V3) - const char *fmt; -#endif unsigned int srvlen; unsigned char *srv; #if (NGX_DEBUG) @@ -466,23 +463,11 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, if (h3scf->hq) { srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; - fmt = NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT; } else #endif { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; - fmt = NGX_HTTP_V3_ALPN_DRAFT_FMT; - } - - /* QUIC draft */ - - if (ngx_quic_version(c) > 1) { - srv = ngx_pnalloc(c->pool, sizeof("\x05h3-xx") - 1); - if (srv == NULL) { - return SSL_TLSEXT_ERR_NOACK; - } - srvlen = ngx_sprintf(srv, fmt, ngx_quic_version(c)) - srv; } } else diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 956c824a2..ea78ac1f3 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -20,10 +20,7 @@ #define NGX_HTTP_V3_ALPN_PROTO "\x02h3" -#define NGX_HTTP_V3_ALPN_DRAFT_FMT "\x05h3-%02uD" - #define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" -#define NGX_HTTP_V3_HQ_ALPN_DRAFT_FMT "\x05hq-%02uD" #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 -- cgit v1.2.3 From 846a386c7ebc78424ed14993e28d30d26bd26318 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: QUIC: removed draft versions support. --- src/event/quic/ngx_event_quic.c | 3 +-- src/event/quic/ngx_event_quic_output.c | 3 +-- src/event/quic/ngx_event_quic_protection.c | 17 ++++------------- src/event/quic/ngx_event_quic_protection.h | 2 +- src/event/quic/ngx_event_quic_transport.c | 6 ------ 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 544cbd3cb..f1d96a58a 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -329,8 +329,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } } - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid, - qc->version) + if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid) != NGX_OK) { return NULL; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index da75aa3de..0f7eff26d 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -950,8 +950,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid, - inpkt->version) + if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid) != 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 3db510eef..bbf85af9a 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -146,7 +146,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, - ngx_str_t *secret, uint32_t version) + ngx_str_t *secret) { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -157,9 +157,6 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, static const uint8_t salt[20] = "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; - static const uint8_t salt29[20] = - "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97" - "\x86\xf1\x9c\x61\x11\xe0\x43\x90\xa8\x99"; client = &keys->secrets[ssl_encryption_initial].client; server = &keys->secrets[ssl_encryption_initial].server; @@ -175,7 +172,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, is_len = SHA256_DIGEST_LENGTH; if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - (version & 0xff000000) ? salt29 : salt, sizeof(salt)) + salt, sizeof(salt)) != NGX_OK) { return NGX_ERROR; @@ -892,12 +889,8 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) /* 5.8. Retry Packet Integrity */ static u_char key[16] = "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; - static u_char key29[16] = - "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1"; static u_char nonce[NGX_QUIC_IV_LEN] = "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; - static u_char nonce29[NGX_QUIC_IV_LEN] = - "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c"; static ngx_str_t in = ngx_string(""); ad.data = res->data; @@ -916,12 +909,10 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } secret.key.len = sizeof(key); - secret.key.data = (pkt->version & 0xff000000) ? key29 : key; + secret.key.data = key; secret.iv.len = NGX_QUIC_IV_LEN; - if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, - (pkt->version & 0xff000000) ? nonce29 : nonce, - &in, &ad, pkt->log) + if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) != NGX_OK) { return NGX_ERROR; diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index 92491f02a..ff375b510 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -19,7 +19,7 @@ ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, - ngx_quic_keys_t *keys, ngx_str_t *secret, uint32_t version); + ngx_quic_keys_t *keys, ngx_str_t *secret); ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 949d2691b..5cf6bc207 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -73,8 +73,6 @@ #define ngx_quic_build_int_set(p, value, len, bits) \ (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) -#define NGX_QUIC_VERSION(c) (0xff000000 + (c)) - static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); static ngx_uint_t ngx_quic_varint_len(uint64_t value); @@ -137,10 +135,6 @@ static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, uint32_t ngx_quic_versions[] = { /* QUICv1 */ 0x00000001, - NGX_QUIC_VERSION(29), - NGX_QUIC_VERSION(30), - NGX_QUIC_VERSION(31), - NGX_QUIC_VERSION(32), }; #define NGX_QUIC_NVERSIONS \ -- cgit v1.2.3 From c242f9f88e17ec04ae90142373e43b5f8f0d17f9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: QUIC: set to standard TLS codepoint after draft versions removal. This is to ease transition with oldish BoringSSL versions, the default for SSL_set_quic_use_legacy_codepoint() has been flipped in BoringSSL a1d3bfb64fd7ef2cb178b5b515522ffd75d7b8c5. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 41806b252..9220f9870 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -567,7 +567,7 @@ ngx_quic_init_connection(ngx_connection_t *c) #endif #if BORINGSSL_API_VERSION >= 13 - SSL_set_quic_use_legacy_codepoint(ssl_conn, qc->version != 1); + SSL_set_quic_use_legacy_codepoint(ssl_conn, 0); #endif qsock = ngx_quic_get_socket(c); -- cgit v1.2.3 From fdc6ab8319117b0c791165b83cfdca27f3fd4f11 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: README: updated to QUICv1. While here, removed old browsers tips. --- README | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/README b/README index cf36dfceb..59a727138 100644 --- a/README +++ b/README @@ -34,8 +34,7 @@ Experimental QUIC support for nginx What works now: - Currently we support IETF-QUIC draft-29 through final RFC documents. - Earlier drafts are NOT supported as they have incompatible wire format. + We support IETF QUIC version 1. Internet drafts are no longer supported. nginx should be able to respond to HTTP/3 requests over QUIC and it should be possible to upload and download big files without errors. @@ -178,21 +177,12 @@ Example configuration: * Browsers - Known to work: Firefox 80+ and Chrome 85+ (QUIC draft 29+) + Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) Beware of strange issues: sometimes browser may decide to ignore QUIC Cache clearing/restart might help. Always check access.log and error.log to make sure you are using HTTP/3 and not TCP https. - + to enable QUIC in Firefox, set the following in 'about:config': - network.http.http3.enabled = true - - + to enable QUIC in Chrome, enable it on command line and force it - on your site: - - $ ./chrome --enable-quic --quic-version=h3-29 \ - --origin-to-force-quic-on=example.com:8443 - * Console clients Known to work: ngtcp2, firefox's neqo and chromium's console clients: @@ -201,10 +191,7 @@ Example configuration: $ ./neqo-client https://127.0.0.1:8443/ - $ chromium-build/out/my_build/quic_client http://example.com:8443 \ - --quic_version=h3-29 \ - --allow_unknown_root_cert \ - --disable_certificate_verification + $ chromium-build/out/my_build/quic_client http://example.com:8443 If you've got it right, in the access log you should see something like: @@ -222,7 +209,7 @@ Example configuration: + Ensure you are using the proper SSL library in runtime (`nginx -V` will show you what you are using) - + Ensure your client is actually sending QUIC requests + + Ensure your client is actually sending requests over QUIC (see "Clients" section about browsers and cache) We recommend to start with simple console client like ngtcp2 -- cgit v1.2.3 From 10d076eee6037ae855050f05d79a9624171f5246 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: README: updated info about incomplete features. --- README | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README b/README index 59a727138..d45aaa3cb 100644 --- a/README +++ b/README @@ -52,21 +52,6 @@ Experimental QUIC support for nginx + Lost packets are detected and retransmitted properly + Clients may migrate to new address - Not (yet) supported features: - - - Explicit Congestion Notification (ECN) as specified in quic-recovery [5] - - A connection with the spin bit succeeds and the bit is spinning - - Structured Logging - - Since the code is experimental and still under development, - a lot of things may not work as expected, for example: - - - Flow control mechanism is basic and intended to avoid CPU hog and make - simple interactions possible - - - Not all protocol requirements are strictly followed; some of checks are - omitted for the sake of simplicity of initial implementation - 2. Installing You will need a BoringSSL [4] library that provides QUIC support -- cgit v1.2.3 From ede7dbe0070875cf93998a70fd2a3ceab21377da Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 26 Jan 2022 14:15:40 +0300 Subject: README: updated link to nginx-devel mailman. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index d45aaa3cb..f5994e8e6 100644 --- a/README +++ b/README @@ -224,7 +224,7 @@ Example configuration: [1] https://datatracker.ietf.org/doc/html/rfc9000 [2] https://datatracker.ietf.org/doc/html/draft-ietf-quic-http - [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel + [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/ [4] https://boringssl.googlesource.com/boringssl/ [5] https://datatracker.ietf.org/doc/html/rfc9002 [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen -- cgit v1.2.3 From d5e71992c3431af3c97312f75057966cbd0eb68f Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 26 Jan 2022 15:48:12 +0300 Subject: QUIC: fixed handling of initial source connection id. This was broken in 1e2f4e9c8195. While there, adjusted formatting of debug message with socket seqnum. --- src/event/quic/ngx_event_quic_migration.c | 4 ++-- src/event/quic/ngx_event_quic_output.c | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index d1a5cf7a0..ca43347eb 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -346,8 +346,8 @@ update: path->received += len; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet len:%O via sock#%uL path#%uL", - len, qsock->sid.seqnum, path->seqnum); + "quic packet len:%O via sock#%L path#%uL", + len, (int64_t) qsock->sid.seqnum, path->seqnum); ngx_quic_path_dbg(c, "status", path); return NGX_OK; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 0f7eff26d..67e527462 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -673,13 +673,10 @@ static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, ngx_quic_header_t *pkt, ngx_quic_path_t *path) { - ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - qsock = ngx_quic_get_socket(c); - ngx_memzero(pkt, sizeof(ngx_quic_header_t)); pkt->flags = NGX_QUIC_PKT_FIXED_BIT; @@ -699,8 +696,7 @@ ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, pkt->dcid.data = path->cid->id; pkt->dcid.len = path->cid->len; - pkt->scid.data = qsock->sid.id; - pkt->scid.len = qsock->sid.len; + pkt->scid = qc->tp.initial_scid; pkt->version = qc->version; pkt->log = c->log; -- cgit v1.2.3 From 9a9cb1982fc4232f74b5f7465876126670009c31 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 26 Jan 2022 18:03:45 +0300 Subject: QUIC: style. --- src/event/quic/ngx_event_quic.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index f1d96a58a..9309faa6c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1474,5 +1474,3 @@ ngx_quic_shutdown_quic(ngx_connection_t *c) ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); } - - -- cgit v1.2.3 From f87630ab49d053d35829e8c2e2160e66495f3a2d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 27 Jan 2022 13:14:01 +0300 Subject: QUIC: limited SSL_set_quic_use_legacy_codepoint() API usage. As advertised in BoringSSL a1d3bfb64fd7ef2cb178b5b515522ffd75d7b8c5, it may be dropped once callers implementing the draft versions cycle out. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 9220f9870..a10fe197b 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -566,7 +566,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif -#if BORINGSSL_API_VERSION >= 13 +#if (BORINGSSL_API_VERSION >= 13 && BORINGSSL_API_VERSION < 15) SSL_set_quic_use_legacy_codepoint(ssl_conn, 0); #endif -- cgit v1.2.3 From acb585518d868a5051c1caa85add006d975751f5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 31 Jan 2022 09:16:47 +0300 Subject: QUIC: allowed main QUIC connection for some operations. Operations like ngx_quic_open_stream(), ngx_http_quic_get_connection(), ngx_http_v3_finalize_connection(), ngx_http_v3_shutdown_connection() used to receive a QUIC stream connection. Now they can receive the main QUIC connection as well. This is useful when calling them from a stream context. --- src/event/quic/ngx_event_quic_streams.c | 9 +++++---- src/http/v3/ngx_http_v3.c | 4 ++-- src/http/v3/ngx_http_v3.h | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 2016c62a1..8928d4ea1 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -40,11 +40,12 @@ ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { uint64_t id; - ngx_quic_stream_t *qs, *nqs; + ngx_connection_t *pc; + ngx_quic_stream_t *nqs; ngx_quic_connection_t *qc; - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); + pc = c->quic ? c->quic->parent : c; + qc = ngx_quic_get_connection(pc); if (bidi) { if (qc->streams.server_streams_bidi @@ -90,7 +91,7 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) qc->streams.server_streams_uni++; } - nqs = ngx_quic_create_stream(qs->parent, id); + nqs = ngx_quic_create_stream(pc, id); if (nqs == NULL) { return NULL; } diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 97d8a5e34..29b07e025 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -78,8 +78,8 @@ ngx_http_v3_keepalive_handler(ngx_event_t *ev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); - ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, - "keepalive timeout"); + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); } diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index ea78ac1f3..b54d9aee0 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -78,7 +78,8 @@ #define ngx_http_quic_get_connection(c) \ - ((ngx_http_connection_t *) (c)->quic->parent->data) + ((ngx_http_connection_t *) ((c)->quic ? (c)->quic->parent->data \ + : (c)->data)) #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session @@ -91,10 +92,12 @@ module) #define ngx_http_v3_finalize_connection(c, code, reason) \ - ngx_quic_finalize_connection(c->quic->parent, 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->parent, code, reason) + ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) typedef struct { -- cgit v1.2.3 From c1b172f1d67f806c84dc088ddc57ed76514e85dd Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 27 Jan 2022 12:20:47 +0300 Subject: HTTP/3: delayed Insert Count Increment instruction. Sending the instruction is delayed until the end of the current event cycle. Delaying the instruction is allowed by quic-qpack-21, section 2.2.2.3. The goal is to reduce the amount of data sent back to client by accumulating several inserts in one instruction and sometimes not sending the instruction at all, if Section Acknowledgement was sent just before it. --- src/http/v3/ngx_http_v3.c | 8 +++++++ src/http/v3/ngx_http_v3_parse.c | 2 ++ src/http/v3/ngx_http_v3_table.c | 49 +++++++++++++++++++++++++++++++++++++---- src/http/v3/ngx_http_v3_table.h | 5 +++++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 29b07e025..84bb8f601 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -47,6 +47,10 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c->keepalive.handler = ngx_http_v3_keepalive_handler; h3c->keepalive.cancelable = 1; + h3c->table.send_insert_count.log = pc->log; + h3c->table.send_insert_count.data = pc; + h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; + cln = ngx_pool_cleanup_add(pc->pool, 0); if (cln == NULL) { goto failed; @@ -93,6 +97,10 @@ ngx_http_v3_cleanup_session(void *data) 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_parse.c b/src/http/v3/ngx_http_v3_parse.c index 25c1edf6d..cd70bd3bf 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -395,6 +395,8 @@ done: if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { return NGX_ERROR; } + + ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); } st->state = sw_start; diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index c6d543ac4..22dc37901 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -232,11 +232,9 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) dt->elts[dt->nelts++] = field; dt->size += size; - /* TODO increment can be sent less often */ + dt->insert_count++; - if (ngx_http_v3_send_inc_insert_count(c, 1) != NGX_OK) { - return NGX_ERROR; - } + ngx_post_event(&dt->send_insert_count, &ngx_posted_events); if (ngx_http_v3_new_entry(c) != NGX_OK) { return NGX_ERROR; @@ -246,6 +244,34 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) } +void +ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 inc insert count handler"); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_count > dt->ack_insert_count) { + if (ngx_http_v3_send_inc_insert_count(c, + dt->insert_count - dt->ack_insert_count) + != NGX_OK) + { + return; + } + + dt->ack_insert_count = dt->insert_count; + } +} + + ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) { @@ -607,6 +633,21 @@ ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) } +void +ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->ack_insert_count < insert_count) { + dt->ack_insert_count = insert_count; + } +} + + static void ngx_http_v3_unblock(void *data) { diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h index 6f88b35b9..1c2fb17b9 100644 --- a/src/http/v3/ngx_http_v3_table.h +++ b/src/http/v3/ngx_http_v3_table.h @@ -26,9 +26,13 @@ typedef struct { ngx_uint_t base; size_t size; size_t capacity; + uint64_t insert_count; + uint64_t ack_insert_count; + ngx_event_t send_insert_count; } ngx_http_v3_dynamic_table_t; +void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); @@ -46,6 +50,7 @@ ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count); ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count); +void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count); ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value); -- cgit v1.2.3 From 5e31cdbb98466055ce27d4596b5e5a1406fc83d4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 31 Jan 2022 09:46:02 +0300 Subject: QUIC: introduced explicit stream states. This allows to eliminate the usage of stream connection event flags for tracking stream state. --- src/event/quic/ngx_event_quic.h | 22 +++++ src/event/quic/ngx_event_quic_ack.c | 11 ++- src/event/quic/ngx_event_quic_streams.c | 159 ++++++++++++++++++-------------- 3 files changed, 117 insertions(+), 75 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 9179611e7..3b3d317fc 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -28,6 +28,26 @@ #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 +typedef enum { + NGX_QUIC_STREAM_SEND_READY = 0, + NGX_QUIC_STREAM_SEND_SEND, + NGX_QUIC_STREAM_SEND_DATA_SENT, + NGX_QUIC_STREAM_SEND_DATA_RECVD, + NGX_QUIC_STREAM_SEND_RESET_SENT, + NGX_QUIC_STREAM_SEND_RESET_RECVD +} ngx_quic_stream_send_state_e; + + +typedef enum { + NGX_QUIC_STREAM_RECV_RECV = 0, + NGX_QUIC_STREAM_RECV_SIZE_KNOWN, + NGX_QUIC_STREAM_RECV_DATA_RECVD, + NGX_QUIC_STREAM_RECV_DATA_READ, + NGX_QUIC_STREAM_RECV_RESET_RECVD, + NGX_QUIC_STREAM_RECV_RESET_READ +} ngx_quic_stream_recv_state_e; + + typedef struct { ngx_ssl_t *ssl; @@ -66,6 +86,8 @@ struct ngx_quic_stream_s { ngx_chain_t *in; ngx_chain_t *out; ngx_uint_t cancelable; /* unsigned cancelable:1; */ + ngx_quic_stream_send_state_e send_state; + ngx_quic_stream_recv_state_e recv_state; }; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index e5e5d3611..8b10e9e46 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -617,10 +617,13 @@ ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) case NGX_QUIC_FT_STREAM: qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (qs && qs->connection->write->error) { - /* RESET_STREAM was sent */ - ngx_quic_free_frame(c, f); - break; + if (qs) { + if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + ngx_quic_free_frame(c, f); + break; + } } /* fall through */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 8928d4ea1..82b0617d6 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -192,12 +192,13 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { qs = (ngx_quic_stream_t *) node; + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + rev = qs->connection->read; - rev->error = 1; rev->ready = 1; wev = qs->connection->write; - wev->error = 1; wev->ready = 1; ngx_post_event(rev, &ngx_posted_events); @@ -221,19 +222,22 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) { - ngx_event_t *wev; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - wev = c->write; + qs = c->quic; - if (wev->error) { + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { return NGX_OK; } - qs = c->quic; + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -250,9 +254,6 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) ngx_quic_queue_frame(qc, frame); - wev->error = 1; - wev->ready = 1; - return NGX_OK; } @@ -260,27 +261,15 @@ 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) { - ngx_quic_stream_t *qs; - - qs = c->quic; - if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) - || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) - { - if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { + return NGX_ERROR; } } if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0 - || (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) - { - if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { - return NGX_ERROR; - } + if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { + return NGX_ERROR; } } @@ -291,19 +280,21 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c) { - ngx_event_t *wev; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - wev = c->write; + qs = c->quic; - if (wev->error) { + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { return NGX_OK; } - qs = c->quic; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; + pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -327,8 +318,6 @@ ngx_quic_shutdown_stream_send(ngx_connection_t *c) ngx_quic_queue_frame(qc, frame); - wev->error = 1; - return NGX_OK; } @@ -336,19 +325,19 @@ ngx_quic_shutdown_stream_send(ngx_connection_t *c) static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c) { - ngx_event_t *rev; ngx_connection_t *pc; ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - rev = c->read; + qs = c->quic; - if (rev->pending_eof || rev->error) { + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { return NGX_OK; } - qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -371,8 +360,6 @@ ngx_quic_shutdown_stream_recv(ngx_connection_t *c) ngx_quic_queue_frame(qc, frame); - rev->error = 1; - return NGX_OK; } @@ -690,9 +677,13 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { 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; } else { qs->recv_max_data = qc->tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; } } else { @@ -704,6 +695,9 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; } qs->recv_window = qs->recv_max_data; @@ -744,25 +738,19 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) pc = qs->parent; rev = c->read; - if (rev->error) { + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + rev->error = 1; return NGX_ERROR; } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv eof:%d buf:%uz", - qs->id, rev->pending_eof, size); - - if (qs->in == NULL || qs->in->buf->sync) { - rev->ready = 0; - - if (qs->recv_offset == qs->final_size) { - rev->eof = 1; - return 0; - } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv buf:%uz", qs->id, size); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL recv() not ready", qs->id); - return NGX_AGAIN; + if (size == 0) { + return 0; } in = ngx_quic_read_chain(pc, &qs->in, size); @@ -780,8 +768,23 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_quic_free_chain(pc, in); - if (qs->in == NULL) { - rev->ready = rev->pending_eof; + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + && qs->recv_offset == qs->final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -839,10 +842,15 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) qc = ngx_quic_get_connection(pc); wev = c->write; - if (wev->error) { + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + wev->error = 1; return NGX_CHAIN_ERROR; } + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + flow = ngx_quic_max_stream_flow(c); if (flow == 0) { wev->ready = 0; @@ -1051,9 +1059,9 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, sc = qs->connection; - rev = sc->read; - - if (rev->error) { + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { return NGX_OK; } @@ -1086,8 +1094,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - rev->pending_eof = 1; qs->final_size = last; + qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; } if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, @@ -1098,6 +1106,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->offset == qs->recv_offset) { + rev = sc->read; rev->ready = 1; if (rev->active) { @@ -1273,11 +1282,15 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_OK; } - sc = qs->connection; + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + return NGX_OK; + } - rev = sc->read; - rev->error = 1; - rev->ready = 1; + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + + sc = qs->connection; if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { return NGX_ERROR; @@ -1299,6 +1312,9 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } + rev = sc->read; + rev->ready = 1; + if (rev->active) { ngx_post_event(rev, &ngx_posted_events); } @@ -1341,6 +1357,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, wev = qs->connection->write; if (wev->active) { + wev->ready = 1; ngx_post_event(wev, &ngx_posted_events); } @@ -1413,11 +1430,9 @@ static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) { uint64_t len; - ngx_event_t *rev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - rev = c->read; qs = c->quic; qc = ngx_quic_get_connection(qs->parent); @@ -1434,7 +1449,9 @@ ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) qs->recv_last += len; - if (!rev->error && qs->recv_last > qs->recv_max_data) { + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV + && qs->recv_last > qs->recv_max_data) + { qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; return NGX_ERROR; } @@ -1454,12 +1471,10 @@ static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) { uint64_t len; - ngx_event_t *rev; ngx_connection_t *pc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - rev = c->read; qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -1475,9 +1490,7 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) qs->recv_offset += len; - if (!rev->pending_eof && !rev->error - && qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) - { + if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { if (ngx_quic_update_max_stream_data(c) != NGX_OK) { return NGX_ERROR; } @@ -1510,6 +1523,10 @@ ngx_quic_update_max_stream_data(ngx_connection_t *c) pc = qs->parent; qc = ngx_quic_get_connection(pc); + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + recv_max_data = qs->recv_offset + qs->recv_window; if (qs->recv_max_data == recv_max_data) { -- cgit v1.2.3 From 77efe512fa18580bef6f8d795bc1c1cfff5d0097 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 31 Jan 2022 09:46:30 +0300 Subject: HTTP/3: proper uni stream closure detection. Previously, closure detection for server-initiated uni streams was not properly implemented. Instead, HTTP/3 code relied on QUIC code posting the read event and setting rev->error when it needed to close the stream. Then, regular uni stream read handler called c->recv() and received error, which closed the stream. This was an ad-hoc solution. If, for whatever reason, the read handler was called earlier, c->recv() would return 0, which would also close the stream. Now server-initiated uni streams have a separate read event handler for tracking stream closure. The handler calls c->recv(), which normally returns 0, but may return error in case of closure. --- src/http/v3/ngx_http_v3_uni.c | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index f0a599655..bd7eb278b 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -26,7 +26,8 @@ typedef struct { static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); -static void ngx_http_v3_dummy_write_handler(ngx_event_t *wev); +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 void ngx_http_v3_push_cleanup(void *data); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); @@ -68,7 +69,7 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) c->data = us; c->read->handler = ngx_http_v3_uni_read_handler; - c->write->handler = ngx_http_v3_dummy_write_handler; + c->write->handler = ngx_http_v3_uni_dummy_write_handler; ngx_http_v3_uni_read_handler(c->read); } @@ -252,7 +253,33 @@ failed: static void -ngx_http_v3_dummy_write_handler(ngx_event_t *wev) +ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) +{ + u_char ch; + ngx_connection_t *c; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); + + if (rev->ready) { + if (c->recv(c, &ch, 1) != 0) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); + ngx_http_v3_close_uni_stream(c); + return; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static void +ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) { ngx_connection_t *c; @@ -393,8 +420,8 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) sc->data = us; - sc->read->handler = ngx_http_v3_uni_read_handler; - sc->write->handler = ngx_http_v3_dummy_write_handler; + sc->read->handler = ngx_http_v3_uni_dummy_read_handler; + sc->write->handler = ngx_http_v3_uni_dummy_write_handler; if (index >= 0) { h3c->known_streams[index] = sc; @@ -409,6 +436,8 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) goto failed; } + ngx_post_event(sc->read, &ngx_posted_events); + return sc; failed: -- cgit v1.2.3 From ca92231bf0742e2798d9e551d70e1c4f14d9e64d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 31 Jan 2022 18:09:03 +0300 Subject: QUIC: style. --- src/event/quic/ngx_event_quic.h | 70 ++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 3b3d317fc..8a188c2da 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -49,45 +49,45 @@ typedef enum { typedef struct { - ngx_ssl_t *ssl; - - ngx_flag_t retry; - ngx_flag_t gso_enabled; - ngx_flag_t disable_active_migration; - ngx_msec_t timeout; - ngx_str_t host_key; - size_t mtu; - size_t stream_buffer_size; - ngx_uint_t max_concurrent_streams_bidi; - ngx_uint_t max_concurrent_streams_uni; - ngx_uint_t active_connection_id_limit; - ngx_int_t stream_close_code; - ngx_int_t stream_reject_code_uni; - ngx_int_t stream_reject_code_bidi; - - u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; - u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; + ngx_ssl_t *ssl; + + ngx_flag_t retry; + ngx_flag_t gso_enabled; + ngx_flag_t disable_active_migration; + ngx_msec_t timeout; + ngx_str_t host_key; + size_t mtu; + size_t stream_buffer_size; + ngx_uint_t max_concurrent_streams_bidi; + ngx_uint_t max_concurrent_streams_uni; + ngx_uint_t active_connection_id_limit; + ngx_int_t stream_close_code; + ngx_int_t stream_reject_code_uni; + ngx_int_t stream_reject_code_bidi; + + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t; struct ngx_quic_stream_s { - ngx_rbtree_node_t node; - ngx_queue_t queue; - ngx_connection_t *parent; - ngx_connection_t *connection; - uint64_t id; - uint64_t acked; - uint64_t send_max_data; - uint64_t recv_max_data; - uint64_t recv_offset; - uint64_t recv_window; - uint64_t recv_last; - uint64_t final_size; - ngx_chain_t *in; - ngx_chain_t *out; - ngx_uint_t cancelable; /* unsigned cancelable:1; */ - ngx_quic_stream_send_state_e send_state; - ngx_quic_stream_recv_state_e recv_state; + ngx_rbtree_node_t node; + ngx_queue_t queue; + ngx_connection_t *parent; + ngx_connection_t *connection; + uint64_t id; + uint64_t acked; + uint64_t send_max_data; + uint64_t recv_max_data; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t final_size; + ngx_chain_t *in; + ngx_chain_t *out; + ngx_uint_t cancelable; /* unsigned cancelable:1; */ + ngx_quic_stream_send_state_e send_state; + ngx_quic_stream_recv_state_e recv_state; }; -- cgit v1.2.3 From 1491d6ec42382148ab2d3de6cf08453ef4b5623f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 26 Jan 2022 12:01:31 +0300 Subject: QUIC: stream event setting function. The function ngx_quic_set_event() is now called instead of posting events directly. --- src/event/quic/ngx_event_quic_streams.c | 73 ++++++++++----------------------- 1 file changed, 21 insertions(+), 52 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 82b0617d6..6f10ff125 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -34,6 +34,7 @@ static ngx_int_t ngx_quic_control_flow(ngx_connection_t *c, uint64_t last); static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c); static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); +static void ngx_quic_set_event(ngx_event_t *ev); ngx_connection_t * @@ -156,7 +157,6 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) { ngx_pool_t *pool; ngx_queue_t *q; - ngx_event_t *rev, *wev; ngx_rbtree_t *tree; ngx_rbtree_node_t *node; ngx_quic_stream_t *qs; @@ -195,17 +195,8 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; - rev = qs->connection->read; - rev->ready = 1; - - wev = qs->connection->write; - wev->ready = 1; - - ngx_post_event(rev, &ngx_posted_events); - - if (rev->timer_set) { - ngx_del_timer(rev); - } + ngx_quic_set_event(qs->connection->read); + ngx_quic_set_event(qs->connection->write); #if (NGX_DEBUG) ns++; @@ -1028,7 +1019,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { uint64_t last; - ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1106,12 +1096,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->offset == qs->recv_offset) { - rev = sc->read; - rev->ready = 1; - - if (rev->active) { - ngx_post_event(rev, &ngx_posted_events); - } + ngx_quic_set_event(sc->read); } return NGX_OK; @@ -1122,7 +1107,6 @@ ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, ngx_quic_max_data_frame_t *f) { - ngx_event_t *wev; ngx_rbtree_t *tree; ngx_rbtree_node_t *node; ngx_quic_stream_t *qs; @@ -1144,12 +1128,7 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, node = ngx_rbtree_next(tree, node)) { qs = (ngx_quic_stream_t *) node; - wev = qs->connection->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } + ngx_quic_set_event(qs->connection->write); } } @@ -1210,7 +1189,6 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) { uint64_t sent; - ngx_event_t *wev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1240,12 +1218,7 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, sent = qs->connection->sent; if (sent >= qs->send_max_data) { - wev = qs->connection->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } + ngx_quic_set_event(qs->connection->write); } qs->send_max_data = f->limit; @@ -1258,7 +1231,6 @@ ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { - ngx_event_t *rev; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1312,12 +1284,7 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } - rev = sc->read; - rev->ready = 1; - - if (rev->active) { - ngx_post_event(rev, &ngx_posted_events); - } + ngx_quic_set_event(qs->connection->read); return NGX_OK; } @@ -1327,7 +1294,6 @@ ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) { - ngx_event_t *wev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1354,12 +1320,7 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_ERROR; } - wev = qs->connection->write; - - if (wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); - } + ngx_quic_set_event(qs->connection->write); return NGX_OK; } @@ -1398,7 +1359,6 @@ void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { uint64_t sent, unacked; - ngx_event_t *wev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1409,13 +1369,11 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) return; } - wev = qs->connection->write; sent = qs->connection->sent; unacked = sent - qs->acked; - if (unacked >= qc->conf->stream_buffer_size && wev->active) { - wev->ready = 1; - ngx_post_event(wev, &ngx_posted_events); + if (unacked >= qc->conf->stream_buffer_size) { + ngx_quic_set_event(qs->connection->write); } qs->acked += f->u.stream.length; @@ -1589,6 +1547,17 @@ ngx_quic_update_max_data(ngx_connection_t *c) } +static void +ngx_quic_set_event(ngx_event_t *ev) +{ + ev->ready = 1; + + if (ev->active) { + ngx_post_event(ev, &ngx_posted_events); + } +} + + ngx_int_t ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { -- cgit v1.2.3 From 3f94e45a3b27a80d60be75bbcd7ef5b2c5e11a91 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 1 Feb 2022 14:35:31 +0300 Subject: QUIC: revised ngx_quic_handle_datagram() error codes. The NGX_DECLINED is replaced with NGX_DONE to match closer to return code of ngx_quic_handle_packet() and ngx_quic_close_connection() rc argument. The ngx_quic_close_connection() rc code is used only when quic connection exists, thus anything goes if qc == NULL. The ngx_quic_handle_datagram() does not return NG_OK in cases when quic connection is not yet created. --- src/event/quic/ngx_event_quic.c | 21 ++++++--------------- src/event/quic/ngx_event_quic_output.c | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 9309faa6c..ed970bc4c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -210,17 +210,13 @@ ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) rc = ngx_quic_handle_datagram(c, c->buffer, conf); if (rc != NGX_OK) { - ngx_quic_close_connection(c, rc == NGX_DECLINED ? NGX_DONE : NGX_ERROR); + ngx_quic_close_connection(c, rc); return; } + /* quic connection is now created */ qc = ngx_quic_get_connection(c); - if (qc == NULL) { - ngx_quic_close_connection(c, NGX_DONE); - return; - } - ngx_add_timer(c->read, qc->tp.max_idle_timeout); ngx_quic_connstate_dbg(c); @@ -443,7 +439,7 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - if (rc == NGX_DECLINED) { + if (rc == NGX_DONE) { return; } @@ -709,13 +705,8 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, } #endif - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_DONE) { - /* stop further processing */ - return NGX_DECLINED; + if (rc == NGX_ERROR || rc == NGX_DONE) { + return rc; } if (rc == NGX_OK) { @@ -750,7 +741,7 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, } if (!good) { - return NGX_DECLINED; + return NGX_DONE; } qc = ngx_quic_get_connection(c); diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 67e527462..b34ca7855 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -979,7 +979,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - return NGX_OK; + return NGX_DONE; } -- cgit v1.2.3 From 271553bbf37b99991c3c86164a5e83ce2b57870d Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 1 Feb 2022 13:05:38 +0300 Subject: QUIC: merged ngx_quic_close_quic() and ngx_quic_close_connection(). The separate ngx_quic_close_quic() doesn't make much sense. --- src/event/quic/ngx_event_quic.c | 63 +++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index ed970bc4c..20ff3305c 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -16,7 +16,6 @@ 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 ngx_int_t ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc); static void ngx_quic_close_timer_handler(ngx_event_t *ev); static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, @@ -455,7 +454,9 @@ ngx_quic_input_handler(ngx_event_t *rev) void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) { + ngx_uint_t i; ngx_pool_t *pool; + ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -469,41 +470,9 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) "quic close connection early error"); } - } else if (ngx_quic_close_quic(c, rc) == NGX_AGAIN) { - return; - } - - if (c->ssl) { - (void) ngx_ssl_shutdown(c); - } - - if (c->read->timer_set) { - ngx_del_timer(c->read); + goto quic_done; } -#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_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) -{ - ngx_uint_t i; - ngx_quic_send_ctx_t *ctx; - ngx_quic_connection_t *qc; - - qc = ngx_quic_get_connection(c); - if (!qc->closing) { /* drop packets from retransmit queues, no ack is expected */ @@ -582,7 +551,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) } if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { - return NGX_AGAIN; + return; } if (qc->push.timer_set) { @@ -602,7 +571,7 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) } if (qc->close.timer_set) { - return NGX_AGAIN; + return; } ngx_quic_close_sockets(c); @@ -613,7 +582,27 @@ ngx_quic_close_quic(ngx_connection_t *c, ngx_int_t rc) /* may be tested from SSL callback during SSL shutdown */ c->udp = NULL; - return NGX_OK; +quic_done: + + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#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); } -- cgit v1.2.3 From e72168a9ea93bde9a7a17613654fb1f4826d65dd Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 1 Feb 2022 13:01:10 +0300 Subject: QUIC: dead code removed. The ngx_quic_parse_packet() now returns NGX_OK, NGX_ERROR (parsing failed) and NGX_ABORT (unsupported version). --- src/event/quic/ngx_event_quic.c | 4 ++-- src/event/quic/ngx_event_quic_transport.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 20ff3305c..17143f3f7 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -764,8 +764,8 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, rc = ngx_quic_parse_packet(pkt); - if (rc == NGX_DECLINED || rc == NGX_ERROR) { - return rc; + if (rc == NGX_ERROR) { + return NGX_DECLINED; } pkt->parsed = 1; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 5cf6bc207..edd7c6b9f 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -285,14 +285,14 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) { - return NGX_DECLINED; + return NGX_ERROR; } return NGX_OK; } if (ngx_quic_parse_long_header(pkt) != NGX_OK) { - return NGX_DECLINED; + return NGX_ERROR; } if (!ngx_quic_supported_version(pkt->version)) { @@ -300,7 +300,7 @@ ngx_quic_parse_packet(ngx_quic_header_t *pkt) } if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { - return NGX_DECLINED; + return NGX_ERROR; } return NGX_OK; -- cgit v1.2.3 From 26bc237677f1baad3ab565b70cc951f8a3b6ad90 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Fri, 28 Jan 2022 14:57:33 +0300 Subject: QUIC: got rid of hash symbol in backup and logging. Now all objectes with sequence number (i.e. sockets, connection ids and paths) are logged as "foo seq:N". --- src/event/quic/ngx_event_quic_connid.c | 4 ++-- src/event/quic/ngx_event_quic_migration.c | 20 ++++++++++---------- src/event/quic/ngx_event_quic_migration.h | 2 +- src/event/quic/ngx_event_quic_socket.c | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c index 32926a022..f50868205 100644 --- a/src/event/quic/ngx_event_quic_connid.c +++ b/src/event/quic/ngx_event_quic_connid.c @@ -308,7 +308,7 @@ ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, } ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic cid #%uL received id:%uz:%xV:%*xs", + "quic cid seq:%uL received id:%uz:%xV:%*xs", cid->seqnum, id->len, id, (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); @@ -388,7 +388,7 @@ ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket #%uL is retired", qsock->sid.seqnum); + "quic socket seq:%uL is retired", qsock->sid.seqnum); ngx_quic_close_socket(c, qsock); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index ca43347eb..6597923da 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -160,7 +160,7 @@ valid: } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic path #%uL addr:%V successfully validated", + "quic path seq:%uL addr:%V successfully validated", path->seqnum, &path->addr_text); ngx_quic_path_dbg(c, "is validated", path); @@ -218,7 +218,7 @@ ngx_quic_new_path(ngx_connection_t *c, NGX_SOCKADDR_STRLEN, 1); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL created addr:%V", + "quic path seq:%uL created addr:%V", path->seqnum, &path->addr_text); return path; } @@ -346,7 +346,7 @@ update: path->received += len; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet len:%O via sock#%L path#%uL", + "quic packet len:%O via sock seq:%L path seq:%uL", len, (int64_t) qsock->sid.seqnum, path->seqnum); ngx_quic_path_dbg(c, "status", path); @@ -375,7 +375,7 @@ ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL addr:%V retired", + "quic path seq:%uL addr:%V retired", path->seqnum, &path->addr_text); return NGX_OK; @@ -398,7 +398,7 @@ ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send path set to #%uL addr:%V", + "quic send path set to seq:%uL addr:%V", path->seqnum, &path->addr_text); } @@ -475,7 +475,7 @@ ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) } ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic migrated to path#%uL addr:%V", + "quic migrated to path seq:%uL addr:%V", qc->path->seqnum, &qc->path->addr_text); ngx_quic_path_dbg(c, "is now active", qc->path); @@ -494,7 +494,7 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) qc = ngx_quic_get_connection(c); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic initiated validation of path #%uL", path->seqnum); + "quic initiated validation of path seq:%uL", path->seqnum); path->validating = 1; @@ -530,7 +530,7 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) ngx_quic_frame_t frame; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic path #%uL send path_challenge tries:%ui", + "quic path seq:%uL send path_challenge tries:%ui", path->seqnum, path->tries); ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); @@ -620,7 +620,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, - "quic path #%uL validation failed", path->seqnum); + "quic path seq:%uL validation failed", path->seqnum); /* found expired path */ @@ -654,7 +654,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) ngx_quic_set_connection_path(c, qc->path); ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic path #%uL addr:%V is restored from backup", + "quic path seq:%uL addr:%V is restored from backup", qc->path->seqnum, &qc->path->addr_text); ngx_quic_path_dbg(c, "is active", qc->path); diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h index a446d10a5..0e1e85454 100644 --- a/src/event/quic/ngx_event_quic_migration.h +++ b/src/event/quic/ngx_event_quic_migration.h @@ -19,7 +19,7 @@ #define ngx_quic_path_dbg(c, msg, path) \ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ - "quic path#%uL %s sent:%O recvd:%O state:%s%s%s", \ + "quic path seq:%uL %s sent:%O recvd:%O state:%s%s%s", \ path->seqnum, msg, path->sent, path->received, \ path->limited ? "L" : "", path->validated ? "V": "N", \ path->validating ? "R": ""); diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index b5d168a7a..44387fd3c 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -160,7 +160,7 @@ ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) qc->nsockets--; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket #%L closed nsock:%ui", + "quic socket seq:%L closed nsock:%ui", (int64_t) qsock->sid.seqnum, qc->nsockets); } @@ -185,7 +185,7 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, qsock->quic = qc; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic socket #%L listening at sid:%xV nsock:%ui", + "quic socket seq:%L listening at sid:%xV nsock:%ui", (int64_t) sid->seqnum, &id, qc->nsockets); return NGX_OK; -- cgit v1.2.3 From b9e398dc7b397d8684340c73720e470a731657ec Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 1 Feb 2022 15:43:56 +0300 Subject: QUIC: improved debug logging. - wording in log->action is adjusted to match function names. - connection close steps are made obvious and start with "quic close" prefix: *1 quic close initiated rc:-4 *1 quic close silent drain:0 timedout:1 *1 quic close resumed rc:-1 *1 quic close resumed rc:-1 *1 quic close resumed rc:-4 *1 quic close completed this makes it easy to understand if particular "close" record is an initial cause or lasting process, or the final one. - cases of close without quic connection now logged as "packet rejected": *14 quic run *14 quic packet rx long flags:ec version:1 *14 quic packet rx hs len:61 *14 quic packet rx dcid len:20 00000000000002c32f60e4aa2b90a64a39dc4228 *14 quic packet rx scid len:8 81190308612cd019 *14 quic expected initial, got handshake *14 quic packet done rc:-1 level:hs decr:0 pn:0 perr:0 *14 quic packet rejected rc:-1, cleanup connection *14 reusable connection: 0 this makes it easy to spot early packet rejection and avoid confuse with quic connection closing (which in fact was not even created). - packet processing summary now uses same prefix "quic packet done rc:" - added debug to places where packet was rejected without any reason logged --- src/event/quic/ngx_event_quic.c | 43 +++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 17143f3f7..2a51c79a7 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -459,20 +459,18 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ngx_quic_close_connection rc:%i", rc); - qc = ngx_quic_get_connection(c); if (qc == NULL) { - if (rc == NGX_ERROR) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close connection early error"); - } - + 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); + if (!qc->closing) { /* drop packets from retransmit queues, no ack is expected */ @@ -490,10 +488,11 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) * closed and its state is discarded when it remains idle */ - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic closing %s connection", - qc->draining ? "drained" : "idle"); + /* this case also handles some errors from ngx_quic_run() */ + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close silent drain:%d timedout:%d", + qc->draining, c->read->timedout); } else { /* @@ -508,7 +507,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) if (rc == NGX_OK) { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close drain:%d", + "quic close immediate drain:%d", qc->draining); qc->close.log = c->log; @@ -528,7 +527,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) } ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic immediate close due to %s error: %ui %s", + "quic close immediate due to %serror: %ui %s", qc->error_app ? "app " : "", qc->error, qc->error_reason ? qc->error_reason : ""); } @@ -576,8 +575,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) ngx_quic_close_sockets(c); - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic part of connection is terminated"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); /* may be tested from SSL callback during SSL shutdown */ c->udp = NULL; @@ -685,12 +683,13 @@ ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, #if (NGX_DEBUG) if (pkt.parsed) { ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet %s done decr:%d pn:%L perr:%ui rc:%i", - ngx_quic_level_name(pkt.level), pkt.decrypted, - pkt.pn, pkt.error, rc); + "quic packet done rc:%i level:%s" + " decr:%d pn:%L perr:%ui", + rc, ngx_quic_level_name(pkt.level), + pkt.decrypted, pkt.pn, pkt.error); } else { ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic packet done parse failed rc:%i", rc); + "quic packet done rc:%i parse failed", rc); } #endif @@ -770,7 +769,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, pkt->parsed = 1; - c->log->action = "processing quic packet"; + c->log->action = "handling quic packet"; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic packet rx dcid len:%uz %xV", @@ -855,10 +854,12 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->level != ssl_encryption_initial) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic expected initial, got handshake"); return NGX_ERROR; } - c->log->action = "processing initial packet"; + c->log->action = "handling initial packet"; if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { /* RFC 9000, 7.2. Negotiating Connection IDs */ -- cgit v1.2.3 From 5a79f55dab7f515eca56639d3bff05b1376701d5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 1 Feb 2022 20:46:32 +0300 Subject: QUIC: do not declare SSL buffering, it's not used. No functional changes. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index a10fe197b..126758253 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -546,7 +546,7 @@ ngx_quic_init_connection(ngx_connection_t *c) qc = ngx_quic_get_connection(c); - if (ngx_ssl_create_connection(qc->conf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { + if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 3b4d10caf6eb3cb7b86c1b6064869b935a7d0076 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 2 Feb 2022 14:16:48 +0300 Subject: QUIC: fixed padding of initial packets in case of limited path. Previously, non-padded initial packet could be sent as a result of the following situation: - initial queue is not empty (so padding to 1200 is required) - handshake queue is not empty (so padding is to be added after h/s packet) - path is limited If serializing handshake packet would violate path limit, such packet was omitted, and the non-padded initial packet was sent. The fix is to avoid sending the packet at all in such case. This follows the original intention introduced in c5155a0cb12f. --- src/event/quic/ngx_event_quic_output.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index b34ca7855..103dd0e5c 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -158,7 +158,14 @@ ngx_quic_create_datagrams(ngx_connection_t *c) ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; if (min > len) { - continue; + /* padding can't be applied - avoid sending the packet */ + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + ngx_quic_revert_send(c, ctx, preserved_pnum[i]); + } + + return NGX_OK; } n = ngx_quic_output_packet(c, ctx, p, len, min); -- cgit v1.2.3 From 8a61c89a276ae5626ff84f6dd1e9da218754a313 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 2 Feb 2022 15:57:08 +0300 Subject: QUIC: do not arm loss detection timer if nothing was sent. Notably, this became quite practicable after the recent fix in cd8018bc81a5. Additionally, do not arm loss detection timer on connection termination. --- src/event/quic/ngx_event_quic_output.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 103dd0e5c..ebfe0cfc2 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -104,7 +104,12 @@ ngx_quic_output(ngx_connection_t *c) return NGX_ERROR; } - if (in_flight != cg->in_flight && !qc->send_timer_set && !qc->closing) { + if (in_flight == cg->in_flight || qc->closing) { + /* no ack-eliciting data was sent or we are done */ + return NGX_OK; + } + + if (!qc->send_timer_set) { qc->send_timer_set = 1; ngx_add_timer(c->read, qc->tp.max_idle_timeout); } -- cgit v1.2.3 From 1cc2be9616bd9117778d3576dcf3112eb93da4e3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 3 Feb 2022 21:29:05 +0300 Subject: QUIC: improved size calculation in ngx_quic_write_chain(). Previously, size was calculated based on the number of input bytes processed by the function. Now only the copied bytes are considered. This prevents overlapping buffers from contributing twice to the overall written size. --- src/event/quic/ngx_event_quic_frames.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 951b6e8a2..4fa6c56c5 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -554,16 +554,16 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, if (b->sync) { ngx_memcpy(p, in->buf->pos, n); + + if (size) { + *size += n; + } } p += n; in->buf->pos += n; offset += n; limit -= n; - - if (size) { - *size += n; - } } if (b->sync && p == b->last) { -- cgit v1.2.3 From e9c170635e7b35dd2a92ab72e57f38a3403e9566 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 3 Feb 2022 18:11:59 +0300 Subject: QUIC: switch stream to DATA_RECVD state. The switch happens when received byte counter reaches stream final size. Previously, this state was skipped. The stream went from SIZE_KNOWN to DATA_READ when all bytes were read by application. The change prevents STOP_SENDING frames from being sent when all data is received from client, but not yet fully read by application. --- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_streams.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 8a188c2da..af6b6838f 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -82,6 +82,7 @@ struct ngx_quic_stream_s { uint64_t recv_offset; uint64_t recv_window; uint64_t recv_last; + uint64_t recv_size; uint64_t final_size; ngx_chain_t *in; ngx_chain_t *out; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 6f10ff125..c0f32394d 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -762,7 +762,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) if (len == 0) { rev->ready = 0; - if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD && qs->recv_offset == qs->final_size) { qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; @@ -1018,6 +1018,7 @@ ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { + size_t size; uint64_t last; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -1089,12 +1090,20 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, - f->offset - qs->recv_offset, NULL) + f->offset - qs->recv_offset, &size) == NGX_CHAIN_ERROR) { return NGX_ERROR; } + qs->recv_size += size; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + && qs->recv_size == qs->final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; + } + if (f->offset == qs->recv_offset) { ngx_quic_set_event(sc->read); } -- cgit v1.2.3 From 78ec75ad2a9a31ad9ef5fb5cc7cd3d0b30ec8181 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 8 Feb 2022 23:00:12 +0300 Subject: QUIC: fixed the "quic_stream_buffer_size" directive. The default value is now correctly set and the configuration is properly merged. --- src/stream/ngx_stream_quic_module.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 0505df501..644fa0380 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -202,6 +202,10 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->mtu, prev->mtu, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); + ngx_conf_merge_size_value(conf->stream_buffer_size, + prev->stream_buffer_size, + 65536); + ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, prev->max_concurrent_streams_bidi, 16); -- cgit v1.2.3 From 90b19bf65de489d7f729c2876ff5cd63670111f9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 9 Feb 2022 14:49:05 +0300 Subject: QUIC: fixed resetting stream wev->ready flag. Previously, the flag could be reset after send_chain() with a limit, even though there was room for more data. The application then started waiting for a write event notification, which never happened. Now the wev->ready flag is only reset when flow control is exhausted. --- src/event/quic/ngx_event_quic_streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index c0f32394d..8b13f6edc 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -883,7 +883,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) ngx_quic_queue_frame(qc, frame); - if (in) { + if (flow == (off_t) n) { wev->ready = 0; } -- cgit v1.2.3 From 72d0668627ae2ee8a8d33d9d9e65ebc5c11e1e38 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 9 Feb 2022 15:53:21 +0300 Subject: QUIC: fixed output context restoring. The cd8018bc81a5 fixed unintended send of non-padded initial packets, but failed to restore context properly: only processed contexts need to be restored. As a consequence, a packet number could be restored from uninitialized value. --- src/event/quic/ngx_event_quic_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index ebfe0cfc2..72a678c3d 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -165,7 +165,7 @@ ngx_quic_create_datagrams(ngx_connection_t *c) if (min > len) { /* padding can't be applied - avoid sending the packet */ - for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + while (i-- > 0) { ctx = &qc->send_ctx[i]; ngx_quic_revert_send(c, ctx, preserved_pnum[i]); } -- cgit v1.2.3 From 45a8ca0e7a1ae7ec4f7769919e9f3e007113d5ad Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 9 Feb 2022 15:51:42 +0300 Subject: QUIC: fixed in-flight bytes accounting. Initially, frames are genereated and stored in ctx->frames. Next, ngx_quic_output() collects frames to be sent in in ctx->sending. On failure, ngx_quic_revert_sned() returns frames into ctx->frames. On success, the ngx_quic_commit_send() moves ack-eliciting frames into ctx->sent and frees non-ack-eliciting frames. This function also updates in-flight bytes counter, so only actually sent frames are accounted. The counter is decremented in the following cases: - acknowledgment is received - packet was declared lost - we are discarding context completely In each of this cases frame is removed from ctx->sent queue and in-flight counter is accordingly decremented. The patch fixes the case of discarding context - only removing frames from ctx->sent must be followed by in-flight bytes counter decrement, otherwise cg->in_flight could experience type underflow. The issue appeared in b1676cd64dc9. --- src/event/quic/ngx_event_quic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 2a51c79a7..66f63fe0e 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1092,7 +1092,6 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ngx_queue_remove(q); f = ngx_queue_data(q, ngx_quic_frame_t, queue); - ngx_quic_congestion_ack(c, f); ngx_quic_free_frame(c, f); } -- cgit v1.2.3 From 1381932d27a2ccba206633bb5febea72de9c56c2 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 15 Feb 2022 14:12:34 +0300 Subject: QUIC: optimized datagram expansion with half-RTT tickets. As shown in RFC 8446, section 2.2, Figure 3, and further specified in section 4.6.1, BoringSSL releases session tickets in Application Data (along with Finished) early, based on a precalculated client Finished transcript, once client signalled early data in extensions. --- src/event/quic/ngx_event_quic_output.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 72a678c3d..b72d3319e 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -476,6 +476,7 @@ ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c) { + ngx_uint_t i; ngx_queue_t *q; ngx_quic_frame_t *f; ngx_quic_send_ctx_t *ctx; @@ -499,13 +500,15 @@ ngx_quic_get_padding_level(ngx_connection_t *c) f = ngx_queue_data(q, ngx_quic_frame_t, queue); if (f->need_ack) { - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; - if (ngx_queue_empty(&ctx->frames)) { - return 0; + if (ngx_queue_empty(&ctx->frames)) { + break; + } } - return 1; + return i; } } -- cgit v1.2.3 From 7c4b4f4d540de7959311ab0f6bfafac5ab46f67b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 5 Feb 2022 12:54:54 +0300 Subject: QUIC: stream lingering. Now ngx_quic_stream_t is decoupled from ngx_connection_t in a way that it can persist after connection is closed by application. During this period, server is expecting stream final size from client for correct flow control. Also, buffered output is sent to client as more flow control credit is granted. --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic.h | 4 +- src/event/quic/ngx_event_quic_connection.h | 3 + src/event/quic/ngx_event_quic_frames.c | 4 + src/event/quic/ngx_event_quic_streams.c | 445 +++++++++++++++++------------ src/http/v3/ngx_http_v3_uni.c | 2 - 6 files changed, 269 insertions(+), 190 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 66f63fe0e..c98f586b7 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -303,6 +303,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ctp->active_connection_id_limit = 2; ngx_queue_init(&qc->streams.uninitialized); + ngx_queue_init(&qc->streams.free); qc->streams.recv_max_data = qc->tp.initial_max_data; qc->streams.recv_window = qc->streams.recv_max_data; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index af6b6838f..c2295816a 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -78,12 +78,14 @@ struct ngx_quic_stream_s { uint64_t id; uint64_t acked; uint64_t send_max_data; + uint64_t send_offset; + uint64_t send_final_size; uint64_t recv_max_data; uint64_t recv_offset; uint64_t recv_window; uint64_t recv_last; uint64_t recv_size; - uint64_t final_size; + uint64_t recv_final_size; ngx_chain_t *in; ngx_chain_t *out; ngx_uint_t cancelable; /* unsigned cancelable:1; */ diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 173af10d1..2b29284af 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -114,13 +114,16 @@ struct ngx_quic_socket_s { typedef struct { ngx_rbtree_t tree; ngx_rbtree_node_t sentinel; + ngx_queue_t uninitialized; + ngx_queue_t free; uint64_t sent; uint64_t recv_offset; uint64_t recv_window; uint64_t recv_last; uint64_t recv_max_data; + uint64_t send_offset; uint64_t send_max_data; uint64_t server_max_streams_uni; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 4fa6c56c5..188235d9e 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -391,6 +391,10 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) return NGX_ERROR; } + if (f->type == NGX_QUIC_FT_STREAM) { + f->u.stream.fin = 0; + } + ngx_queue_insert_after(&f->queue, &nf->queue); return NGX_OK; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 8b13f6edc..54ed051ca 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -13,6 +13,8 @@ #define NGX_QUIC_STREAM_GONE (void *) -1 +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); 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); @@ -28,11 +30,12 @@ static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size); static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit); -static size_t ngx_quic_max_stream_flow(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_control_flow(ngx_connection_t *c, uint64_t last); -static ngx_int_t ngx_quic_update_flow(ngx_connection_t *c, uint64_t last); -static ngx_int_t ngx_quic_update_max_stream_data(ngx_connection_t *c); +static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); +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); static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); static void ngx_quic_set_event(ngx_event_t *ev); @@ -186,15 +189,20 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ns = 0; #endif - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + if (qs->connection == NULL) { + ngx_quic_close_stream(qs); + continue; + } + ngx_quic_set_event(qs->connection->read); ngx_quic_set_event(qs->connection->write); @@ -212,14 +220,18 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + return ngx_quic_do_reset_stream(c->quic, err); +} + + +static ngx_int_t +ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) { ngx_connection_t *pc; ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; - if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) @@ -228,10 +240,14 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) } qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + qs->send_final_size = qs->send_offset; pc = qs->parent; qc = ngx_quic_get_connection(pc); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL reset", qs->id); + frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { return NGX_ERROR; @@ -241,10 +257,13 @@ ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) frame->type = NGX_QUIC_FT_RESET_STREAM; frame->u.reset_stream.id = qs->id; frame->u.reset_stream.error_code = err; - frame->u.reset_stream.final_size = c->sent; + frame->u.reset_stream.final_size = qs->send_offset; ngx_quic_queue_frame(qc, frame); + ngx_quic_free_chain(pc, qs->out); + qs->out = NULL; + return NGX_OK; } @@ -271,10 +290,7 @@ ngx_quic_shutdown_stream(ngx_connection_t *c, int how) static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c) { - ngx_connection_t *pc; - ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; - ngx_quic_connection_t *qc; + ngx_quic_stream_t *qs; qs = c->quic; @@ -284,32 +300,13 @@ ngx_quic_shutdown_stream_send(ngx_connection_t *c) return NGX_OK; } - qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; - - pc = qs->parent; - qc = ngx_quic_get_connection(pc); - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_ERROR; - } + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + qs->send_final_size = c->sent; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, "quic stream id:0x%xL send shutdown", qs->id); - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM; - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 1; - - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = 0; - - ngx_quic_queue_frame(qc, frame); - - return NGX_OK; + return ngx_quic_stream_flush(qs); } @@ -341,7 +338,7 @@ ngx_quic_shutdown_stream_recv(ngx_connection_t *c) return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, "quic stream id:0x%xL recv shutdown", qs->id); frame->level = ssl_encryption_application; @@ -591,6 +588,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) { ngx_log_t *log; ngx_pool_t *pool; + ngx_queue_t *q; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_pool_cleanup_t *cln; @@ -601,25 +599,41 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) qc = ngx_quic_get_connection(c); - pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); - if (pool == NULL) { - return NULL; - } + if (!ngx_queue_empty(&qc->streams.free)) { + q = ngx_queue_head(&qc->streams.free); + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_queue_remove(&qs->queue); - qs = ngx_pcalloc(pool, sizeof(ngx_quic_stream_t)); - if (qs == NULL) { - ngx_destroy_pool(pool); - return NULL; + } else { + /* + * the number of streams is limited by transport + * parameters and application requirements + */ + + qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t)); + if (qs == NULL) { + return NULL; + } } + ngx_memzero(qs, sizeof(ngx_quic_stream_t)); + qs->node.key = id; qs->parent = c; qs->id = id; - qs->final_size = (uint64_t) -1; + qs->send_final_size = (uint64_t) -1; + qs->recv_final_size = (uint64_t) -1; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } log = ngx_palloc(pool, sizeof(ngx_log_t)); if (log == NULL) { ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); return NULL; } @@ -629,6 +643,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc = ngx_get_connection(c->fd, log); if (sc == NULL) { ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); return NULL; } @@ -697,6 +712,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) if (cln == NULL) { ngx_close_connection(sc); ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); return NULL; } @@ -737,7 +753,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return NGX_ERROR; } - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "quic stream id:0x%xL recv buf:%uz", qs->id, size); if (size == 0) { @@ -763,7 +779,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) rev->ready = 0; if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD - && qs->recv_offset == qs->final_size) + && qs->recv_offset == qs->recv_final_size) { qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; } @@ -781,7 +797,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL recv len:%z", qs->id, len); - if (ngx_quic_update_flow(c, qs->recv_offset + len) != NGX_OK) { + if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { return NGX_ERROR; } @@ -822,9 +838,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) off_t flow; size_t n; ngx_event_t *wev; - ngx_chain_t *out; ngx_connection_t *pc; - ngx_quic_frame_t *frame; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -842,7 +856,8 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) qs->send_state = NGX_QUIC_STREAM_SEND_SEND; - flow = ngx_quic_max_stream_flow(c); + flow = qs->acked + qc->conf->stream_buffer_size - c->sent; + if (flow == 0) { wev->ready = 0; return in; @@ -852,37 +867,15 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) limit = flow; } - in = ngx_quic_write_chain(pc, &qs->out, in, limit, 0, &n); + in = ngx_quic_write_chain(pc, &qs->out, in, limit, + c->sent - qs->send_offset, &n); if (in == NGX_CHAIN_ERROR) { return NGX_CHAIN_ERROR; } - out = ngx_quic_read_chain(pc, &qs->out, n); - if (out == NGX_CHAIN_ERROR) { - return NGX_CHAIN_ERROR; - } - - frame = ngx_quic_alloc_frame(pc); - if (frame == NULL) { - return NGX_CHAIN_ERROR; - } - - frame->level = ssl_encryption_application; - frame->type = NGX_QUIC_FT_STREAM; - frame->data = out; - frame->u.stream.off = 1; - frame->u.stream.len = 1; - frame->u.stream.fin = 0; - - frame->u.stream.stream_id = qs->id; - frame->u.stream.offset = c->sent; - frame->u.stream.length = n; - c->sent += n; qc->streams.sent += n; - ngx_quic_queue_frame(qc, frame); - if (flow == (off_t) n) { wev->ready = 0; } @@ -890,61 +883,96 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic send_chain sent:%uz", n); + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + return in; } -static size_t -ngx_quic_max_stream_flow(ngx_connection_t *c) +static ngx_int_t +ngx_quic_stream_flush(ngx_quic_stream_t *qs) { - size_t size; - uint64_t sent, unacked; - ngx_quic_stream_t *qs; + off_t limit; + size_t len; + ngx_uint_t last; + ngx_chain_t *out, *cl; + ngx_quic_frame_t *frame; + ngx_connection_t *pc; ngx_quic_connection_t *qc; - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); + if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) { + return NGX_OK; + } - size = qc->conf->stream_buffer_size; - sent = c->sent; - unacked = sent - qs->acked; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); if (qc->streams.send_max_data == 0) { qc->streams.send_max_data = qc->ctp.initial_max_data; } - if (unacked >= size) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit buffer size"); - return 0; + limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset, + qs->send_max_data - qs->send_offset); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush limit:%O", qs->id, limit); + + out = ngx_quic_read_chain(pc, &qs->out, limit); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; } - size -= unacked; + len = 0; + last = 0; - if (qc->streams.sent >= qc->streams.send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_DATA"); - return 0; + for (cl = out; cl; cl = cl->next) { + len += cl->buf->last - cl->buf->pos; } - if (qc->streams.sent + size > qc->streams.send_max_data) { - size = qc->streams.send_max_data - qc->streams.sent; + if (qs->send_final_size != (uint64_t) -1 + && qs->send_final_size == qs->send_offset + len) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; + last = 1; } - if (sent >= qs->send_max_data) { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow hit MAX_STREAM_DATA"); - return 0; + if (len == 0 && !last) { + return NGX_OK; } - if (sent + size > qs->send_max_data) { - size = qs->send_max_data - sent; + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send flow:%uz", size); + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM; + frame->data = out; + + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = last; + + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = qs->send_offset; + frame->u.stream.length = len; + + ngx_quic_queue_frame(qc, frame); + + qs->send_offset += len; + qc->streams.send_offset += len; - return size; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush len:%uz last:%ui", + qs->id, len, last); + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + return NGX_OK; } @@ -953,40 +981,67 @@ ngx_quic_stream_cleanup_handler(void *data) { ngx_connection_t *c = data; + ngx_quic_stream_t *qs; + + qs = c->quic; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + qs->connection = NULL; + + if (ngx_quic_close_stream(qs) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } +} + + +static ngx_int_t +ngx_quic_close_stream(ngx_quic_stream_t *qs) +{ ngx_connection_t *pc; ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL cleanup", qs->id); + if (!qc->closing) { + /* make sure everything is sent and final size is received */ + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV + || qs->send_state == NGX_QUIC_STREAM_SEND_READY + || qs->send_state == NGX_QUIC_STREAM_SEND_SEND) + { + return NGX_OK; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL close", qs->id); - ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_quic_free_chain(pc, qs->in); ngx_quic_free_chain(pc, qs->out); + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ ngx_post_event(pc->read, &ngx_posted_events); - return; - } - - if (qc->error) { - goto done; + return NGX_OK; } - (void) ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN); - - (void) ngx_quic_update_flow(c, qs->recv_last); - if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { - goto done; + return NGX_ERROR; } frame->level = ssl_encryption_application; @@ -1004,13 +1059,11 @@ ngx_quic_stream_cleanup_handler(void *data) ngx_quic_queue_frame(qc, frame); } -done: - - (void) ngx_quic_output(pc); - if (qc->shutdown) { ngx_post_event(pc->read, &ngx_posted_events); } + + return NGX_OK; } @@ -1020,7 +1073,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, { size_t size; uint64_t last; - ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; ngx_quic_stream_frame_t *f; @@ -1048,19 +1100,17 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - sc = qs->connection; - if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) { return NGX_OK; } - if (ngx_quic_control_flow(sc, last) != NGX_OK) { + if (ngx_quic_control_flow(qs, last) != NGX_OK) { return NGX_ERROR; } - if (qs->final_size != (uint64_t) -1 && last > qs->final_size) { + if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) { qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; return NGX_ERROR; } @@ -1075,7 +1125,8 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, } if (f->fin) { - if (qs->final_size != (uint64_t) -1 && qs->final_size != last) { + if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) + { qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; return NGX_ERROR; } @@ -1085,7 +1136,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_ERROR; } - qs->final_size = last; + qs->recv_final_size = last; qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; } @@ -1099,13 +1150,17 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->recv_size += size; if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN - && qs->recv_size == qs->final_size) + && qs->recv_size == qs->recv_final_size) { qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; } + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + if (f->offset == qs->recv_offset) { - ngx_quic_set_event(sc->read); + ngx_quic_set_event(qs->connection->read); } return NGX_OK; @@ -1128,20 +1183,26 @@ ngx_quic_handle_max_data_frame(ngx_connection_t *c, return NGX_OK; } - if (tree->root != tree->sentinel - && qc->streams.sent >= qc->streams.send_max_data) + if (tree->root == tree->sentinel + || qc->streams.send_offset < qc->streams.send_max_data) { - - for (node = ngx_rbtree_min(tree->root, tree->sentinel); - node; - node = ngx_rbtree_next(tree, node)) - { - qs = (ngx_quic_stream_t *) node; - ngx_quic_set_event(qs->connection->write); - } + /* not blocked on MAX_DATA */ + qc->streams.send_max_data = f->max_data; + return NGX_OK; } qc->streams.send_max_data = f->max_data; + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node && qc->streams.send_offset < qc->streams.send_max_data) { + + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_ERROR; + } + } return NGX_OK; } @@ -1189,7 +1250,7 @@ ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, return NGX_OK; } - return ngx_quic_update_max_stream_data(qs->connection); + return ngx_quic_update_max_stream_data(qs); } @@ -1197,7 +1258,6 @@ ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) { - uint64_t sent; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1224,15 +1284,15 @@ ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, return NGX_OK; } - sent = qs->connection->sent; - - if (sent >= qs->send_max_data) { - ngx_quic_set_event(qs->connection->write); + if (qs->send_offset < qs->send_max_data) { + /* not blocked on MAX_STREAM_DATA */ + qs->send_max_data = f->limit; + return NGX_OK; } qs->send_max_data = f->limit; - return NGX_OK; + return ngx_quic_stream_flush(qs); } @@ -1240,7 +1300,6 @@ ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { - ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1271,13 +1330,13 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; - sc = qs->connection; - - if (ngx_quic_control_flow(sc, f->final_size) != NGX_OK) { + if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) { return NGX_ERROR; } - if (qs->final_size != (uint64_t) -1 && qs->final_size != f->final_size) { + if (qs->recv_final_size != (uint64_t) -1 + && qs->recv_final_size != f->final_size) + { qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; return NGX_ERROR; } @@ -1287,12 +1346,16 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return NGX_ERROR; } - qs->final_size = f->final_size; + qs->recv_final_size = f->final_size; - if (ngx_quic_update_flow(sc, qs->final_size) != NGX_OK) { + if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) { return NGX_ERROR; } + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + ngx_quic_set_event(qs->connection->read); return NGX_OK; @@ -1325,10 +1388,14 @@ ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, return NGX_OK; } - if (ngx_quic_reset_stream(qs->connection, f->error_code) != NGX_OK) { + if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) { return NGX_ERROR; } + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + ngx_quic_set_event(qs->connection->write); return NGX_OK; @@ -1378,30 +1445,37 @@ ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) return; } + if (qs->connection == NULL) { + qs->acked += f->u.stream.length; + return; + } + sent = qs->connection->sent; unacked = sent - qs->acked; + qs->acked += f->u.stream.length; - if (unacked >= qc->conf->stream_buffer_size) { - ngx_quic_set_event(qs->connection->write); - } + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack len:%uL acked:%uL unacked:%uL", + qs->id, f->u.stream.length, qs->acked, sent - qs->acked); - qs->acked += f->u.stream.length; + if (unacked != qc->conf->stream_buffer_size) { + /* not blocked on buffer size */ + return; + } - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, qs->connection->log, 0, - "quic stream ack len:%uL acked:%uL unacked:%uL", - f->u.stream.length, qs->acked, sent - qs->acked); + ngx_quic_set_event(qs->connection->write); } static ngx_int_t -ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) +ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last) { uint64_t len; - ngx_quic_stream_t *qs; + ngx_connection_t *pc; ngx_quic_connection_t *qc; - qs = c->quic; - qc = ngx_quic_get_connection(qs->parent); + pc = qs->parent; + qc = ngx_quic_get_connection(pc); if (last <= qs->recv_last) { return NGX_OK; @@ -1409,9 +1483,9 @@ ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) len = last - qs->recv_last; - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flow control msd:%uL/%uL md:%uL/%uL", - last, qs->recv_max_data, qc->streams.recv_last + len, + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL", + qs->id, last, qs->recv_max_data, qc->streams.recv_last + len, qc->streams.recv_max_data); qs->recv_last += len; @@ -1435,14 +1509,12 @@ ngx_quic_control_flow(ngx_connection_t *c, uint64_t last) static ngx_int_t -ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) +ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last) { uint64_t len; 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); @@ -1452,13 +1524,13 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) len = last - qs->recv_offset; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flow update %uL", last); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update %uL", qs->id, last); qs->recv_offset += len; if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { - if (ngx_quic_update_max_stream_data(c) != NGX_OK) { + if (ngx_quic_update_max_stream_data(qs) != NGX_OK) { return NGX_ERROR; } } @@ -1478,15 +1550,13 @@ ngx_quic_update_flow(ngx_connection_t *c, uint64_t last) static ngx_int_t -ngx_quic_update_max_stream_data(ngx_connection_t *c) +ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs) { uint64_t recv_max_data; ngx_connection_t *pc; ngx_quic_frame_t *frame; - ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; - qs = c->quic; pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -1502,8 +1572,9 @@ ngx_quic_update_max_stream_data(ngx_connection_t *c) qs->recv_max_data = recv_max_data; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic flow update msd:%uL", qs->recv_max_data); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update msd:%uL", + qs->id, qs->recv_max_data); frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index bd7eb278b..96b7d7ebf 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -295,8 +295,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) } -/* XXX async & buffered stream writes */ - ngx_connection_t * ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) { -- cgit v1.2.3 From 8b3a050f3b5995631f1a8011916283b9b7424d6b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 14 Feb 2022 15:27:59 +0300 Subject: QUIC: ngx_quic_buffer_t object. The object is used instead of ngx_chain_t pointer for buffer operations like ngx_quic_write_chain() and ngx_quic_read_chain(). These functions are renamed to ngx_quic_write_buffer() and ngx_quic_read_buffer(). --- src/event/quic/ngx_event_quic.c | 2 +- src/event/quic/ngx_event_quic.h | 14 +++-- src/event/quic/ngx_event_quic_connection.h | 3 +- src/event/quic/ngx_event_quic_frames.c | 98 ++++++++++++++++++++++-------- src/event/quic/ngx_event_quic_frames.h | 12 ++-- src/event/quic/ngx_event_quic_ssl.c | 50 ++++----------- src/event/quic/ngx_event_quic_streams.c | 51 +++++++--------- 7 files changed, 128 insertions(+), 102 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index c98f586b7..8ef559dae 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -1077,7 +1077,7 @@ ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) ctx = ngx_quic_get_send_ctx(qc, level); - ngx_quic_free_chain(c, ctx->crypto); + ngx_quic_free_buffer(c, &ctx->crypto); while (!ngx_queue_empty(&ctx->sent)) { q = ngx_queue_head(&ctx->sent); diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index c2295816a..8f6dd7e78 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -48,6 +48,13 @@ typedef enum { } ngx_quic_stream_recv_state_e; +typedef struct { + uint64_t size; + uint64_t offset; + ngx_chain_t *chain; +} ngx_quic_buffer_t; + + typedef struct { ngx_ssl_t *ssl; @@ -84,13 +91,12 @@ struct ngx_quic_stream_s { uint64_t recv_offset; uint64_t recv_window; uint64_t recv_last; - uint64_t recv_size; uint64_t recv_final_size; - ngx_chain_t *in; - ngx_chain_t *out; - ngx_uint_t cancelable; /* unsigned cancelable:1; */ + ngx_quic_buffer_t send; + ngx_quic_buffer_t recv; ngx_quic_stream_send_state_e send_state; ngx_quic_stream_recv_state_e recv_state; + ngx_uint_t cancelable; /* unsigned cancelable:1; */ }; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 2b29284af..377b26bd6 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -160,8 +160,7 @@ typedef struct { struct ngx_quic_send_ctx_s { enum ssl_encryption_level_t level; - ngx_chain_t *crypto; - uint64_t crypto_received; + ngx_quic_buffer_t crypto; uint64_t crypto_sent; uint64_t pnum; /* to be sent */ diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 188235d9e..b89072ea2 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -340,6 +340,7 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) { size_t shrink; ngx_quic_frame_t *nf; + ngx_quic_buffer_t qb; ngx_quic_ordered_frame_t *of, *onf; switch (f->type) { @@ -375,6 +376,14 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) return NGX_ERROR; } + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + qb.chain = f->data; + + f->data = ngx_quic_read_buffer(c, &qb, of->length); + if (f->data == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + nf = ngx_quic_alloc_frame(c); if (nf == NULL) { return NGX_ERROR; @@ -385,11 +394,7 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) onf->offset += of->length; onf->length = shrink; nf->len = ngx_quic_create_frame(NULL, nf); - - f->data = ngx_quic_read_chain(c, &nf->data, of->length); - if (f->data == NGX_CHAIN_ERROR) { - return NGX_ERROR; - } + nf->data = qb.chain; if (f->type == NGX_QUIC_FT_STREAM) { f->u.stream.fin = 0; @@ -402,13 +407,13 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) ngx_chain_t * -ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) +ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) { - off_t n; + uint64_t n; ngx_buf_t *b; ngx_chain_t *out, **ll; - out = *chain; + out = qb->chain; for (ll = &out; *ll; ll = &(*ll)->next) { b = (*ll)->buf; @@ -433,15 +438,53 @@ ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, off_t limit) } limit -= n; + qb->offset += n; } - *chain = *ll; + qb->chain = *ll; *ll = NULL; return out; } +void +ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + + while (qb->chain) { + if (qb->offset >= offset) { + break; + } + + cl = qb->chain; + b = cl->buf; + n = b->last - b->pos; + + if (qb->offset + n > offset) { + n = offset - qb->offset; + b->pos += n; + qb->offset += n; + break; + } + + qb->offset += n; + qb->chain = cl->next; + + cl->next = NULL; + ngx_quic_free_chain(c, cl); + } + + if (qb->chain == NULL) { + qb->offset = offset; + } +} + + ngx_chain_t * ngx_quic_alloc_chain(ngx_connection_t *c) { @@ -496,17 +539,16 @@ ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) ngx_chain_t * -ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, - off_t limit, off_t offset, size_t *size) +ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset) { - off_t n; u_char *p; + uint64_t n, base; ngx_buf_t *b; - ngx_chain_t *cl; + ngx_chain_t *cl, **chain; - if (size) { - *size = 0; - } + base = qb->offset; + chain = &qb->chain; while (in && limit) { cl = *chain; @@ -526,21 +568,21 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, b = cl->buf; n = b->last - b->pos; - if (n <= offset) { - offset -= n; + if (base + n <= offset) { + base += n; chain = &cl->next; continue; } - if (b->sync && offset > 0) { - if (ngx_quic_split_chain(c, cl, offset) != NGX_OK) { + if (b->sync && offset > base) { + if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) { return NGX_CHAIN_ERROR; } continue; } - p = b->pos + offset; + p = b->pos + (offset - base); while (in) { @@ -558,10 +600,7 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, if (b->sync) { ngx_memcpy(p, in->buf->pos, n); - - if (size) { - *size += n; - } + qb->size += n; } p += n; @@ -588,6 +627,15 @@ ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, ngx_chain_t *in, } +void +ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb) +{ + ngx_quic_free_chain(c, qb->chain); + + qb->chain = NULL; +} + + #if (NGX_DEBUG) void diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index b06575d4e..853e36ca1 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -28,10 +28,14 @@ ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); void ngx_quic_trim_chain(ngx_chain_t *in, size_t size); void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); -ngx_chain_t *ngx_quic_read_chain(ngx_connection_t *c, ngx_chain_t **chain, - off_t limit); -ngx_chain_t *ngx_quic_write_chain(ngx_connection_t *c, ngx_chain_t **chain, - ngx_chain_t *in, off_t limit, off_t offset, size_t *size); + +ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t limit); +ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset); +void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset); +void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb); #if (NGX_DEBUG) void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 126758253..8e01a8742 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -331,10 +331,8 @@ ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - size_t len; uint64_t last; - ngx_buf_t *b; - ngx_chain_t *cl, **ll; + ngx_chain_t *cl; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; ngx_quic_crypto_frame_t *f; @@ -346,12 +344,12 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, /* no overflow since both values are 62-bit */ last = f->offset + f->length; - if (last > ctx->crypto_received + NGX_QUIC_MAX_BUFFERED) { + if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) { qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; return NGX_ERROR; } - if (last <= ctx->crypto_received) { + if (last <= ctx->crypto.offset) { if (pkt->level == ssl_encryption_initial) { /* speeding up handshake completion */ @@ -368,45 +366,23 @@ ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - if (f->offset > ctx->crypto_received) { - if (ngx_quic_write_chain(c, &ctx->crypto, frame->data, f->length, - f->offset - ctx->crypto_received, NULL) - == NGX_CHAIN_ERROR) - { + if (f->offset == ctx->crypto.offset) { + if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { return NGX_ERROR; } - return NGX_OK; - } - - ngx_quic_trim_chain(frame->data, ctx->crypto_received - f->offset); - - if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) { - return NGX_ERROR; - } + ngx_quic_skip_buffer(c, &ctx->crypto, last); - ngx_quic_trim_chain(ctx->crypto, last - ctx->crypto_received); - ctx->crypto_received = last; - - cl = ctx->crypto; - ll = &cl; - len = 0; - - while (*ll) { - b = (*ll)->buf; - - if (b->sync && b->pos != b->last) { - /* hole */ - break; + } else { + if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length, + f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; } - - len += b->last - b->pos; - ll = &(*ll)->next; } - ctx->crypto_received += len; - ctx->crypto = *ll; - *ll = NULL; + cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); if (cl) { if (ngx_quic_crypto_input(c, cl) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 54ed051ca..1906bc695 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -261,8 +261,7 @@ ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) ngx_quic_queue_frame(qc, frame); - ngx_quic_free_chain(pc, qs->out); - qs->out = NULL; + ngx_quic_free_buffer(pc, &qs->send); return NGX_OK; } @@ -760,7 +759,7 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) return 0; } - in = ngx_quic_read_chain(pc, &qs->in, size); + in = ngx_quic_read_buffer(pc, &qs->recv, size); if (in == NGX_CHAIN_ERROR) { return NGX_ERROR; } @@ -835,8 +834,7 @@ ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) static ngx_chain_t * ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { - off_t flow; - size_t n; + uint64_t n, flow; ngx_event_t *wev; ngx_connection_t *pc; ngx_quic_stream_t *qs; @@ -863,25 +861,27 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) return in; } - if (limit == 0 || limit > flow) { + if (limit == 0 || limit > (off_t) flow) { limit = flow; } - in = ngx_quic_write_chain(pc, &qs->out, in, limit, - c->sent - qs->send_offset, &n); + n = qs->send.size; + + in = ngx_quic_write_buffer(pc, &qs->send, in, limit, c->sent); if (in == NGX_CHAIN_ERROR) { return NGX_CHAIN_ERROR; } + n = qs->send.size - n; c->sent += n; qc->streams.sent += n; - if (flow == (off_t) n) { + if (flow == n) { wev->ready = 0; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic send_chain sent:%uz", n); + "quic send_chain sent:%uL", n); if (ngx_quic_stream_flush(qs) != NGX_OK) { return NGX_CHAIN_ERROR; @@ -894,10 +894,9 @@ 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) { - off_t limit; - size_t len; + off_t limit, len; ngx_uint_t last; - ngx_chain_t *out, *cl; + ngx_chain_t *out; ngx_quic_frame_t *frame; ngx_connection_t *pc; ngx_quic_connection_t *qc; @@ -919,20 +918,18 @@ ngx_quic_stream_flush(ngx_quic_stream_t *qs) ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "quic stream id:0x%xL flush limit:%O", qs->id, limit); - out = ngx_quic_read_chain(pc, &qs->out, limit); + len = qs->send.offset; + + out = ngx_quic_read_buffer(pc, &qs->send, limit); if (out == NGX_CHAIN_ERROR) { return NGX_ERROR; } - len = 0; + len = qs->send.offset - len; last = 0; - for (cl = out; cl; cl = cl->next) { - len += cl->buf->last - cl->buf->pos; - } - if (qs->send_final_size != (uint64_t) -1 - && qs->send_final_size == qs->send_offset + len) + && qs->send_final_size == qs->send.offset) { qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; last = 1; @@ -965,7 +962,7 @@ ngx_quic_stream_flush(ngx_quic_stream_t *qs) qc->streams.send_offset += len; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, - "quic stream id:0x%xL flush len:%uz last:%ui", + "quic stream id:0x%xL flush len:%O last:%ui", qs->id, len, last); if (qs->connection == NULL) { @@ -1026,8 +1023,8 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, "quic stream id:0x%xL close", qs->id); - ngx_quic_free_chain(pc, qs->in); - ngx_quic_free_chain(pc, qs->out); + ngx_quic_free_buffer(pc, &qs->send); + ngx_quic_free_buffer(pc, &qs->recv); ngx_rbtree_delete(&qc->streams.tree, &qs->node); ngx_queue_insert_tail(&qc->streams.free, &qs->queue); @@ -1071,7 +1068,6 @@ ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_frame_t *frame) { - size_t size; uint64_t last; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1140,17 +1136,14 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; } - if (ngx_quic_write_chain(c, &qs->in, frame->data, f->length, - f->offset - qs->recv_offset, &size) + if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset) == NGX_CHAIN_ERROR) { return NGX_ERROR; } - qs->recv_size += size; - if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN - && qs->recv_size == qs->recv_final_size) + && qs->recv.size == qs->recv_final_size) { qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; } -- cgit v1.2.3 From 055025fa3b6d42d61ee99d8b6d26a0bf93444e85 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 14 Feb 2022 14:51:10 +0300 Subject: QUIC: trim input chain in ngx_quic_buffer_write(). This allows to eliminate explicit trimming when handling input STREAM frame. As a result, ngx_quic_trim_chain() is eliminated as well. --- src/event/quic/ngx_event_quic_frames.c | 36 +++++++++++++++------------------ src/event/quic/ngx_event_quic_frames.h | 1 - src/event/quic/ngx_event_quic_streams.c | 5 ----- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index b89072ea2..4a39141eb 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -258,26 +258,6 @@ ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) } -void -ngx_quic_trim_chain(ngx_chain_t *in, size_t size) -{ - size_t n; - ngx_buf_t *b; - - while (in && size > 0) { - b = in->buf; - n = ngx_min((size_t) (b->last - b->pos), size); - - b->pos += n; - size -= n; - - if (b->pos == b->last) { - in = in->next; - } - } -} - - void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) { @@ -551,6 +531,22 @@ ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, chain = &qb->chain; while (in && limit) { + + if (offset < base) { + n = ngx_min((uint64_t) (in->buf->last - in->buf->pos), + ngx_min(base - offset, limit)); + + in->buf->pos += n; + offset += n; + limit -= n; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + continue; + } + cl = *chain; if (cl == NULL) { diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 853e36ca1..48cb22e91 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -26,7 +26,6 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len); -void ngx_quic_trim_chain(ngx_chain_t *in, size_t size); void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 1906bc695..78650b04f 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1115,11 +1115,6 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return NGX_OK; } - if (f->offset < qs->recv_offset) { - ngx_quic_trim_chain(frame->data, qs->recv_offset - f->offset); - f->offset = qs->recv_offset; - } - if (f->fin) { if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) { -- cgit v1.2.3 From f15459fc466b12e3c1591b4d0a06c113f7a591c5 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 14 Feb 2022 14:53:46 +0300 Subject: QUIC: eliminated ngx_quic_copy_buf(). Its only call is substituted with QUIC buffer write/read pair. --- src/event/quic/ngx_event_quic_frames.c | 34 ---------------------------------- src/event/quic/ngx_event_quic_frames.h | 2 -- src/event/quic/ngx_event_quic_ssl.c | 29 +++++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 4a39141eb..c6d8de74e 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -484,40 +484,6 @@ ngx_quic_alloc_chain(ngx_connection_t *c) } -ngx_chain_t * -ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, size_t len) -{ - size_t n; - ngx_buf_t *b; - ngx_chain_t *cl, *out, **ll; - - out = NULL; - ll = &out; - - while (len) { - cl = ngx_quic_alloc_chain(c); - if (cl == NULL) { - return NGX_CHAIN_ERROR; - } - - b = cl->buf; - n = ngx_min((size_t) (b->end - b->last), len); - - b->last = ngx_cpymem(b->last, data, n); - - data += n; - len -= n; - - *ll = cl; - ll = &cl->next; - } - - *ll = NULL; - - return out; -} - - ngx_chain_t * ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ngx_chain_t *in, uint64_t limit, uint64_t offset) diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index 48cb22e91..fbff68e5d 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -24,8 +24,6 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len); ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); -ngx_chain_t *ngx_quic_copy_buf(ngx_connection_t *c, u_char *data, - size_t len); void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 8e01a8742..006566141 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -183,10 +183,13 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, { u_char *p, *end; size_t client_params_len; + ngx_buf_t buf; + ngx_chain_t *out, cl; const uint8_t *client_params; ngx_quic_tp_t ctp; ngx_quic_frame_t *frame; ngx_connection_t *c; + ngx_quic_buffer_t qb; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) @@ -263,16 +266,34 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ctx = ngx_quic_get_send_ctx(qc, level); - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = (u_char *) data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + + if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { return 0; } - frame->data = ngx_quic_copy_buf(c, (u_char *) data, len); - if (frame->data == NGX_CHAIN_ERROR) { + out = ngx_quic_read_buffer(c, &qb, len); + if (out == NGX_CHAIN_ERROR) { + return 0; + } + + ngx_quic_free_buffer(c, &qb); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { return 0; } + frame->data = out; frame->level = level; frame->type = NGX_QUIC_FT_CRYPTO; frame->u.crypto.offset = ctx->crypto_sent; -- cgit v1.2.3 From cac8623697fadd1a42134ca3af8d0c55c9b755b7 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 14 Feb 2022 14:54:34 +0300 Subject: QUIC: optimize insertion at the end of QUIC buffer. --- src/event/quic/ngx_event_quic.h | 2 ++ src/event/quic/ngx_event_quic_frames.c | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 8f6dd7e78..109cd54ef 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -51,7 +51,9 @@ typedef enum { typedef struct { uint64_t size; uint64_t offset; + uint64_t last_offset; ngx_chain_t *chain; + ngx_chain_t **last_chain; } ngx_quic_buffer_t; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index c6d8de74e..5ffae32c3 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -421,6 +421,10 @@ ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) qb->offset += n; } + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } + qb->chain = *ll; *ll = NULL; @@ -462,6 +466,10 @@ ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, if (qb->chain == NULL) { qb->offset = offset; } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } } @@ -493,8 +501,14 @@ ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, ngx_buf_t *b; ngx_chain_t *cl, **chain; - base = qb->offset; - chain = &qb->chain; + if (qb->last_chain && offset >= qb->last_offset) { + base = qb->last_offset; + chain = qb->last_chain; + + } else { + base = qb->offset; + chain = &qb->chain; + } while (in && limit) { @@ -585,6 +599,9 @@ ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, } } + qb->last_offset = base; + qb->last_chain = chain; + return in; } -- cgit v1.2.3 From 2526632f7172d46d54dbf9738b497d91b9543f50 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Feb 2022 15:45:47 +0300 Subject: QUIC: fixed indentation. --- src/event/quic/bpf/ngx_quic_reuseport_helper.c | 2 +- src/event/quic/ngx_event_quic.c | 8 ++++---- src/event/quic/ngx_event_quic_ack.c | 4 ++-- src/event/quic/ngx_event_quic_migration.c | 4 ++-- src/event/quic/ngx_event_quic_socket.c | 2 +- src/event/quic/ngx_event_quic_ssl.c | 16 ++++++++-------- src/event/quic/ngx_event_quic_tokens.c | 2 +- src/event/quic/ngx_event_quic_transport.c | 6 +++--- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/event/quic/bpf/ngx_quic_reuseport_helper.c index 2de9b2864..999e7607c 100644 --- a/src/event/quic/bpf/ngx_quic_reuseport_helper.c +++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -126,7 +126,7 @@ int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) default: debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", - rc, key); + rc, key); goto failed; } diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 8ef559dae..96884cd44 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -491,9 +491,9 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) /* this case also handles some errors from ngx_quic_run() */ - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close silent drain:%d timedout:%d", - qc->draining, c->read->timedout); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close silent drain:%d timedout:%d", + qc->draining, c->read->timedout); } else { /* @@ -816,7 +816,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, { /* packet comes from unknown path, possibly migration */ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic too early migration attempt"); + "quic too early migration attempt"); return NGX_DONE; } } diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 8b10e9e46..1d530c85a 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -834,7 +834,7 @@ ngx_quic_pto_handler(ngx_event_t *ev) for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { - ctx = &qc->send_ctx[i]; + ctx = &qc->send_ctx[i]; if (ngx_queue_empty(&ctx->sent)) { continue; @@ -1166,7 +1166,7 @@ ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) return NGX_OK; } - if (ctx->level == ssl_encryption_application) { + if (ctx->level == ssl_encryption_application) { delay = ngx_current_msec - ctx->ack_delay_start; qc = ngx_quic_get_connection(c); diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 6597923da..e07c53250 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -108,7 +108,7 @@ ngx_quic_handle_path_response_frame(ngx_connection_t *c, } ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stale PATH_RESPONSE ignored"); + "quic stale PATH_RESPONSE ignored"); return NGX_OK; @@ -387,7 +387,7 @@ ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) { size_t len; - ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); + ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); c->socklen = path->socklen; if (c->addr_text.data) { diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 44387fd3c..c9e1b603c 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -38,7 +38,7 @@ ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, 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; + return NGX_ERROR; } /* socket to use for further processing (id auto-generated) */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 006566141..41a0ccff8 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -212,16 +212,16 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) - SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); - if (alpn_len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; - qc->error_reason = "unsupported protocol in ALPN extension"; + if (alpn_len == 0) { + qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error_reason = "unsupported protocol in ALPN extension"; - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; - } + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } #endif diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index 5b1a9df46..b91134516 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -27,7 +27,7 @@ ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token) { - ngx_str_t tmp; + ngx_str_t tmp; tmp.data = secret; tmp.len = NGX_QUIC_SR_KEY_LEN; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index edd7c6b9f..660038461 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -588,7 +588,7 @@ ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) /* flags, version, dcid and scid with lengths and zero-length token */ len = 5 + 2 + pkt->dcid.len + pkt->scid.len - + (pkt->level == ssl_encryption_initial ? 1 : 0); + + (pkt->level == ssl_encryption_initial ? 1 : 0); if (len > pkt_len) { return 0; @@ -1052,7 +1052,7 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, goto error; } - p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); if (p == NULL) { goto error; } @@ -1555,7 +1555,7 @@ ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) u_char *start; ngx_uint_t type; - type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; if (p == NULL) { len = ngx_quic_varint_len(type); -- cgit v1.2.3 From c24c27bb074b571fc9e9d75e19be31b83c69c253 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 17 Feb 2022 22:38:42 +0300 Subject: QUIC: fixed insertion at the end of buffer. Previously, last buffer was tracked by keeping a pointer to the previous chain link "next" field. When the previous buffer was split and then removed, the pointer was no longer valid. Writing at this pointer resulted in broken data chains. Now last buffer is tracked by keeping a direct pointer to it. --- src/event/quic/ngx_event_quic.h | 2 +- src/event/quic/ngx_event_quic_frames.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 109cd54ef..903b690b9 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -53,7 +53,7 @@ typedef struct { uint64_t offset; uint64_t last_offset; ngx_chain_t *chain; - ngx_chain_t **last_chain; + ngx_chain_t *last_chain; } ngx_quic_buffer_t; diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 5ffae32c3..9a1a6afe5 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -503,7 +503,7 @@ ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, if (qb->last_chain && offset >= qb->last_offset) { base = qb->last_offset; - chain = qb->last_chain; + chain = &qb->last_chain; } else { base = qb->offset; @@ -600,7 +600,7 @@ ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, } qb->last_offset = base; - qb->last_chain = chain; + qb->last_chain = *chain; return in; } -- cgit v1.2.3 From 9d81ef744cdaacf1e52bcaec4224d375af5ba59b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 20 Apr 2022 16:01:17 +0400 Subject: QUIC: separate UDP framework for QUIC. Previously, QUIC used the existing UDP framework, which was created for UDP in Stream. However the way QUIC connections are created and looked up is different from the way UDP connections in Stream are created and looked up. Now these two implementations are decoupled. --- auto/modules | 1 + src/core/ngx_connection.c | 4 - src/event/ngx_event.c | 12 +- src/event/ngx_event_udp.c | 173 ++++------- src/event/ngx_event_udp.h | 17 +- src/event/quic/ngx_event_quic.c | 8 +- src/event/quic/ngx_event_quic.h | 3 + src/event/quic/ngx_event_quic_connection.h | 2 + src/event/quic/ngx_event_quic_migration.c | 7 +- src/event/quic/ngx_event_quic_socket.c | 5 +- src/event/quic/ngx_event_quic_udp.c | 473 +++++++++++++++++++++++++++++ src/http/ngx_http.c | 7 + src/stream/ngx_stream.c | 15 + 13 files changed, 585 insertions(+), 142 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_udp.c diff --git a/auto/modules b/auto/modules index 19967fa40..575fff0b7 100644 --- a/auto/modules +++ b/auto/modules @@ -1344,6 +1344,7 @@ if [ $USE_OPENSSL_QUIC = YES ]; then src/event/quic/ngx_event_quic_output.h \ src/event/quic/ngx_event_quic_socket.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_udp.c \ src/event/quic/ngx_event_quic_transport.c \ src/event/quic/ngx_event_quic_protection.c \ src/event/quic/ngx_event_quic_frames.c \ diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 4a7d2fe79..cbd5803be 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -72,10 +72,6 @@ ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr, ngx_memcpy(ls->addr_text.data, text, len); -#if !(NGX_WIN32) - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value); -#endif - ls->fd = (ngx_socket_t) -1; ls->type = SOCK_STREAM; diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index aebc4541d..284aafbe6 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -886,8 +886,16 @@ ngx_event_process_init(ngx_cycle_t *cycle) #else - rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept - : ngx_event_recvmsg; + if (c->type == SOCK_STREAM) { + rev->handler = ngx_event_accept; + +#if (NGX_QUIC) + } else if (ls[i].quic) { + rev->handler = ngx_quic_recvmsg; +#endif + } else { + rev->handler = ngx_event_recvmsg; + } #if (NGX_HAVE_REUSEPORT) diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index 1053fa0ac..a7fa38aa8 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -15,27 +15,25 @@ static void ngx_close_accepted_udp_connection(ngx_connection_t *c); static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); -static ngx_int_t ngx_create_udp_connection(ngx_connection_t *c); +static ngx_int_t ngx_insert_udp_connection(ngx_connection_t *c); static ngx_connection_t *ngx_lookup_udp_connection(ngx_listening_t *ls, - ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + struct sockaddr *sockaddr, socklen_t socklen, + struct sockaddr *local_sockaddr, socklen_t local_socklen); void ngx_event_recvmsg(ngx_event_t *ev) { - size_t len; ssize_t n; - ngx_str_t key; ngx_buf_t buf; ngx_log_t *log; ngx_err_t err; - socklen_t local_socklen; + socklen_t socklen, local_socklen; ngx_event_t *rev, *wev; struct iovec iov[1]; struct msghdr msg; ngx_sockaddr_t sa, lsa; - ngx_udp_dgram_t dgram; - struct sockaddr *local_sockaddr; + struct sockaddr *sockaddr, *local_sockaddr; ngx_listening_t *ls; ngx_event_conf_t *ecf; ngx_connection_t *c, *lc; @@ -110,21 +108,21 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - dgram.sockaddr = msg.msg_name; - dgram.socklen = msg.msg_namelen; + sockaddr = msg.msg_name; + socklen = msg.msg_namelen; - if (dgram.socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { - dgram.socklen = sizeof(ngx_sockaddr_t); + if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + socklen = sizeof(ngx_sockaddr_t); } - if (dgram.socklen == 0) { + if (socklen == 0) { /* * on Linux recvmsg() returns zero msg_namelen * when receiving packets from unbound AF_UNIX sockets */ - dgram.socklen = sizeof(struct sockaddr); + socklen = sizeof(struct sockaddr); ngx_memzero(&sa, sizeof(struct sockaddr)); sa.sockaddr.sa_family = ls->sockaddr->sa_family; } @@ -152,35 +150,8 @@ ngx_event_recvmsg(ngx_event_t *ev) #endif - key.data = (u_char *) dgram.sockaddr; - key.len = dgram.socklen; - -#if (NGX_HAVE_UNIX_DOMAIN) - - if (dgram.sockaddr->sa_family == AF_UNIX) { - struct sockaddr_un *saun = (struct sockaddr_un *) dgram.sockaddr; - - if (dgram.socklen <= (socklen_t) offsetof(struct sockaddr_un, - sun_path) - || saun->sun_path[0] == '\0') - { - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, - "unbound unix socket"); - key.len = 0; - } - } - -#endif - -#if (NGX_QUIC) - if (ls->quic) { - if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { - goto next; - } - } -#endif - - c = ngx_lookup_udp_connection(ls, &key, local_sockaddr, local_socklen); + c = ngx_lookup_udp_connection(ls, sockaddr, socklen, local_sockaddr, + local_socklen); if (c) { @@ -202,14 +173,10 @@ ngx_event_recvmsg(ngx_event_t *ev) buf.pos = buffer; buf.last = buffer + n; - buf.start = buf.pos; - buf.end = buffer + sizeof(buffer); rev = c->read; - dgram.buffer = &buf; - - c->udp->dgram = &dgram; + c->udp->buffer = &buf; rev->ready = 1; rev->active = 0; @@ -217,7 +184,7 @@ ngx_event_recvmsg(ngx_event_t *ev) rev->handler(rev); if (c->udp) { - c->udp->dgram = NULL; + c->udp->buffer = NULL; } rev->ready = 0; @@ -240,7 +207,7 @@ ngx_event_recvmsg(ngx_event_t *ev) c->shared = 1; c->type = SOCK_DGRAM; - c->socklen = dgram.socklen; + c->socklen = socklen; #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, 1); @@ -252,21 +219,13 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } - len = dgram.socklen; - -#if (NGX_QUIC) - if (ls->quic) { - len = NGX_SOCKADDRLEN; - } -#endif - - c->sockaddr = ngx_palloc(c->pool, len); + c->sockaddr = ngx_palloc(c->pool, socklen); if (c->sockaddr == NULL) { ngx_close_accepted_udp_connection(c); return; } - ngx_memcpy(c->sockaddr, dgram.sockaddr, dgram.socklen); + ngx_memcpy(c->sockaddr, sockaddr, socklen); log = ngx_palloc(c->pool, sizeof(ngx_log_t)); if (log == NULL) { @@ -369,7 +328,7 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - if (ngx_create_udp_connection(c) != NGX_OK) { + if (ngx_insert_udp_connection(c) != NGX_OK) { ngx_close_accepted_udp_connection(c); return; } @@ -412,17 +371,17 @@ ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size) ssize_t n; ngx_buf_t *b; - if (c->udp == NULL || c->udp->dgram == NULL) { + if (c->udp == NULL || c->udp->buffer == NULL) { return NGX_AGAIN; } - b = c->udp->dgram->buffer; + b = c->udp->buffer; n = ngx_min(b->last - b->pos, (ssize_t) size); ngx_memcpy(buf, b->pos, n); - c->udp->dgram = NULL; + c->udp->buffer = NULL; c->read->ready = 0; c->read->active = 1; @@ -458,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_memn2cmp(udp->key.data, udpt->key.data, - udp->key.len, udpt->key.len); + rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, + ct->sockaddr, ct->socklen, 1); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -485,18 +444,12 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, static ngx_int_t -ngx_create_udp_connection(ngx_connection_t *c) +ngx_insert_udp_connection(ngx_connection_t *c) { - ngx_str_t key; + uint32_t hash; ngx_pool_cleanup_t *cln; ngx_udp_connection_t *udp; -#if (NGX_QUIC) - if (c->listening->quic) { - return NGX_OK; - } -#endif - if (c->udp) { return NGX_OK; } @@ -506,34 +459,10 @@ ngx_create_udp_connection(ngx_connection_t *c) return NGX_ERROR; } - cln = ngx_pool_cleanup_add(c->pool, 0); - if (cln == NULL) { - return NGX_ERROR; - } - - cln->data = c; - cln->handler = ngx_delete_udp_connection; - - key.data = (u_char *) c->sockaddr; - key.len = c->socklen; - - ngx_insert_udp_connection(c, udp, &key); - - c->udp = udp; - - return NGX_OK; -} - - -void -ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, - ngx_str_t *key) -{ - uint32_t hash; + udp->connection = c; ngx_crc32_init(hash); - - ngx_crc32_update(&hash, key->data, key->len); + ngx_crc32_update(&hash, (u_char *) c->sockaddr, c->socklen); if (c->listening->wildcard) { ngx_crc32_update(&hash, (u_char *) c->local_sockaddr, c->local_socklen); @@ -541,11 +470,21 @@ ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, ngx_crc32_final(hash); - udp->connection = c; - udp->key = *key; udp->node.key = hash; + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->data = c; + cln->handler = ngx_delete_udp_connection; + ngx_rbtree_insert(&c->listening->rbtree, &udp->node); + + c->udp = udp; + + return NGX_OK; } @@ -565,8 +504,8 @@ ngx_delete_udp_connection(void *data) static ngx_connection_t * -ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, - struct sockaddr *local_sockaddr, socklen_t local_socklen) +ngx_lookup_udp_connection(ngx_listening_t *ls, struct sockaddr *sockaddr, + socklen_t socklen, struct sockaddr *local_sockaddr, socklen_t local_socklen) { uint32_t hash; ngx_int_t rc; @@ -574,15 +513,27 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, ngx_rbtree_node_t *node, *sentinel; ngx_udp_connection_t *udp; - if (key->len == 0) { - return NULL; +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + return NULL; + } } +#endif + node = ls->rbtree.root; sentinel = ls->rbtree.sentinel; ngx_crc32_init(hash); - ngx_crc32_update(&hash, key->data, key->len); + ngx_crc32_update(&hash, (u_char *) sockaddr, socklen); if (ls->wildcard) { ngx_crc32_update(&hash, (u_char *) local_sockaddr, local_socklen); @@ -608,7 +559,8 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, c = udp->connection; - rc = ngx_memn2cmp(key->data, udp->key.data, key->len, udp->key.len); + rc = ngx_cmp_sockaddr(sockaddr, socklen, + c->sockaddr, c->socklen, 1); if (rc == 0 && ls->wildcard) { rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, @@ -616,13 +568,6 @@ ngx_lookup_udp_connection(ngx_listening_t *ls, ngx_str_t *key, } if (rc == 0) { - -#if (NGX_QUIC) - if (ls->quic && c->udp != udp) { - c->udp = udp; - } -#endif - return c; } diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h index b5ceeca3f..d7a64ee03 100644 --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -23,18 +23,10 @@ #endif -typedef struct { - ngx_buf_t *buffer; - struct sockaddr *sockaddr; - socklen_t socklen; -} ngx_udp_dgram_t; - - struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_str_t key; - ngx_udp_dgram_t *dgram; + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_buf_t *buffer; }; @@ -65,9 +57,6 @@ void ngx_event_recvmsg(ngx_event_t *ev); ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags); void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -void ngx_insert_udp_connection(ngx_connection_t *c, ngx_udp_connection_t *udp, - ngx_str_t *key); - #endif void ngx_delete_udp_connection(void *data); diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 96884cd44..834e7935f 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -430,7 +430,7 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - b = c->udp->dgram->buffer; + b = c->udp->buffer; rc = ngx_quic_handle_datagram(c, b, NULL); @@ -758,6 +758,7 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt) { ngx_int_t rc; + ngx_quic_socket_t *qsock; ngx_quic_connection_t *qc; c->log->action = "parsing quic packet"; @@ -809,8 +810,9 @@ ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, } if (pkt->first) { - if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, - c->udp->dgram->socklen, + qsock = ngx_quic_get_socket(c); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, qc->path->sockaddr, qc->path->socklen, 1) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 903b690b9..92c9ecae0 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -102,6 +102,9 @@ struct ngx_quic_stream_s { }; +void ngx_quic_recvmsg(ngx_event_t *ev); +void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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, diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 377b26bd6..2b198ac6f 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -107,6 +107,8 @@ struct ngx_quic_socket_s { ngx_quic_connection_t *quic; ngx_queue_t queue; ngx_quic_server_id_t sid; + ngx_sockaddr_t sockaddr; + socklen_t socklen; ngx_uint_t used; /* unsigned used:1; */ }; diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index e07c53250..36a2af73c 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -264,7 +264,7 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) len = pkt->raw->last - pkt->raw->start; - if (c->udp->dgram == NULL) { + if (c->udp->buffer == NULL) { /* first ever packet in connection, path already exists */ path = qc->path; goto update; @@ -278,7 +278,7 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) { path = ngx_queue_data(q, ngx_quic_path_t, queue); - if (ngx_cmp_sockaddr(c->udp->dgram->sockaddr, c->udp->dgram->socklen, + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, path->sockaddr, path->socklen, 1) == NGX_OK) { @@ -315,8 +315,7 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) return NGX_DONE; } - path = ngx_quic_new_path(c, c->udp->dgram->sockaddr, - c->udp->dgram->socklen, cid); + path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid); if (path == NULL) { return NGX_ERROR; } diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index c9e1b603c..6813fcd0a 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -177,7 +177,10 @@ ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, id.data = sid->id; id.len = sid->len; - ngx_insert_udp_connection(c, &qsock->udp, &id); + qsock->udp.connection = c; + qsock->udp.node.key = ngx_crc32_long(id.data, id.len); + + ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); ngx_queue_insert_tail(&qc->sockets, &qsock->queue); diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c new file mode 100644 index 000000000..db6377371 --- /dev/null +++ b/src/event/quic/ngx_event_quic_udp.c @@ -0,0 +1,473 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static void ngx_quic_close_accepted_connection(ngx_connection_t *c); +static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls, + ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + + +void +ngx_quic_recvmsg(ngx_event_t *ev) +{ + ssize_t n; + ngx_str_t key; + ngx_buf_t buf; + ngx_log_t *log; + ngx_err_t err; + socklen_t socklen, local_socklen; + ngx_event_t *rev, *wev; + struct iovec iov[1]; + struct msghdr msg; + ngx_sockaddr_t sa, lsa; + struct sockaddr *sockaddr, *local_sockaddr; + ngx_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + ngx_quic_socket_t *qsock; + static u_char buffer[65535]; + +#if (NGX_HAVE_ADDRINFO_CMSG) + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + if (ev->timedout) { + if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { + return; + } + + ev->timedout = 0; + } + + ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); + + if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { + ev->available = ecf->multi_accept; + } + + lc = ev->data; + ls = lc->listening; + ev->ready = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic recvmsg on %V, ready: %d", + &ls->addr_text, ev->available); + + do { + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov[0].iov_base = (void *) buffer; + iov[0].iov_len = sizeof(buffer); + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(ngx_sockaddr_t); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); + + ngx_memzero(&msg_control, sizeof(msg_control)); + } +#endif + + n = recvmsg(lc->fd, &msg, 0); + + if (n == -1) { + err = ngx_socket_errno; + + if (err == NGX_EAGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, + "quic recvmsg() not ready"); + return; + } + + ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed"); + + return; + } + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "quic recvmsg() truncated data"); + continue; + } +#endif + + sockaddr = msg.msg_name; + socklen = msg.msg_namelen; + + if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + socklen = sizeof(ngx_sockaddr_t); + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + goto next; + } + } + +#endif + + local_sockaddr = ls->sockaddr; + local_socklen = ls->socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + + if (ls->wildcard) { + struct cmsghdr *cmsg; + + ngx_memcpy(&lsa, local_sockaddr, local_socklen); + local_sockaddr = &lsa.sockaddr; + + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { + break; + } + } + } + +#endif + + if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { + goto next; + } + + c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen); + + if (c) { + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + ngx_log_handler_pt handler; + + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recvmsg: fd:%d n:%z", c->fd, n); + + c->log->handler = handler; + } +#endif + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = buffer; + buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); + + qsock = ngx_quic_get_socket(c); + + ngx_memcpy(&qsock->sockaddr.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; + } + + rev->ready = 0; + rev->active = 1; + + goto next; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); +#endif + + ngx_accept_disabled = ngx_cycle->connection_n / 8 + - ngx_cycle->free_connection_n; + + c = ngx_get_connection(lc->fd, ev->log); + if (c == NULL) { + return; + } + + c->shared = 1; + c->type = SOCK_DGRAM; + c->socklen = socklen; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + c->pool = ngx_create_pool(ls->pool_size, ev->log); + if (c->pool == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); + if (c->sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(c->sockaddr, sockaddr, socklen); + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + *log = ls->log; + + c->log = log; + c->pool->log = log; + c->listening = ls; + + if (local_sockaddr == &lsa.sockaddr) { + local_sockaddr = ngx_palloc(c->pool, local_socklen); + if (local_sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(local_sockaddr, &lsa, local_socklen); + } + + c->local_sockaddr = local_sockaddr; + c->local_socklen = local_socklen; + + c->buffer = ngx_create_temp_buf(c->pool, n); + if (c->buffer == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n); + + rev = c->read; + wev = c->write; + + rev->active = 1; + wev->ready = 1; + + rev->log = log; + wev->log = log; + + /* + * TODO: MT: - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + * + * TODO: MP: - allocated in a shared memory + * - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + */ + + 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 + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + ls->addr_text_max_len, 0); + if (c->addr_text.len == 0) { + ngx_quic_close_accepted_connection(c); + return; + } + } + +#if (NGX_DEBUG) + { + ngx_str_t addr; + u_char text[NGX_SOCKADDR_STRLEN]; + + ngx_debug_accepted_connection(ecf, c); + + if (log->log_level & NGX_LOG_DEBUG_EVENT) { + addr.data = text; + addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, + "*%uA quic recvmsg: %V fd:%d n:%z", + c->number, &addr, c->fd, n); + } + + } +#endif + + log->data = NULL; + log->handler = NULL; + + ls->handler(c); + + next: + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + ev->available -= n; + } + + } while (ev->available); +} + + +static void +ngx_quic_close_accepted_connection(ngx_connection_t *c) +{ + ngx_free_connection(c); + + c->fd = (ngx_socket_t) -1; + + if (c->pool) { + ngx_destroy_pool(c->pool); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif +} + + +void +ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_int_t rc; + ngx_connection_t *c, *ct; + ngx_rbtree_node_t **p; + ngx_quic_socket_t *qsock, *qsockt; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + qsock = (ngx_quic_socket_t *) node; + c = qsock->udp.connection; + + qsockt = (ngx_quic_socket_t *) temp; + ct = qsockt->udp.connection; + + rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id, + qsock->sid.len, qsockt->sid.len); + + if (rc == 0 && c->listening->wildcard) { + rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, + ct->local_sockaddr, ct->local_socklen, 1); + } + + p = (rc < 0) ? &temp->left : &temp->right; + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +static ngx_connection_t * +ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, + struct sockaddr *local_sockaddr, socklen_t local_socklen) +{ + uint32_t hash; + ngx_int_t rc; + ngx_connection_t *c; + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_socket_t *qsock; + + if (key->len == 0) { + return NULL; + } + + node = ls->rbtree.root; + sentinel = ls->rbtree.sentinel; + hash = ngx_crc32_long(key->data, key->len); + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + qsock = (ngx_quic_socket_t *) node; + + rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len); + + c = qsock->udp.connection; + + if (rc == 0 && ls->wildcard) { + rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, + c->local_sockaddr, c->local_socklen, 1); + } + + if (rc == 0) { + c->udp = &qsock->udp; + return c; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 082a849a2..134eed944 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1822,7 +1822,14 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; #if (NGX_HTTP_V3) + ls->quic = addr->opt.http3; + + if (ls->quic) { + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, + ngx_quic_rbtree_insert_value); + } + #endif return ls; diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index a1a82f95a..4c41af173 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -519,8 +519,23 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) #endif #if (NGX_STREAM_QUIC) + ls->quic = addr[i].opt.quic; + + if (ls->quic) { + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, + ngx_quic_rbtree_insert_value); + } + #endif + +#if !(NGX_WIN32) + if (!ls->quic) { + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, + ngx_udp_rbtree_insert_value); + } +#endif + stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); if (stport == NULL) { return NGX_CONF_ERROR; -- cgit v1.2.3 From 7123ff29167b3c51643438c33c4f9a9a39dd22a6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 26 May 2022 16:17:56 +0400 Subject: HTTP/3: require that field section base index is not negative. RFC 9204 explicitly requires that. --- src/http/v3/ngx_http_v3_parse.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index cd70bd3bf..7dc53493c 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -474,7 +474,13 @@ done: } 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; } -- cgit v1.2.3 From a1f32e5871823aeb2e03dcf8a67b00ddff64f478 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 8 Jun 2022 15:30:08 +0400 Subject: README: updated after HTTP/3 RFC publication, minor refinements. --- README | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README b/README index f5994e8e6..f1c399b07 100644 --- a/README +++ b/README @@ -64,7 +64,7 @@ Experimental QUIC support for nginx -L../boringssl/build/crypto" $ make - Alternatively, nginx can be configured with QuicTLS [9] + Alternatively, nginx can be configured with QuicTLS [5] $ ./auto/configure --with-debug --with-http_v3_module \ --with-cc-opt="-I../quictls/build/include" \ @@ -103,7 +103,7 @@ Experimental QUIC support for nginx quic_gso on; - To limit maximum packet size: + To limit maximum UDP payload size on receive path: quic_mtu ; @@ -112,7 +112,7 @@ Experimental QUIC support for nginx quic_host_key ; - By default this Linux-specific optimization [8] is disabled. + By default, GSO Linux-specific optimization [8] is disabled. Enable if your network interface is configured to support GSO. A number of directives were added that configure HTTP/3: @@ -223,11 +223,10 @@ Example configuration: 7. Links [1] https://datatracker.ietf.org/doc/html/rfc9000 - [2] https://datatracker.ietf.org/doc/html/draft-ietf-quic-http + [2] https://datatracker.ietf.org/doc/html/rfc9114 [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/ [4] https://boringssl.googlesource.com/boringssl/ - [5] https://datatracker.ietf.org/doc/html/rfc9002 + [5] https://github.com/quictls/openssl [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen [7] https://nginx.org/en/docs/debugging_log.html [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf - [9] https://github.com/quictls/openssl -- cgit v1.2.3 From 854e41fec24e1f292ec5a951e7bfc9377afc0905 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 8 Jun 2022 16:19:01 +0400 Subject: HTTP/3: updated SETTINGS_MAX_FIELD_SECTION_SIZE name. --- src/http/v3/ngx_http_v3.h | 2 +- src/http/v3/ngx_http_v3_table.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index b54d9aee0..a3544ebc4 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -39,7 +39,7 @@ #define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d #define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 -#define NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE 0x06 +#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06 #define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 #define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index 22dc37901..7e07a15b9 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -699,9 +699,10 @@ ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); break; - case NGX_HTTP_V3_PARAM_MAX_HEADER_LIST_SIZE: + case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 param SETTINGS_MAX_HEADER_LIST_SIZE:%uL", value); + "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL", + value); break; case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: -- cgit v1.2.3 From 16bb8459d0118a7745751efd3388e117ee69146c Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 27 Jul 2022 17:15:33 +0400 Subject: QUIC: fixed-length buffers for secrets. --- src/event/quic/ngx_event_quic_protection.c | 187 ++++++++++++++--------------- 1 file changed, 89 insertions(+), 98 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index bbf85af9a..61ca9994f 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -17,6 +17,9 @@ #define NGX_QUIC_AES_128_KEY_LEN 16 +/* largest hash used in TLS is SHA-384 */ +#define NGX_QUIC_MAX_MD_SIZE 48 + #define NGX_AES_128_GCM_SHA256 0x1301 #define NGX_AES_256_GCM_SHA384 0x1302 #define NGX_CHACHA20_POLY1305_SHA256 0x1303 @@ -29,6 +32,18 @@ #endif +typedef struct { + size_t len; + u_char data[NGX_QUIC_MAX_MD_SIZE]; +} ngx_quic_md_t; + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_IV_LEN]; +} ngx_quic_iv_t; + + typedef struct { const ngx_quic_cipher_t *c; const EVP_CIPHER *hp; @@ -37,10 +52,10 @@ typedef struct { typedef struct ngx_quic_secret_s { - ngx_str_t secret; - ngx_str_t key; - ngx_str_t iv; - ngx_str_t hp; + ngx_quic_md_t secret; + ngx_quic_md_t key; + ngx_quic_iv_t iv; + ngx_quic_md_t hp; } ngx_quic_secret_t; @@ -57,6 +72,25 @@ struct ngx_quic_keys_s { }; +typedef struct { + size_t out_len; + u_char *out; + + size_t prk_len; + const uint8_t *prk; + + size_t label_len; + const u_char *label; +} ngx_quic_hkdf_t; + +#define ngx_quic_hkdf_set(label, out, prk) \ + { \ + (out)->len, (out)->data, \ + (prk)->len, (prk)->data, \ + (sizeof(label) - 1), (u_char *)(label), \ + } + + static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len); @@ -78,8 +112,8 @@ static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); -static ngx_int_t ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, - ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len); +static ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, + const EVP_MD *digest, ngx_pool_t *pool); static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -204,28 +238,20 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, client->iv.len = NGX_QUIC_IV_LEN; server->iv.len = NGX_QUIC_IV_LEN; - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *prk; - } seq[] = { + ngx_quic_hkdf_t seq[] = { /* labels per RFC 9001, 5.1. Packet Protection Keys */ - { ngx_string("tls13 client in"), &client->secret, &iss }, - { ngx_string("tls13 quic key"), &client->key, &client->secret }, - { ngx_string("tls13 quic iv"), &client->iv, &client->secret }, - { ngx_string("tls13 quic hp"), &client->hp, &client->secret }, - { ngx_string("tls13 server in"), &server->secret, &iss }, - { ngx_string("tls13 quic key"), &server->key, &server->secret }, - { ngx_string("tls13 quic iv"), &server->iv, &server->secret }, - { ngx_string("tls13 quic hp"), &server->hp, &server->secret }, + ngx_quic_hkdf_set("tls13 client in", &client->secret, &iss), + ngx_quic_hkdf_set("tls13 quic key", &client->key, &client->secret), + ngx_quic_hkdf_set("tls13 quic iv", &client->iv, &client->secret), + ngx_quic_hkdf_set("tls13 quic hp", &client->hp, &client->secret), + ngx_quic_hkdf_set("tls13 server in", &server->secret, &iss), + ngx_quic_hkdf_set("tls13 quic key", &server->key, &server->secret), + ngx_quic_hkdf_set("tls13 quic iv", &server->iv, &server->secret), + ngx_quic_hkdf_set("tls13 quic hp", &server->hp, &server->secret), }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(pool, digest, seq[i].key, &seq[i].label, - seq[i].prk->data, seq[i].prk->len) - != NGX_OK) - { + if (ngx_quic_hkdf_expand(&seq[i], digest, pool) != NGX_OK) { return NGX_ERROR; } } @@ -235,40 +261,34 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, static ngx_int_t -ngx_quic_hkdf_expand(ngx_pool_t *pool, const EVP_MD *digest, ngx_str_t *out, - ngx_str_t *label, const uint8_t *prk, size_t prk_len) +ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_pool_t *pool) { size_t info_len; uint8_t *p; uint8_t info[20]; - if (out->data == NULL) { - out->data = ngx_pnalloc(pool, out->len); - if (out->data == NULL) { - return NGX_ERROR; - } - } - - info_len = 2 + 1 + label->len + 1; + info_len = 2 + 1 + h->label_len + 1; info[0] = 0; - info[1] = out->len; - info[2] = label->len; - p = ngx_cpymem(&info[3], label->data, label->len); + info[1] = h->out_len; + info[2] = h->label_len; + + p = ngx_cpymem(&info[3], h->label, h->label_len); *p = '\0'; - if (ngx_hkdf_expand(out->data, out->len, digest, - prk, prk_len, info, info_len) + if (ngx_hkdf_expand(h->out, h->out_len, digest, + h->prk, h->prk_len, info, info_len) != NGX_OK) { ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, - "ngx_hkdf_expand(%V) failed", label); + "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); return NGX_ERROR; } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, - "quic expand %V key len:%uz %xV", label, out->len, out); + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pool->log, 0, + "quic expand \"%*s\" len:%uz %*xs", + h->label_len, h->label, h->out_len, h->out_len, h->out); #endif return NGX_OK; @@ -652,6 +672,7 @@ ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) { ngx_int_t key_len; + ngx_str_t secret_str; ngx_uint_t i; ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; @@ -668,8 +689,9 @@ ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, return NGX_ERROR; } - peer_secret->secret.data = ngx_pnalloc(pool, secret_len); - if (peer_secret->secret.data == NULL) { + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, pool->log, 0, + "unexpected secret len: %uz", secret_len); return NGX_ERROR; } @@ -680,22 +702,17 @@ ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, peer_secret->iv.len = NGX_QUIC_IV_LEN; peer_secret->hp.len = key_len; - struct { - ngx_str_t label; - ngx_str_t *key; - const uint8_t *secret; - } seq[] = { - { ngx_string("tls13 quic key"), &peer_secret->key, secret }, - { ngx_string("tls13 quic iv"), &peer_secret->iv, secret }, - { ngx_string("tls13 quic hp"), &peer_secret->hp, secret }, + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_t seq[] = { + ngx_quic_hkdf_set("tls13 quic key", &peer_secret->key, &secret_str), + ngx_quic_hkdf_set("tls13 quic iv", &peer_secret->iv, &secret_str), + ngx_quic_hkdf_set("tls13 quic hp", &peer_secret->hp, &secret_str), }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(pool, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret, secret_len) - != NGX_OK) - { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, pool) != NGX_OK) { return NGX_ERROR; } } @@ -769,49 +786,23 @@ ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) next->server.iv.len = NGX_QUIC_IV_LEN; next->server.hp = current->server.hp; - struct { - ngx_str_t label; - ngx_str_t *key; - ngx_str_t *secret; - } seq[] = { - { - ngx_string("tls13 quic ku"), - &next->client.secret, - ¤t->client.secret, - }, - { - ngx_string("tls13 quic key"), - &next->client.key, - &next->client.secret, - }, - { - ngx_string("tls13 quic iv"), - &next->client.iv, - &next->client.secret, - }, - { - ngx_string("tls13 quic ku"), - &next->server.secret, - ¤t->server.secret, - }, - { - ngx_string("tls13 quic key"), - &next->server.key, - &next->server.secret, - }, - { - ngx_string("tls13 quic iv"), - &next->server.iv, - &next->server.secret, - }, + ngx_quic_hkdf_t seq[] = { + ngx_quic_hkdf_set("tls13 quic ku", + &next->client.secret, ¤t->client.secret), + ngx_quic_hkdf_set("tls13 quic key", + &next->client.key, &next->client.secret), + ngx_quic_hkdf_set("tls13 quic iv", + &next->client.iv, &next->client.secret), + ngx_quic_hkdf_set("tls13 quic ku", + &next->server.secret, ¤t->server.secret), + ngx_quic_hkdf_set("tls13 quic key", + &next->server.key, &next->server.secret), + ngx_quic_hkdf_set("tls13 quic iv", + &next->server.iv, &next->server.secret), }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - - if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label, - seq[i].secret->data, seq[i].secret->len) - != NGX_OK) - { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->pool) != NGX_OK) { return NGX_ERROR; } } @@ -909,7 +900,7 @@ ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) } secret.key.len = sizeof(key); - secret.key.data = key; + ngx_memcpy(secret.key.data, key, sizeof(key)); secret.iv.len = NGX_QUIC_IV_LEN; if (ngx_quic_tls_seal(ciphers.c, &secret, &itag, nonce, &in, &ad, pkt->log) -- cgit v1.2.3 From 93c21be4d6184842b20541be190c630ec042b66a Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 27 Jul 2022 17:16:40 +0400 Subject: QUIC: avoided pool usage in ngx_quic_protection.c. --- src/event/quic/ngx_event_quic.c | 2 +- src/event/quic/ngx_event_quic_output.c | 2 +- src/event/quic/ngx_event_quic_protection.c | 30 +++++++++++++++--------------- src/event/quic/ngx_event_quic_protection.h | 6 +++--- src/event/quic/ngx_event_quic_ssl.c | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 834e7935f..e82fe129a 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -325,7 +325,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } } - if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &pkt->dcid) + if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) != NGX_OK) { return NULL; diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index b72d3319e..720b8fccc 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -961,7 +961,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - if (ngx_quic_keys_set_initial_secret(c->pool, pkt.keys, &inpkt->dcid) + if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, 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 61ca9994f..03f7b5c2b 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -113,7 +113,7 @@ static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); static ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, - const EVP_MD *digest, ngx_pool_t *pool); + const EVP_MD *digest, ngx_log_t *log); static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -179,8 +179,8 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ngx_int_t -ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, - ngx_str_t *secret) +ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, + ngx_log_t *log) { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; @@ -217,12 +217,12 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, .len = is_len }; - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pool->log, 0, + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic ngx_quic_set_initial_secret"); #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pool->log, 0, + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, "quic initial secret len:%uz %*xs", is_len, is_len, is); #endif @@ -251,7 +251,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(&seq[i], digest, pool) != NGX_OK) { + if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { return NGX_ERROR; } } @@ -261,7 +261,7 @@ ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys, static ngx_int_t -ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_pool_t *pool) +ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) { size_t info_len; uint8_t *p; @@ -280,13 +280,13 @@ ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_pool_t *pool) h->prk, h->prk_len, info, info_len) != NGX_OK) { - ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, + ngx_ssl_error(NGX_LOG_INFO, log, 0, "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); return NGX_ERROR; } #ifdef NGX_QUIC_DEBUG_CRYPTO - ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pool->log, 0, + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, "quic expand \"%*s\" len:%uz %*xs", h->label_len, h->label, h->out_len, h->out_len, h->out); #endif @@ -667,7 +667,7 @@ failed: ngx_int_t -ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, +ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) { @@ -685,12 +685,12 @@ ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); if (key_len == NGX_ERROR) { - ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher"); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); return NGX_ERROR; } if (sizeof(peer_secret->secret.data) < secret_len) { - ngx_log_error(NGX_LOG_ALERT, pool->log, 0, + ngx_log_error(NGX_LOG_ALERT, log, 0, "unexpected secret len: %uz", secret_len); return NGX_ERROR; } @@ -712,7 +712,7 @@ ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write, }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, pool) != NGX_OK) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { return NGX_ERROR; } } @@ -802,7 +802,7 @@ ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) }; for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { - if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->pool) != NGX_OK) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { return NGX_ERROR; } } diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index ff375b510..a9d721274 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -18,9 +18,9 @@ ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); -ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, - ngx_quic_keys_t *keys, ngx_str_t *secret); -ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, + ngx_str_t *secret, 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, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 41a0ccff8..aacea02a2 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -73,7 +73,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, secret_len, rsecret); #endif - if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, cipher, rsecret, secret_len) != NGX_OK) { @@ -109,7 +109,7 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, secret_len, wsecret); #endif - if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, cipher, wsecret, secret_len) != NGX_OK) { @@ -143,7 +143,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, cipher = SSL_get_current_cipher(ssl_conn); - if (ngx_quic_keys_set_encryption_secret(c->pool, 0, qc->keys, level, + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, cipher, rsecret, secret_len) != NGX_OK) { @@ -164,7 +164,7 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, secret_len, wsecret); #endif - if (ngx_quic_keys_set_encryption_secret(c->pool, 1, qc->keys, level, + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, cipher, wsecret, secret_len) != NGX_OK) { -- cgit v1.2.3 From 664cb29f5240768988cfa02834bebfddeb32cfc9 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Wed, 27 Jul 2022 17:31:16 +0400 Subject: QUIC: removed ngx_quic_keys_new(). The ngx_quic_keys_t structure is now exposed. --- src/event/quic/ngx_event_quic.c | 2 +- src/event/quic/ngx_event_quic_output.c | 8 +++--- src/event/quic/ngx_event_quic_protection.c | 45 ------------------------------ src/event/quic/ngx_event_quic_protection.h | 40 +++++++++++++++++++++++++- 4 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index e82fe129a..a1abd267d 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -238,7 +238,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } - qc->keys = ngx_quic_keys_new(c->pool); + qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); if (qc->keys == NULL) { return NULL; } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 720b8fccc..c656c527e 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -928,6 +928,7 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, { ssize_t len; ngx_str_t res; + ngx_quic_keys_t keys; ngx_quic_frame_t frame; ngx_quic_header_t pkt; @@ -956,10 +957,9 @@ ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, return NGX_ERROR; } - pkt.keys = ngx_quic_keys_new(c->pool); - if (pkt.keys == NULL) { - return NGX_ERROR; - } + ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); + + pkt.keys = &keys; if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) != NGX_OK) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 03f7b5c2b..2b6834988 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -10,16 +10,11 @@ #include -/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ -#define NGX_QUIC_IV_LEN 12 /* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ #define NGX_QUIC_HP_LEN 5 #define NGX_QUIC_AES_128_KEY_LEN 16 -/* largest hash used in TLS is SHA-384 */ -#define NGX_QUIC_MAX_MD_SIZE 48 - #define NGX_AES_128_GCM_SHA256 0x1301 #define NGX_AES_256_GCM_SHA384 0x1302 #define NGX_CHACHA20_POLY1305_SHA256 0x1303 @@ -32,18 +27,6 @@ #endif -typedef struct { - size_t len; - u_char data[NGX_QUIC_MAX_MD_SIZE]; -} ngx_quic_md_t; - - -typedef struct { - size_t len; - u_char data[NGX_QUIC_IV_LEN]; -} ngx_quic_iv_t; - - typedef struct { const ngx_quic_cipher_t *c; const EVP_CIPHER *hp; @@ -51,27 +34,6 @@ typedef struct { } ngx_quic_ciphers_t; -typedef struct ngx_quic_secret_s { - ngx_quic_md_t secret; - ngx_quic_md_t key; - ngx_quic_iv_t iv; - ngx_quic_md_t hp; -} ngx_quic_secret_t; - - -typedef struct { - ngx_quic_secret_t client; - ngx_quic_secret_t server; -} ngx_quic_secrets_t; - - -struct ngx_quic_keys_s { - ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; - ngx_quic_secrets_t next_key; - ngx_uint_t cipher; -}; - - typedef struct { size_t out_len; u_char *out; @@ -721,13 +683,6 @@ ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, } -ngx_quic_keys_t * -ngx_quic_keys_new(ngx_pool_t *pool) -{ - return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t)); -} - - ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, enum ssl_encryption_level_t level) diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index a9d721274..c8dc26bd1 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -16,8 +16,46 @@ #define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) +/* RFC 5116, 5.1 and RFC 8439, 2.3 for all supported ciphers */ +#define NGX_QUIC_IV_LEN 12 + +/* largest hash used in TLS is SHA-384 */ +#define NGX_QUIC_MAX_MD_SIZE 48 + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_MAX_MD_SIZE]; +} ngx_quic_md_t; + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_IV_LEN]; +} ngx_quic_iv_t; + + +typedef struct { + ngx_quic_md_t secret; + ngx_quic_md_t key; + ngx_quic_iv_t iv; + ngx_quic_md_t hp; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + -ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool); ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, ngx_log_t *log); ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, -- cgit v1.2.3 From 30ff0778544b4366867ac7ba8e3431b082f31ab4 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 31 May 2022 11:05:22 +0400 Subject: QUIC: avoided pool usage in token calculation. --- src/event/quic/ngx_event_quic_output.c | 13 +++++++++++-- src/event/quic/ngx_event_quic_tokens.c | 24 +++++++----------------- src/event/quic/ngx_event_quic_tokens.h | 14 +++++++++++++- src/event/quic/ngx_event_quic_transport.h | 1 + 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index c656c527e..ee64d555e 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1009,10 +1009,14 @@ ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; u_char dcid[NGX_QUIC_SERVER_CID_LEN]; + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, c->sockaddr, c->socklen, conf->av_token_key, + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key, &token, &inpkt->dcid, expires, 1) != NGX_OK) { @@ -1075,11 +1079,16 @@ ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + qc = ngx_quic_get_connection(c); expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, path->sockaddr, path->socklen, + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen, qc->conf->av_token_key, &token, NULL, expires, 0) != NGX_OK) { diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index b91134516..aeea2ed03 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -11,14 +11,6 @@ #include -#define NGX_QUIC_MAX_TOKEN_SIZE 64 - /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ - -/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */ -#define NGX_QUIC_AES_256_CBC_IV_LEN 16 -#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16 - - static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, ngx_uint_t no_port, u_char buf[20]); @@ -48,7 +40,7 @@ ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, +ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) { @@ -80,9 +72,9 @@ ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, cipher = EVP_aes_256_cbc(); iv_len = NGX_QUIC_AES_256_CBC_IV_LEN; - token->len = iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE; - token->data = ngx_pnalloc(c->pool, token->len); - if (token->data == NULL) { + if ((size_t) (iv_len + len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) > token->len) + { + ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small"); return NGX_ERROR; } @@ -119,7 +111,7 @@ ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, EVP_CIPHER_CTX_free(ctx); #ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, "quic new token len:%uz %xV", token->len, token); #endif @@ -268,10 +260,8 @@ ngx_quic_validate_token(ngx_connection_t *c, u_char *key, if (odcid.len) { pkt->odcid.len = odcid.len; - pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); - if (pkt->odcid.data == NULL) { - return NGX_ERROR; - } + pkt->odcid.data = pkt->odcid_buf; + ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len); } else { pkt->odcid = pkt->dcid; diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h index 25be94d65..4a40f7b3a 100644 --- a/src/event/quic/ngx_event_quic_tokens.h +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -12,9 +12,21 @@ #include +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + +/* RFC 3602, 2.1 and 2.4 for AES-CBC block size and IV length */ +#define NGX_QUIC_AES_256_CBC_IV_LEN 16 +#define NGX_QUIC_AES_256_CBC_BLOCK_SIZE 16 + +#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_CBC_IV_LEN \ + + NGX_QUIC_MAX_TOKEN_SIZE \ + + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) + + ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, u_char *token); -ngx_int_t ngx_quic_new_token(ngx_connection_t *c, struct sockaddr *sockaddr, +ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index a512ad802..6f95f85ad 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -322,6 +322,7 @@ typedef struct { /* cleartext fields */ ngx_str_t odcid; /* retry packet tag */ + u_char odcid_buf[NGX_QUIC_MAX_CID_LEN]; ngx_str_t dcid; ngx_str_t scid; uint64_t pn; -- cgit v1.2.3 From 5cde1259b2de257ce380813f03ef874c37632423 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 3 Aug 2022 16:59:51 +0400 Subject: HTTP/3: skip empty request body buffers (ticket #2374). When client DATA frame header and its content come in different QUIC packets, it may happen that only the header is processed by the first ngx_http_v3_request_body_filter() call. In this case an empty request body buffer is added to r->request_body->bufs, which is later reused in a subsequent ngx_http_v3_request_body_filter() call without being removed from the body chain. As a result, rb->request_body->bufs ends up with two copies of the same buffer. The fix is to avoid adding empty request body buffers to r->request_body->bufs. --- src/http/v3/ngx_http_v3_request.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4dbda3596..14802249b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1552,15 +1552,17 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } /* rc == NGX_OK */ - } - if (max != -1 && (uint64_t) (max - rb->received) < st->length) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "client intended to send too large " - "body: %O+%ui bytes", - rb->received, st->length); + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%ui bytes", + rb->received, st->length); - return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + continue; } if (b -- cgit v1.2.3 From e9051af79df0e5535b69c952137f1600c8fa4d40 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 12 Sep 2022 18:37:36 +0400 Subject: README: updated the current status. --- README | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README b/README index 7b7605728..4b9e14776 100644 --- a/README +++ b/README @@ -20,15 +20,13 @@ Experimental QUIC support for nginx The project code base is under the same BSD license as nginx. - The code is currently at a beta level of quality and should not - be used in production. + The code is currently at a beta level of quality, however + there are several production deployments with it. - We are working on improving HTTP/3 support with the goal of - integrating it to the main NGINX codebase. Expect frequent - updates of this code and don't rely on it for whatever purpose. - - We'll be grateful for any feedback and code submissions however - we don't bear any responsibilities for any issues with this code. + We are working on improving HTTP/3 support to integrate it into + the main NGINX codebase. Thus, expect further updates of this code, + including features, changes in behaviour, bug fixes, and refactoring. + We'll be grateful for any feedback and code submissions. You can always contact us via nginx-devel mailing list [3]. -- cgit v1.2.3 From fcba3d14581342b94a5e8da50d853066447afbdb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 30 Sep 2022 17:24:47 +0400 Subject: QUIC: "info" logging level on insufficient client connection ids. Apparently, this error is reported on NAT rebinding if client didn't previously send NEW_CONNECTION_ID to supply additional connection ids. --- src/event/quic/ngx_event_quic_migration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 36a2af73c..37c7b8675 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -309,7 +309,7 @@ ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) /* new path requires new client id */ cid = ngx_quic_next_client_id(c); if (cid == NULL) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic no available client ids for new path"); /* stop processing of this datagram */ return NGX_DONE; -- cgit v1.2.3 From 00468d71bf8cdbec28a44c92f59c5edf42d7739c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 20 Oct 2022 16:21:05 +0400 Subject: QUIC: using native TLSv1.3 cipher suite constants. After BoringSSL aligned[1] with OpenSSL on TLS1_3_CK_* macros, and LibreSSL uses OpenSSL naming, our own variants can be dropped now. Compatibility is preserved with libraries that lack these macros. Additionally, transition to SSL_CIPHER_get_id() fixes build error with LibreSSL that doesn't implement SSL_CIPHER_get_protocol_id(). [1] https://boringssl.googlesource.com/boringssl/+/dfddbc4ded --- src/event/quic/ngx_event_quic_protection.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 2b6834988..2216973e0 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -15,9 +15,12 @@ #define NGX_QUIC_AES_128_KEY_LEN 16 -#define NGX_AES_128_GCM_SHA256 0x1301 -#define NGX_AES_256_GCM_SHA384 0x1302 -#define NGX_CHACHA20_POLY1305_SHA256 0x1303 +#ifndef TLS1_3_CK_AES_128_GCM_SHA256 +#define TLS1_3_CK_AES_128_GCM_SHA256 0x03001301 +#define TLS1_3_CK_AES_256_GCM_SHA384 0x03001302 +#define TLS1_3_CK_CHACHA20_POLY1305_SHA256 \ + 0x03001303 +#endif #ifdef OPENSSL_IS_BORINGSSL @@ -90,12 +93,12 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, ngx_int_t len; if (level == ssl_encryption_initial) { - id = NGX_AES_128_GCM_SHA256; + id = TLS1_3_CK_AES_128_GCM_SHA256; } switch (id) { - case NGX_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_128_GCM_SHA256: #ifdef OPENSSL_IS_BORINGSSL ciphers->c = EVP_aead_aes_128_gcm(); #else @@ -106,7 +109,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, len = 16; break; - case NGX_AES_256_GCM_SHA384: + case TLS1_3_CK_AES_256_GCM_SHA384: #ifdef OPENSSL_IS_BORINGSSL ciphers->c = EVP_aead_aes_256_gcm(); #else @@ -117,7 +120,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, len = 32; break; - case NGX_CHACHA20_POLY1305_SHA256: + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: #ifdef OPENSSL_IS_BORINGSSL ciphers->c = EVP_aead_chacha20_poly1305(); #else @@ -642,7 +645,7 @@ ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, peer_secret = is_write ? &keys->secrets[level].server : &keys->secrets[level].client; - keys->cipher = SSL_CIPHER_get_protocol_id(cipher); + keys->cipher = SSL_CIPHER_get_id(cipher); key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); -- cgit v1.2.3 From 1ad1b85feb11285c5b65cc2d53c3f7ec54e6fabd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 20 Oct 2022 16:21:06 +0400 Subject: QUIC: using SSL_set_quic_early_data_enabled() only with QuicTLS. This function is present in QuicTLS only. After SSL_READ_EARLY_DATA_SUCCESS became visible in LibreSSL together with experimental QUIC API, this required to revise the conditional compilation test to use more narrow macros. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index aacea02a2..bb5809d58 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -557,7 +557,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } -#ifdef SSL_READ_EARLY_DATA_SUCCESS +#ifdef OPENSSL_INFO_QUIC if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { SSL_set_quic_early_data_enabled(ssl_conn, 1); } -- cgit v1.2.3 From ce65faea95598bac9c95686d1d211d9fcd0b1ee3 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 20 Oct 2022 16:21:06 +0400 Subject: QUIC: support for setting QUIC methods with LibreSSL. Setting QUIC methods is converted to use C99 designated initializers for simplicity, as LibreSSL 3.6.0 has different SSL_QUIC_METHOD layout. Additionally, only set_read_secret/set_write_secret callbacks are set. Although they are preferred in LibreSSL over set_encryption_secrets, better be on a safe side as LibreSSL has unexpectedly incompatible set_encryption_secrets calling convention expressed in passing read and write secrets split in separate calls, unlike this is documented in old BoringSSL sources. To avoid introducing further changes for the old API, it is simply disabled. --- src/event/quic/ngx_event_quic_ssl.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index bb5809d58..1eef2972d 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -18,7 +18,7 @@ #define NGX_QUIC_MAX_BUFFERED 65535 -#if BORINGSSL_API_VERSION >= 10 +#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); @@ -40,19 +40,19 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); static SSL_QUIC_METHOD quic_method = { -#if BORINGSSL_API_VERSION >= 10 - ngx_quic_set_read_secret, - ngx_quic_set_write_secret, +#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER + .set_read_secret = ngx_quic_set_read_secret, + .set_write_secret = ngx_quic_set_write_secret, #else - ngx_quic_set_encryption_secrets, + .set_encryption_secrets = ngx_quic_set_encryption_secrets, #endif - ngx_quic_add_handshake_data, - ngx_quic_flush_flight, - ngx_quic_send_alert, + .add_handshake_data = ngx_quic_add_handshake_data, + .flush_flight = ngx_quic_flush_flight, + .send_alert = ngx_quic_send_alert, }; -#if BORINGSSL_API_VERSION >= 10 +#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, -- cgit v1.2.3 From 34500dcac348ed6438c5feb995a9eece98ec27bb Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 20 Oct 2022 16:21:07 +0400 Subject: QUIC: removed compatibility with older BoringSSL API. SSL_CIPHER_get_protocol_id() appeared in BoringSSL somewhere between BORINGSSL_API_VERSION 12 and 13 for compatibility with OpenSSL 1.1.1. It was adopted without a proper macro test, which remained unnoticed. This justifies that such old BoringSSL API isn't widely used and its support can be dropped. While here, removed SSL_set_quic_use_legacy_codepoint() that became useless after the default was flipped in BoringSSL over a year ago. --- src/event/quic/ngx_event_quic_ssl.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 1eef2972d..2d9de48a5 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -18,7 +18,7 @@ #define NGX_QUIC_MAX_BUFFERED 65535 -#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER +#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); @@ -40,7 +40,7 @@ static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); static SSL_QUIC_METHOD quic_method = { -#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER +#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER .set_read_secret = ngx_quic_set_read_secret, .set_write_secret = ngx_quic_set_write_secret, #else @@ -52,7 +52,7 @@ static SSL_QUIC_METHOD quic_method = { }; -#if BORINGSSL_API_VERSION >= 10 || defined LIBRESSL_VERSION_NUMBER +#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, @@ -563,10 +563,6 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif -#if (BORINGSSL_API_VERSION >= 13 && BORINGSSL_API_VERSION < 15) - SSL_set_quic_use_legacy_codepoint(ssl_conn, 0); -#endif - qsock = ngx_quic_get_socket(c); dcid.data = qsock->sid.id; @@ -602,7 +598,7 @@ ngx_quic_init_connection(ngx_connection_t *c) return NGX_ERROR; } -#if BORINGSSL_API_VERSION >= 11 +#ifdef OPENSSL_IS_BORINGSSL if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic SSL_set_quic_early_data_context() failed"); -- cgit v1.2.3 From 3cd06a3a9a1f815cbd5391211947d7aed4feb061 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 20 Oct 2022 16:30:43 +0400 Subject: README: converted to passive voice, LibreSSL support. --- README | 80 +++++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/README b/README index 4b9e14776..7f545031a 100644 --- a/README +++ b/README @@ -23,16 +23,17 @@ Experimental QUIC support for nginx The code is currently at a beta level of quality, however there are several production deployments with it. - We are working on improving HTTP/3 support to integrate it into - the main NGINX codebase. Thus, expect further updates of this code, - including features, changes in behaviour, bug fixes, and refactoring. - We'll be grateful for any feedback and code submissions. + NGINX Development Team is working on improving HTTP/3 support to + integrate it into the main NGINX codebase. Thus, expect further + updates of this code, including features, changes in behaviour, + bug fixes, and refactoring. NGINX Development team will be + grateful for any feedback and code submissions. - You can always contact us via nginx-devel mailing list [3]. + Please contact NGINX Development Team via nginx-devel mailing list [3]. What works now: - We support IETF QUIC version 1. Internet drafts are no longer supported. + IETF QUIC version 1 is supported. Internet drafts are no longer supported. nginx should be able to respond to HTTP/3 requests over QUIC and it should be possible to upload and download big files without errors. @@ -52,24 +53,40 @@ Experimental QUIC support for nginx 2. Installing - You will need a BoringSSL [4] library that provides QUIC support + A library that provides QUIC support is required to build nginx, there + are several of those available on the market: + + BoringSSL [4] + + LibreSSL [5] + + QuicTLS [6] + + Clone the NGINX QUIC repository $ hg clone -b quic https://hg.nginx.org/nginx-quic $ cd nginx-quic + + Use the following command to configure nginx with BoringSSL [4] + $ ./auto/configure --with-debug --with-http_v3_module \ --with-cc-opt="-I../boringssl/include" \ --with-ld-opt="-L../boringssl/build/ssl \ -L../boringssl/build/crypto" $ make - Alternatively, nginx can be configured with QuicTLS [5] + Alternatively, nginx can be configured with QuicTLS [6] $ ./auto/configure --with-debug --with-http_v3_module \ --with-cc-opt="-I../quictls/build/include" \ --with-ld-opt="-L../quictls/build/lib" - When configuring nginx, you can enable QUIC and HTTP/3 using the - following new configuration options: + Alternatively, nginx can be configured with a modern version + of LibreSSL [7] + + $ ./auto/configure --with-debug --with-http_v3_module \ + --with-cc-opt="-I../libressl/build/include" \ + --with-ld-opt="-L../libressl/build/lib" + + When configuring nginx, it's possible to enable QUIC and HTTP/3 + using the following new configuration options: --with-http_v3_module - enable QUIC and HTTP/3 --with-stream_quic_module - enable QUIC in Stream @@ -82,8 +99,8 @@ Experimental QUIC support for nginx The Stream "listen" directive got a new option "quic" which enables QUIC as client transport protocol instead of TCP or plain UDP. - Along with "http3" or "quic", you also have to specify "reuseport" - option [6] to make it work properly with multiple workers. + Along with "http3" or "quic", it's also possible to specify "reuseport" + option [8] to make it work properly with multiple workers. To enable address validation: @@ -110,8 +127,9 @@ Experimental QUIC support for nginx quic_host_key ; - By default, GSO Linux-specific optimization [8] is disabled. - Enable if your network interface is configured to support GSO. + By default, GSO Linux-specific optimization [10] is disabled. + Enable it in case a corresponding network interface is configured to + support GSO. A number of directives were added that configure HTTP/3: @@ -164,7 +182,7 @@ Example configuration: Beware of strange issues: sometimes browser may decide to ignore QUIC Cache clearing/restart might help. Always check access.log and - error.log to make sure you are using HTTP/3 and not TCP https. + error.log to make sure the browser is using HTTP/3 and not TCP https. * Console clients @@ -177,7 +195,7 @@ Example configuration: $ chromium-build/out/my_build/quic_client http://example.com:8443 - If you've got it right, in the access log you should see something like: + In case everyhing is right, the access log should show something like: 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" "nghttp3/ngtcp2 client" "quic" @@ -185,28 +203,28 @@ Example configuration: 5. Troubleshooting - Here are some tips that may help you to identify problems: + Here are some tips that may help to identify problems: - + Ensure you are building with proper SSL library that supports QUIC + + Ensure nginx is built with proper SSL library that supports QUIC - + Ensure you are using the proper SSL library in runtime - (`nginx -V` will show you what you are using) + + Ensure nginx is using the proper SSL library in runtime + (`nginx -V` shows what it's using) - + Ensure your client is actually sending requests over QUIC + + Ensure a client is actually sending requests over QUIC (see "Clients" section about browsers and cache) We recommend to start with simple console client like ngtcp2 - to ensure you've got server configured properly before trying + to ensure the server is configured properly before trying with real browsers that may be very picky with certificates, for example. - + Build nginx with debug support [7] and check your debug log. + + Build nginx with debug support [9] and check the debug log. It should contain all details about connection and why it failed. All related messages contain "quic " prefix and can be easily filtered out. - + If you want to investigate deeper, you may want to enable - additional debugging in src/event/quic/ngx_event_quic_connection.h: + + For a deeper investigation, please enable additional debugging + in src/event/quic/ngx_event_quic_connection.h: #define NGX_QUIC_DEBUG_PACKETS #define NGX_QUIC_DEBUG_FRAMES @@ -215,7 +233,7 @@ Example configuration: 6. Contributing - If you are willing to contribute, please refer to + Please refer to http://nginx.org/en/docs/contributing_changes.html 7. Links @@ -224,7 +242,9 @@ Example configuration: [2] https://datatracker.ietf.org/doc/html/rfc9114 [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/ [4] https://boringssl.googlesource.com/boringssl/ - [5] https://github.com/quictls/openssl - [6] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen - [7] https://nginx.org/en/docs/debugging_log.html - [8] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf + [5] https://www.libressl.org/ + [6] https://github.com/quictls/openssl + [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0 + [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen + [9] https://nginx.org/en/docs/debugging_log.html + [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf -- cgit v1.2.3 From 0da752661530c145b3c5d1a739115b1968219732 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 1 Nov 2022 17:00:35 +0400 Subject: Set default listen socket type in http. The type field was added in 7999d3fbb765 at early stages of QUIC implementation and was not initialized for default listen. Missing initialization resulted in default listen socket creation error. --- src/http/ngx_http_core_module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 78a9e5ea7..248fa80ea 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -3008,6 +3008,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) lsopt.socklen = sizeof(struct sockaddr_in); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) -- cgit v1.2.3 From 36d80a52693904d829d9a4e27361bcd29e41d48d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 14:10:04 +0400 Subject: HTTP/3: fixed server_name regex captures (ticket #2407). Previously, HTTP/3 stream connection didn't inherit the servername regex from the main QUIC connection saved when processing SNI and using regular expressions in server names. As a result, it didn't execute to set regex captures when choosing the virtual server while parsing HTTP/3 headers. --- src/http/v3/ngx_http_v3_request.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 14802249b..7921d8dc5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,6 +81,7 @@ ngx_http_v3_init(ngx_connection_t *c) if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; + hc->ssl_servername_regex = phc->ssl_servername_regex; hc->conf_ctx = phc->conf_ctx; ngx_set_connection_log(c, clcf->error_log); -- cgit v1.2.3 From 84a51e4de1790fb3f910e2add72bafd7ce482f4f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:34 +0400 Subject: QUIC: fixed C4706 warnings with MSVC 2010. The fix is to avoid assignments within conditional expression. --- src/event/quic/ngx_event_quic_transport.c | 60 ++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 660038461..3838f6463 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -804,11 +804,23 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_ACK: case NGX_QUIC_FT_ACK_ECN: - if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.largest)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.delay)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.range_count)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.first_range)))) - { + p = ngx_quic_parse_int(p, end, &f->u.ack.largest); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.delay); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.range_count); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.first_range); + if (p == NULL) { goto error; } @@ -818,10 +830,11 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, for (i = 0; i < f->u.ack.range_count; i++) { p = ngx_quic_parse_int(p, end, &varint); - if (p) { - p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; } + p = ngx_quic_parse_int(p, end, &varint); if (p == NULL) { goto error; } @@ -833,10 +846,18 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, if (f->type == NGX_QUIC_FT_ACK_ECN) { - if (!((p = ngx_quic_parse_int(p, end, &f->u.ack.ect0)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.ect1)) - && (p = ngx_quic_parse_int(p, end, &f->u.ack.ce)))) - { + p = ngx_quic_parse_int(p, end, &f->u.ack.ect0); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect1); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ce); + if (p == NULL) { goto error; } @@ -989,11 +1010,18 @@ ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, case NGX_QUIC_FT_RESET_STREAM: - if (!((p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id)) - && (p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code)) - && (p = ngx_quic_parse_int(p, end, - &f->u.reset_stream.final_size)))) - { + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size); + if (p == NULL) { goto error; } -- cgit v1.2.3 From 44a901c9e0776ce3d152c78149d13e9222a3776f Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:35 +0400 Subject: QUIC: moved variable declaration to fix build with MSVC 2010. Previously, ngx_quic_hkdf_t variables used declaration with assignment in the middle of a function, which is not supported by MSVC 2010. Fixing this also required to rewrite the ngx_quic_hkdf_set macro and to switch to an explicit array size. --- src/event/quic/ngx_event_quic_protection.c | 68 ++++++++++++++---------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 2216973e0..ee3ebc6f7 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -48,12 +48,10 @@ typedef struct { const u_char *label; } ngx_quic_hkdf_t; -#define ngx_quic_hkdf_set(label, out, prk) \ - { \ - (out)->len, (out)->data, \ - (prk)->len, (prk)->data, \ - (sizeof(label) - 1), (u_char *)(label), \ - } +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, @@ -151,6 +149,7 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, uint8_t is[SHA256_DIGEST_LENGTH]; ngx_uint_t i; const EVP_MD *digest; + ngx_quic_hkdf_t seq[8]; ngx_quic_secret_t *client, *server; static const uint8_t salt[20] = @@ -203,17 +202,15 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, client->iv.len = NGX_QUIC_IV_LEN; server->iv.len = NGX_QUIC_IV_LEN; - ngx_quic_hkdf_t seq[] = { - /* labels per RFC 9001, 5.1. Packet Protection Keys */ - ngx_quic_hkdf_set("tls13 client in", &client->secret, &iss), - ngx_quic_hkdf_set("tls13 quic key", &client->key, &client->secret), - ngx_quic_hkdf_set("tls13 quic iv", &client->iv, &client->secret), - ngx_quic_hkdf_set("tls13 quic hp", &client->hp, &client->secret), - ngx_quic_hkdf_set("tls13 server in", &server->secret, &iss), - ngx_quic_hkdf_set("tls13 quic key", &server->key, &server->secret), - ngx_quic_hkdf_set("tls13 quic iv", &server->iv, &server->secret), - ngx_quic_hkdf_set("tls13 quic hp", &server->hp, &server->secret), - }; + /* labels per RFC 9001, 5.1. Packet Protection Keys */ + ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client->key, &client->secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret); + ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss); + ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server->key, &server->secret); + ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret); + ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret); for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { @@ -639,6 +636,7 @@ ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, ngx_int_t key_len; ngx_str_t secret_str; ngx_uint_t i; + ngx_quic_hkdf_t seq[3]; ngx_quic_secret_t *peer_secret; ngx_quic_ciphers_t ciphers; @@ -670,11 +668,10 @@ ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, secret_str.len = secret_len; secret_str.data = (u_char *) secret; - ngx_quic_hkdf_t seq[] = { - ngx_quic_hkdf_set("tls13 quic key", &peer_secret->key, &secret_str), - ngx_quic_hkdf_set("tls13 quic iv", &peer_secret->iv, &secret_str), - ngx_quic_hkdf_set("tls13 quic hp", &peer_secret->hp, &secret_str), - }; + ngx_quic_hkdf_set(&seq[0], "tls13 quic key", + &peer_secret->key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str); + ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str); for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { @@ -720,6 +717,7 @@ ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) { ngx_uint_t i; + ngx_quic_hkdf_t seq[6]; ngx_quic_ciphers_t ciphers; ngx_quic_secrets_t *current, *next; @@ -744,20 +742,18 @@ ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys) next->server.iv.len = NGX_QUIC_IV_LEN; next->server.hp = current->server.hp; - ngx_quic_hkdf_t seq[] = { - ngx_quic_hkdf_set("tls13 quic ku", - &next->client.secret, ¤t->client.secret), - ngx_quic_hkdf_set("tls13 quic key", - &next->client.key, &next->client.secret), - ngx_quic_hkdf_set("tls13 quic iv", - &next->client.iv, &next->client.secret), - ngx_quic_hkdf_set("tls13 quic ku", - &next->server.secret, ¤t->server.secret), - ngx_quic_hkdf_set("tls13 quic key", - &next->server.key, &next->server.secret), - ngx_quic_hkdf_set("tls13 quic iv", - &next->server.iv, &next->server.secret), - }; + ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", + &next->client.secret, ¤t->client.secret); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", + &next->client.key, &next->client.secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", + &next->client.iv, &next->client.secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", + &next->server.secret, ¤t->server.secret); + ngx_quic_hkdf_set(&seq[4], "tls13 quic key", + &next->server.key, &next->server.secret); + ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", + &next->server.iv, &next->server.secret); for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { -- cgit v1.2.3 From 499dbd28be42c75f90919881ce0df1dce85ad9f1 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:35 +0400 Subject: QUIC: avoid using C99 designated initializers. They are not supported by MSVC till 2012. SSL_QUIC_METHOD initialization is moved to run-time to preserve portability among SSL library implementations, which allows to reduce its visibility. Note using of a static storage to keep SSL_set_quic_method() reference valid. --- src/event/quic/ngx_event_quic_protection.c | 7 +++--- src/event/quic/ngx_event_quic_ssl.c | 40 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index ee3ebc6f7..1247e1954 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -147,6 +147,7 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, { size_t is_len; uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_str_t iss; ngx_uint_t i; const EVP_MD *digest; ngx_quic_hkdf_t seq[8]; @@ -176,10 +177,8 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, return NGX_ERROR; } - ngx_str_t iss = { - .data = is, - .len = is_len - }; + iss.len = is_len; + iss.data = is; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic ngx_quic_set_initial_secret"); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 2d9de48a5..fd0d8252e 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -39,19 +39,6 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); -static SSL_QUIC_METHOD quic_method = { -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER - .set_read_secret = ngx_quic_set_read_secret, - .set_write_secret = ngx_quic_set_write_secret, -#else - .set_encryption_secrets = ngx_quic_set_encryption_secrets, -#endif - .add_handshake_data = ngx_quic_add_handshake_data, - .flush_flight = ngx_quic_flush_flight, - .send_alert = ngx_quic_send_alert, -}; - - #if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER static int @@ -533,13 +520,14 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { - u_char *p; - size_t clen; - ssize_t len; - ngx_str_t dcid; - ngx_ssl_conn_t *ssl_conn; - ngx_quic_socket_t *qsock; - ngx_quic_connection_t *qc; + u_char *p; + size_t clen; + ssize_t len; + ngx_str_t dcid; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + static SSL_QUIC_METHOD quic_method; qc = ngx_quic_get_connection(c); @@ -551,6 +539,18 @@ ngx_quic_init_connection(ngx_connection_t *c) ssl_conn = c->ssl->connection; + if (!quic_method.send_alert) { +#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER + quic_method.set_read_secret = ngx_quic_set_read_secret; + quic_method.set_write_secret = ngx_quic_set_write_secret; +#else + quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets; +#endif + quic_method.add_handshake_data = ngx_quic_add_handshake_data; + quic_method.flush_flight = ngx_quic_flush_flight; + quic_method.send_alert = ngx_quic_send_alert; + } + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic SSL_set_quic_method() failed"); -- cgit v1.2.3 From 553eb6a16838ea297d8eed5166c63afb38136941 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:36 +0400 Subject: QUIC: fixed C4389 MSVC warning about signed/unsigned mismatch. --- src/event/quic/ngx_event_quic_protection.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 1247e1954..3110c9e64 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -988,8 +988,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) u_char *p, *sample; size_t len; uint64_t pn, lpn; - ngx_int_t pnl, rc, key_phase; + ngx_int_t pnl, rc; ngx_str_t in, ad; + ngx_uint_t key_phase; ngx_quic_secret_t *secret; ngx_quic_ciphers_t ciphers; uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; -- cgit v1.2.3 From 885f50f773d1ca6d4b3d95bf23f4e50830dd1736 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:36 +0400 Subject: Added shutdown macros for win32 required for QUIC. --- src/os/win32/ngx_socket.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/os/win32/ngx_socket.h b/src/os/win32/ngx_socket.h index ab56bc8b3..5b6138944 100644 --- a/src/os/win32/ngx_socket.h +++ b/src/os/win32/ngx_socket.h @@ -14,6 +14,8 @@ #define NGX_WRITE_SHUTDOWN SD_SEND +#define NGX_READ_SHUTDOWN SD_RECEIVE +#define NGX_RDWR_SHUTDOWN SD_BOTH typedef SOCKET ngx_socket_t; -- cgit v1.2.3 From fd0b558ac811cbf50e4d6388edffb7baffd4cfc5 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:37 +0400 Subject: QUIC: plug MSVC warning about potentially uninitialized variable. --- src/event/quic/ngx_event_quic_tokens.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c index aeea2ed03..e67825aab 100644 --- a/src/event/quic/ngx_event_quic_tokens.c +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -178,6 +178,10 @@ ngx_quic_validate_token(ngx_connection_t *c, u_char *key, u_char addr_hash[20]; u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; +#if NGX_SUPPRESS_WARN + ngx_str_null(&odcid); +#endif + /* Retry token or NEW_TOKEN in a previous connection */ cipher = EVP_aes_256_cbc(); -- cgit v1.2.3 From bc79d773f9d9443a9654930a5da882c92e857038 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 18:05:37 +0400 Subject: QUIC: fixed C4334 MSVC warning about 32 to 64 bits conversion. --- src/event/quic/ngx_event_quic_ack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 1d530c85a..ebbcf7210 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -195,7 +195,7 @@ ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, } else { qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); - ack_delay = ack->delay * (1 << qc->ctp.ack_delay_exponent) / 1000; + ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000; if (c->ssl->handshaked) { ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); -- cgit v1.2.3 From b015d4965ef296b7b31adae9165c5c049cf9026c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 23 Nov 2022 18:50:26 +0400 Subject: QUIC: fixed triggering stream read event (ticket #2409). If a client packet carrying a stream data frame is not acked due to packet loss, the stream data is retransmitted later by client. It's also possible that the retransmitted range is bigger than before due to more stream data being available by then. If the original data was read out by the application, there would be no read event triggered by the retransmitted frame, even though it contains new data. --- src/event/quic/ngx_event_quic_streams.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 78650b04f..73272ff95 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1147,7 +1147,7 @@ ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, return ngx_quic_close_stream(qs); } - if (f->offset == qs->recv_offset) { + if (f->offset <= qs->recv_offset) { ngx_quic_set_event(qs->connection->read); } -- cgit v1.2.3 From 7d73c50a2d11314270663ebfa4665719c66634f4 Mon Sep 17 00:00:00 2001 From: Jiuzhou Cui Date: Fri, 25 Nov 2022 15:07:23 +0800 Subject: HTTP/3: fixed build without NGX_PCRE (broken by 0f5fc7a320db). --- src/http/v3/ngx_http_v3_request.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7921d8dc5..969fdf356 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,7 +81,9 @@ ngx_http_v3_init(ngx_connection_t *c) if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; +#if (NGX_PCRE) hc->ssl_servername_regex = phc->ssl_servername_regex; +#endif hc->conf_ctx = phc->conf_ctx; ngx_set_connection_log(c, clcf->error_log); -- cgit v1.2.3 From d3294e61d598021554b91dd350c2dcd33c55887d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 25 Nov 2022 15:56:33 +0400 Subject: QUIC: fixed computation of nonce with packet numbers beyond 2^32. Prodded by Yu Zhu. --- src/event/quic/ngx_event_quic_protection.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 3110c9e64..91cfc6be3 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -964,10 +964,14 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) { - nonce[len - 4] ^= (pn & 0xff000000) >> 24; - nonce[len - 3] ^= (pn & 0x00ff0000) >> 16; - nonce[len - 2] ^= (pn & 0x0000ff00) >> 8; - nonce[len - 1] ^= (pn & 0x000000ff); + nonce[len - 8] ^= (pn >> 56) & 0x3f; + nonce[len - 7] ^= (pn >> 48) & 0xff; + nonce[len - 6] ^= (pn >> 40) & 0xff; + nonce[len - 5] ^= (pn >> 32) & 0xff; + nonce[len - 4] ^= (pn >> 24) & 0xff; + nonce[len - 3] ^= (pn >> 16) & 0xff; + nonce[len - 2] ^= (pn >> 8) & 0xff; + nonce[len - 1] ^= pn & 0xff; } -- cgit v1.2.3 From d3fb12d77fad66eba2d8db102e505000c2a80e6d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 7 Sep 2022 12:37:15 +0400 Subject: QUIC: treat qc->error == -1 as a missing error. Previously, zero was used for this purpose. However, NGX_QUIC_ERR_NO_ERROR is zero too. As a result, NGX_QUIC_ERR_NO_ERROR was changed to NGX_QUIC_ERR_INTERNAL_ERROR when closing a QUIC connection. --- src/event/quic/ngx_event_quic.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index a1abd267d..59f14b9e5 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -73,7 +73,7 @@ ngx_quic_connstate_dbg(ngx_connection_t *c) if (qc) { - if (qc->error) { + if (qc->error != (ngx_uint_t) -1) { p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); p = ngx_slprintf(p, last, " error:%ui", qc->error); @@ -523,7 +523,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) qc->error = NGX_QUIC_ERR_NO_ERROR; } else { - if (qc->error == 0 && !qc->error_app) { + if (qc->error == (ngx_uint_t) -1 && !qc->error_app) { qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; } @@ -939,7 +939,7 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); - qc->error = 0; + qc->error = (ngx_uint_t) -1; qc->error_reason = 0; c->log->action = "decrypting packet"; -- cgit v1.2.3 From 62b928a45f32404afdd67925498d593fcba51f59 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 22 Aug 2022 15:28:51 +0400 Subject: QUIC: made ngx_quic_finalize_connecion() more graceful. Previously, ngx_quic_finalize_connection() closed the connection with NGX_ERROR code, which resulted in immediate connection closure. Now the code is NGX_OK, which provides a more graceful shutdown with a timeout. --- src/event/quic/ngx_event_quic.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 59f14b9e5..59c799d36 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -414,6 +414,7 @@ ngx_quic_input_handler(ngx_event_t *rev) } if (c->close) { + qc->error = NGX_QUIC_ERR_NO_ERROR; qc->error_reason = "graceful shutdown"; ngx_quic_close_connection(c, NGX_OK); return; @@ -506,31 +507,26 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) qc->error_level = c->ssl ? SSL_quic_read_level(c->ssl->connection) : ssl_encryption_initial; - if (rc == NGX_OK) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close immediate drain:%d", - qc->draining); + if (qc->error == (ngx_uint_t) -1) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + qc->error_app = 0; + } + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close immediate term:%d drain:%d " + "%serror:%ui \"%s\"", + rc == NGX_ERROR ? 1 : 0, qc->draining, + qc->error_app ? "app " : "", qc->error, + qc->error_reason ? qc->error_reason : ""); + + if (rc == NGX_OK) { qc->close.log = c->log; qc->close.data = c; qc->close.handler = ngx_quic_close_timer_handler; qc->close.cancelable = 1; ctx = ngx_quic_get_send_ctx(qc, qc->error_level); - ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); - - qc->error = NGX_QUIC_ERR_NO_ERROR; - - } else { - if (qc->error == (ngx_uint_t) -1 && !qc->error_app) { - qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; - } - - ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic close immediate due to %serror: %ui %s", - qc->error_app ? "app " : "", qc->error, - qc->error_reason ? qc->error_reason : ""); } (void) ngx_quic_send_cc(c); @@ -617,7 +613,7 @@ ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, qc->error_app = 1; qc->error_ftype = 0; - ngx_quic_close_connection(c, NGX_ERROR); + ngx_quic_close_connection(c, NGX_OK); } -- cgit v1.2.3 From a89167c247afd67e43017583033463f551737262 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 7 Sep 2022 19:25:13 +0400 Subject: QUIC: post close event for connection close. Previously, close event was used only for close timeout, while read event was used for posting connection close. --- src/event/quic/ngx_event_quic.c | 45 ++++++++++++++++++--------------- src/event/quic/ngx_event_quic_streams.c | 4 +-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 59c799d36..3e98a11ed 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -15,8 +15,7 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, 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_timer_handler(ngx_event_t *ev); +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); @@ -283,6 +282,11 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->push.handler = ngx_quic_push_handler; qc->push.cancelable = 1; + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_handler; + qc->close.cancelable = 1; + qc->path_validation.log = c->log; qc->path_validation.data = c; qc->path_validation.handler = ngx_quic_path_validation_handler; @@ -420,19 +424,11 @@ ngx_quic_input_handler(ngx_event_t *rev) return; } - if (!rev->ready) { - if (qc->closing) { - ngx_quic_close_connection(c, NGX_OK); - - } else if (qc->shutdown) { - ngx_quic_shutdown_quic(c); - } - + b = c->udp->buffer; + if (b == NULL) { return; } - b = c->udp->buffer; - rc = ngx_quic_handle_datagram(c, b, NULL); if (rc == NGX_ERROR) { @@ -520,11 +516,6 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) qc->error_reason ? qc->error_reason : ""); if (rc == NGX_OK) { - qc->close.log = c->log; - qc->close.data = c; - qc->close.handler = ngx_quic_close_timer_handler; - qc->close.cancelable = 1; - ctx = ngx_quic_get_send_ctx(qc, qc->error_level); ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); } @@ -570,6 +561,10 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) return; } + if (qc->close.posted) { + ngx_delete_posted_event(&qc->close); + } + ngx_quic_close_sockets(c); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); @@ -633,14 +628,22 @@ ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, static void -ngx_quic_close_timer_handler(ngx_event_t *ev) +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 timer"); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); c = ev->data; - ngx_quic_close_connection(c, NGX_DONE); + qc = ngx_quic_get_connection(c); + + if (qc->closing) { + ngx_quic_close_connection(c, NGX_OK); + + } else if (qc->shutdown) { + ngx_quic_shutdown_quic(c); + } } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 73272ff95..e18a7d915 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1031,7 +1031,7 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) if (qc->closing) { /* schedule handler call to continue ngx_quic_close_connection() */ - ngx_post_event(pc->read, &ngx_posted_events); + ngx_post_event(&qc->close, &ngx_posted_events); return NGX_OK; } @@ -1057,7 +1057,7 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) } if (qc->shutdown) { - ngx_post_event(pc->read, &ngx_posted_events); + ngx_post_event(&qc->close, &ngx_posted_events); } return NGX_OK; -- cgit v1.2.3 From dc82bed893cc4292c459d41269882b621b98f5b3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 29 Nov 2022 17:46:46 +0400 Subject: QUIC: reusable mode for main connection. The connection is automatically switched to this mode by transport layer when there are no non-cancelable streams. Currently, cancelable streams are HTTP/3 encoder/decoder/control streams. --- src/event/quic/ngx_event_quic.c | 49 +++++----------- src/event/quic/ngx_event_quic.h | 1 + src/event/quic/ngx_event_quic_streams.c | 100 ++++++++++++++++++++++++++------ src/http/v3/ngx_http_v3_uni.c | 14 ++++- 4 files changed, 110 insertions(+), 54 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 3e98a11ed..7245ee7d0 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -341,6 +341,8 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } + ngx_reusable_connection(c, 1); + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic connection created"); @@ -420,7 +422,7 @@ ngx_quic_input_handler(ngx_event_t *rev) if (c->close) { qc->error = NGX_QUIC_ERR_NO_ERROR; qc->error_reason = "graceful shutdown"; - ngx_quic_close_connection(c, NGX_OK); + ngx_quic_close_connection(c, NGX_ERROR); return; } @@ -603,12 +605,17 @@ ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); + + if (qc->closing) { + return; + } + qc->error = err; qc->error_reason = reason; qc->error_app = 1; qc->error_ftype = 0; - ngx_quic_close_connection(c, NGX_OK); + ngx_post_event(&qc->close, &ngx_posted_events); } @@ -630,20 +637,13 @@ ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, static void ngx_quic_close_handler(ngx_event_t *ev) { - ngx_connection_t *c; - ngx_quic_connection_t *qc; + ngx_connection_t *c; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); c = ev->data; - qc = ngx_quic_get_connection(c); - - if (qc->closing) { - ngx_quic_close_connection(c, NGX_OK); - } else if (qc->shutdown) { - ngx_quic_shutdown_quic(c); - } + ngx_quic_close_connection(c, NGX_OK); } @@ -1428,31 +1428,10 @@ ngx_quic_push_handler(ngx_event_t *ev) void ngx_quic_shutdown_quic(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); - - if (qc->closing) { - return; + if (c->reusable) { + qc = ngx_quic_get_connection(c); + ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); } - - 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_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 92c9ecae0..231ff6e96 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -113,6 +113,7 @@ void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, const char *reason); 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_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index e18a7d915..bad8b4ae5 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -33,6 +33,7 @@ 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); @@ -51,6 +52,10 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) pc = c->quic ? c->quic->parent : c; qc = ngx_quic_get_connection(pc); + if (qc->closing) { + return NULL; + } + if (bidi) { if (qc->streams.server_streams_bidi >= qc->streams.server_max_streams_bidi) @@ -161,13 +166,10 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) ngx_pool_t *pool; ngx_queue_t *q; ngx_rbtree_t *tree; + ngx_connection_t *sc; ngx_rbtree_node_t *node; ngx_quic_stream_t *qs; -#if (NGX_DEBUG) - ngx_uint_t ns; -#endif - while (!ngx_queue_empty(&qc->streams.uninitialized)) { q = ngx_queue_head(&qc->streams.uninitialized); ngx_queue_remove(q); @@ -185,34 +187,34 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) return NGX_OK; } -#if (NGX_DEBUG) - ns = 0; -#endif - node = ngx_rbtree_min(tree->root, tree->sentinel); while (node) { qs = (ngx_quic_stream_t *) node; node = ngx_rbtree_next(tree, node); + sc = qs->connection; qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; - if (qs->connection == NULL) { + if (sc == NULL) { ngx_quic_close_stream(qs); continue; } - ngx_quic_set_event(qs->connection->read); - ngx_quic_set_event(qs->connection->write); + ngx_quic_set_event(sc->read); + ngx_quic_set_event(sc->write); -#if (NGX_DEBUG) - ns++; -#endif + sc->close = 1; + sc->read->handler(sc->read); } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic connection has %ui active streams", ns); + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has active streams"); return NGX_AGAIN; } @@ -587,6 +589,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_queue_t *q; ngx_connection_t *sc; ngx_quic_stream_t *qs; @@ -639,10 +642,14 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) *log = *c->log; pool->log = log; + reusable = c->reusable; + ngx_reusable_connection(c, 0); + sc = ngx_get_connection(c->fd, log); if (sc == NULL) { ngx_destroy_pool(pool); ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); return NULL; } @@ -712,6 +719,7 @@ 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; } @@ -724,6 +732,31 @@ 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) { @@ -1056,14 +1089,47 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) ngx_quic_queue_frame(qc, frame); } + if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + } + if (qc->shutdown) { - ngx_post_event(&qc->close, &ngx_posted_events); + ngx_quic_shutdown_quic(pc); } return NGX_OK; } +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/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index 96b7d7ebf..d6a6e97e6 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -52,7 +52,7 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) return; } - c->quic->cancelable = 1; + ngx_quic_cancelable_stream(c); us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); if (us == NULL) { @@ -182,6 +182,11 @@ ngx_http_v3_uni_read_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + ngx_memzero(&b, sizeof(ngx_buf_t)); while (rev->ready) { @@ -262,6 +267,11 @@ ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + if (rev->ready) { if (c->recv(c, &ch, 1) != 0) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); @@ -404,7 +414,7 @@ ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) goto failed; } - sc->quic->cancelable = 1; + ngx_quic_cancelable_stream(sc); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create uni stream, type:%ui", type); -- cgit v1.2.3 From a2a513b93caeba13e408bfc80f8626f49550be40 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 22 Aug 2022 15:33:23 +0400 Subject: QUIC: defer stream removal until all its data is acked. Previously, stream was kept alive until all its data is sent. This resulted in disabling retransmission of final part of stream when QUIC connection was closed right after closing stream connection. --- src/event/quic/ngx_event_quic.h | 4 +- src/event/quic/ngx_event_quic_ack.c | 1 + src/event/quic/ngx_event_quic_streams.c | 82 ++++++++++++++++++++++++--------- 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 231ff6e96..d33bd8d4d 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -85,6 +85,7 @@ struct ngx_quic_stream_s { ngx_connection_t *parent; ngx_connection_t *connection; uint64_t id; + uint64_t sent; uint64_t acked; uint64_t send_max_data; uint64_t send_offset; @@ -98,7 +99,8 @@ 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; - ngx_uint_t cancelable; /* unsigned cancelable:1; */ + unsigned cancelable:1; + unsigned fin_acked:1; }; diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index ebbcf7210..d236deb59 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -253,6 +253,7 @@ ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, break; case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_RESET_STREAM: ngx_quic_handle_stream_ack(c, f); break; } diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index bad8b4ae5..999bfccef 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -887,7 +887,7 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) qs->send_state = NGX_QUIC_STREAM_SEND_SEND; - flow = qs->acked + qc->conf->stream_buffer_size - c->sent; + flow = qs->acked + qc->conf->stream_buffer_size - qs->sent; if (flow == 0) { wev->ready = 0; @@ -900,13 +900,14 @@ ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) n = qs->send.size; - in = ngx_quic_write_buffer(pc, &qs->send, in, limit, c->sent); + in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent); if (in == NGX_CHAIN_ERROR) { return NGX_CHAIN_ERROR; } n = qs->send.size - n; c->sent += n; + qs->sent += n; qc->streams.sent += n; if (flow == n) { @@ -1045,9 +1046,12 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) if (!qc->closing) { /* make sure everything is sent and final size is received */ - if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV - || qs->send_state == NGX_QUIC_STREAM_SEND_READY - || qs->send_state == NGX_QUIC_STREAM_SEND_SEND) + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD + && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD) { return NGX_OK; } @@ -1488,36 +1492,70 @@ ngx_quic_handle_max_streams_frame(ngx_connection_t *c, void ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) { - uint64_t sent, unacked; + uint64_t acked; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); - qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); - if (qs == NULL) { - return; - } + switch (f->type) { - if (qs->connection == NULL) { + case NGX_QUIC_FT_RESET_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id); + if (qs == NULL) { + return; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack reset final_size:%uL", + qs->id, f->u.reset_stream.final_size); + + break; + + case NGX_QUIC_FT_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (qs == NULL) { + return; + } + + acked = qs->acked; qs->acked += f->u.stream.length; - return; - } - sent = qs->connection->sent; - unacked = sent - qs->acked; - qs->acked += f->u.stream.length; + if (f->u.stream.fin) { + qs->fin_acked = 1; + } + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT + && qs->acked == qs->sent && qs->fin_acked) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL", + qs->id, f->u.stream.length, f->u.stream.fin, + qs->sent - qs->acked); + + if (qs->connection + && qs->sent - acked == qc->conf->stream_buffer_size + && f->u.stream.length > 0) + { + ngx_quic_set_event(qs->connection->write); + } - ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stream id:0x%xL ack len:%uL acked:%uL unacked:%uL", - qs->id, f->u.stream.length, qs->acked, sent - qs->acked); + break; - if (unacked != qc->conf->stream_buffer_size) { - /* not blocked on buffer size */ + default: return; } - ngx_quic_set_event(qs->connection->write); + if (qs->connection == NULL) { + ngx_quic_close_stream(qs); + } } -- cgit v1.2.3 From 21c34aadf9ff8c53f8b1413452895aa49d3fffa8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 7 Sep 2022 13:12:56 +0400 Subject: QUIC: do not send MAX_STREAMS in shutdown state. No more streams are expected from client. --- src/event/quic/ngx_event_quic_streams.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 999bfccef..212d7edb3 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1072,6 +1072,15 @@ 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) { frame = ngx_quic_alloc_frame(pc); if (frame == NULL) { @@ -1093,14 +1102,6 @@ ngx_quic_close_stream(ngx_quic_stream_t *qs) ngx_quic_queue_frame(qc, frame); } - 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; } -- cgit v1.2.3 From dd4c31fc34bd8390dd3fa9e804afc15b57b69135 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 19 Oct 2022 17:45:30 +0400 Subject: HTTP/3: unified hq code with regular HTTP/3 code. The change removes hq-specific request handler. Now hq requests are handled by the HTTP/3 request handler. --- src/http/v3/ngx_http_v3.c | 24 ++++++-- src/http/v3/ngx_http_v3.h | 5 +- src/http/v3/ngx_http_v3_request.c | 118 ++++++++------------------------------ src/http/v3/ngx_http_v3_uni.c | 15 +++++ 4 files changed, 62 insertions(+), 100 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 84bb8f601..b9956e68f 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -17,10 +17,13 @@ static void ngx_http_v3_cleanup_session(void *data); ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c) { - ngx_connection_t *pc; - ngx_pool_cleanup_t *cln; - ngx_http_connection_t *hc; - ngx_http_v3_session_t *h3c; + ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; +#if (NGX_HTTP_V3_HQ) + ngx_http_v3_srv_conf_t *h3scf; +#endif pc = c->quic->parent; hc = pc->data; @@ -39,6 +42,13 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c->max_push_id = (uint64_t) -1; h3c->goaway_push_id = (uint64_t) -1; +#if (NGX_HTTP_V3_HQ) + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + if (h3scf->hq) { + h3c->hq = 1; + } +#endif + ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); @@ -61,6 +71,12 @@ ngx_http_v3_init_session(ngx_connection_t *c) hc->v3_session = h3c; +#if (NGX_HTTP_V3_HQ) + if (h3c->hq) { + return NGX_OK; + } +#endif + return ngx_http_v3_send_settings(c); failed: diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index a3544ebc4..7ffd38768 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -145,7 +145,10 @@ struct ngx_http_v3_session_s { off_t total_bytes; off_t payload_bytes; - ngx_uint_t goaway; /* unsigned goaway:1; */ + unsigned goaway:1; +#if (NGX_HTTP_V3_HQ) + unsigned hq:1; +#endif ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; }; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 969fdf356..bdb5331fe 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,9 +10,6 @@ #include -#if (NGX_HTTP_V3_HQ) -static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); -#endif 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_request(void *data); @@ -89,13 +86,6 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } -#if (NGX_HTTP_V3_HQ) - if (h3scf->hq) { - ngx_http_v3_init_hq_stream(c); - return; - } -#endif - if (ngx_http_v3_init_session(c) != NGX_OK) { ngx_http_close_connection(c); return; @@ -110,83 +100,12 @@ ngx_http_v3_init(ngx_connection_t *c) } -#if (NGX_HTTP_V3_HQ) - -static void -ngx_http_v3_init_hq_stream(ngx_connection_t *c) -{ - uint64_t n; - ngx_event_t *rev; - ngx_http_connection_t *hc; - ngx_http_core_loc_conf_t *clcf; - ngx_http_core_srv_conf_t *cscf; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream"); - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); -#endif - - hc = c->data; - - /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ - - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "unexpected uni stream"); - ngx_http_close_connection(c); - return; - } - - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - - n = c->quic->id >> 2; - - if (n >= clcf->keepalive_requests) { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "reached maximum number of requests"); - ngx_http_close_connection(c); - return; - } - - if (ngx_current_msec - c->quic->parent->start_time - > clcf->keepalive_time) - { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "reached maximum time for requests"); - ngx_http_close_connection(c); - return; - } - - rev = c->read; - - if (rev->ready) { - rev->handler(rev); - return; - } - - cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - - ngx_add_timer(rev, cscf->client_header_timeout); - ngx_reusable_connection(c, 1); - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_http_close_connection(c); - return; - } -} - -#endif - - static void ngx_http_v3_init_request_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; + ngx_connection_t *pc; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -219,15 +138,21 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) return; } + pc = c->quic->parent; + if (n + 1 == clcf->keepalive_requests - || ngx_current_msec - c->quic->parent->start_time - > clcf->keepalive_time) + || ngx_current_msec - pc->start_time > clcf->keepalive_time) { h3c->goaway = 1; - if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { - ngx_http_close_connection(c); - return; +#if (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + ngx_http_close_connection(c); + return; + } } ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, @@ -235,8 +160,14 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) } rev = c->read; - rev->handler = ngx_http_v3_wait_request_handler; - c->write->handler = ngx_http_empty_handler; + +#if (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + } if (rev->ready) { rev->handler(rev); @@ -264,8 +195,8 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_request_t *r; - ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; + ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; c = rev->data; @@ -404,13 +335,10 @@ ngx_http_v3_reset_connection(ngx_connection_t *c) h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + if (h3scf->max_table_capacity > 0 && !c->read->eof #if (NGX_HTTP_V3_HQ) - if (h3scf->hq) { - return; - } + && !h3scf->hq #endif - - if (h3scf->max_table_capacity > 0 && !c->read->eof && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index d6a6e97e6..d0e392de4 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -37,8 +37,23 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { uint64_t n; +#if (NGX_HTTP_V3_HQ) + ngx_http_v3_session_t *h3c; +#endif ngx_http_v3_uni_stream_t *us; +#if (NGX_HTTP_V3_HQ) + h3c = ngx_http_v3_get_session(c); + if (h3c->hq) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "uni stream in hq mode"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } +#endif + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); n = c->quic->id >> 2; -- cgit v1.2.3 From fed44881d3bf5126b49144dba58f2830e0fe9866 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 19 Oct 2022 17:45:18 +0400 Subject: QUIC: idle mode for main connection. Now main QUIC connection for HTTP/3 always has c->idle flag set. This allows the connection to receive worker shutdown notification. It is passed to application level via a new conf->shutdown() callback. The HTTP/3 shutdown callback sends GOAWAY to client and gracefully shuts down the QUIC connection. --- src/event/quic/ngx_event_quic.c | 17 ++++++++++++++--- src/event/quic/ngx_event_quic.h | 5 +++++ src/http/v3/ngx_http_v3.h | 2 ++ src/http/v3/ngx_http_v3_module.c | 2 ++ src/http/v3/ngx_http_v3_request.c | 35 ++++++++++++++++++++++++++++++++++- 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 7245ee7d0..a28f5a7ac 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -341,6 +341,7 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, return NULL; } + c->idle = 1; ngx_reusable_connection(c, 1); ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -420,9 +421,19 @@ ngx_quic_input_handler(ngx_event_t *rev) } if (c->close) { - qc->error = NGX_QUIC_ERR_NO_ERROR; - qc->error_reason = "graceful shutdown"; - ngx_quic_close_connection(c, NGX_ERROR); + c->close = 0; + + if (!ngx_exiting) { + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + return; } diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index d33bd8d4d..335d925cb 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -28,6 +28,9 @@ #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 +typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); + + typedef enum { NGX_QUIC_STREAM_SEND_READY = 0, NGX_QUIC_STREAM_SEND_SEND, @@ -74,6 +77,8 @@ typedef struct { ngx_int_t stream_reject_code_uni; ngx_int_t stream_reject_code_bidi; + 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; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 7ffd38768..818ca2bf5 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -141,6 +141,7 @@ struct ngx_http_v3_session_s { uint64_t next_push_id; uint64_t max_push_id; uint64_t goaway_push_id; + uint64_t next_request_id; off_t total_bytes; off_t payload_bytes; @@ -158,6 +159,7 @@ void ngx_http_v3_init(ngx_connection_t *c); void ngx_http_v3_reset_connection(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_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 d274a3bf2..d87a1c9ef 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -249,6 +249,8 @@ 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.shutdown = ngx_http_v3_shutdown; + return h3scf; } diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index bdb5331fe..c75bc19c5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -100,6 +100,37 @@ 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 (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + (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) { @@ -140,6 +171,8 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) pc = c->quic->parent; + h3c->next_request_id = c->quic->id + 0x04; + if (n + 1 == clcf->keepalive_requests || ngx_current_msec - pc->start_time > clcf->keepalive_time) { @@ -149,7 +182,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) if (!h3c->hq) #endif { - if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { ngx_http_close_connection(c); return; } -- cgit v1.2.3 From 7e3aa23991cbab236617f15ab3602c1b43b2aae1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 30 Nov 2022 14:09:08 +0400 Subject: QUIC: removed cancelable flag from QUIC and HTTP/3 events. All these events are created in context of a client connection and are deleted when the connection is closed. Setting ev->cancelable could trigger premature connection closure and a socket leak alert. --- src/event/quic/ngx_event_quic.c | 4 ---- src/http/v3/ngx_http_v3.c | 1 - 2 files changed, 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index a28f5a7ac..9b342d7de 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -275,22 +275,18 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, qc->pto.log = c->log; qc->pto.data = c; qc->pto.handler = ngx_quic_pto_handler; - qc->pto.cancelable = 1; qc->push.log = c->log; qc->push.data = c; qc->push.handler = ngx_quic_push_handler; - qc->push.cancelable = 1; qc->close.log = c->log; qc->close.data = c; qc->close.handler = ngx_quic_close_handler; - qc->close.cancelable = 1; qc->path_validation.log = c->log; qc->path_validation.data = c; qc->path_validation.handler = ngx_quic_path_validation_handler; - qc->path_validation.cancelable = 1; qc->conf = conf; diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index b9956e68f..70f397985 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -55,7 +55,6 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c->keepalive.log = pc->log; h3c->keepalive.data = pc; h3c->keepalive.handler = ngx_http_v3_keepalive_handler; - h3c->keepalive.cancelable = 1; h3c->table.send_insert_count.log = pc->log; h3c->table.send_insert_count.data = pc; -- cgit v1.2.3 From 8a1deaca78d65f4db82f856e22dcf879d1cec479 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 22 Aug 2022 14:09:03 +0400 Subject: HTTP/3: renamed functions. ngx_http_v3_init() is renamed ngx_http_v3_init_stream(). ngx_http_v3_reset_connection() is renamed to ngx_http_v3_reset_stream(). --- src/http/ngx_http_request.c | 4 ++-- src/http/v3/ngx_http_v3.h | 4 ++-- src/http/v3/ngx_http_v3_request.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 86da94262..8cc3cff24 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -326,7 +326,7 @@ ngx_http_init_connection(ngx_connection_t *c) #if (NGX_HTTP_V3) if (hc->addr_conf->http3) { - ngx_http_v3_init(c); + ngx_http_v3_init_stream(c); return; } #endif @@ -3786,7 +3786,7 @@ ngx_http_close_connection(ngx_connection_t *c) #if (NGX_HTTP_V3) if (c->quic) { - ngx_http_v3_reset_connection(c); + 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 818ca2bf5..8c6c57edf 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -155,8 +155,8 @@ struct ngx_http_v3_session_s { }; -void ngx_http_v3_init(ngx_connection_t *c); -void ngx_http_v3_reset_connection(ngx_connection_t *c); +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_shutdown(ngx_connection_t *c); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c75bc19c5..cbfede836 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -55,7 +55,7 @@ static const struct { void -ngx_http_v3_init(ngx_connection_t *c) +ngx_http_v3_init_stream(ngx_connection_t *c) { ngx_http_connection_t *hc, *phc; ngx_http_v3_srv_conf_t *h3scf; @@ -362,7 +362,7 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void -ngx_http_v3_reset_connection(ngx_connection_t *c) +ngx_http_v3_reset_stream(ngx_connection_t *c) { ngx_http_v3_srv_conf_t *h3scf; -- cgit v1.2.3 From 64ccdf45288c46b5f8e12426d3802c44d789d115 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 30 Nov 2022 12:51:15 +0400 Subject: QUIC: application init() callback. It's called after handshake completion or prior to the first early data stream creation. The callback should initialize application-level data before creating streams. HTTP/3 callback implementation sets keepalive timer and sends SETTINGS. Also, this allows to limit max handshake time in ngx_http_v3_init_stream(). --- src/event/quic/ngx_event_quic.h | 2 ++ src/event/quic/ngx_event_quic_streams.c | 22 ++++++++++++++++--- src/http/v3/ngx_http_v3.c | 28 +++++++----------------- src/http/v3/ngx_http_v3.h | 1 + src/http/v3/ngx_http_v3_module.c | 1 + src/http/v3/ngx_http_v3_request.c | 38 ++++++++++++++++++++++++++++----- 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 335d925cb..d13adbdc9 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -28,6 +28,7 @@ #define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 +typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); @@ -77,6 +78,7 @@ 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]; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 212d7edb3..2b613ac74 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -21,6 +21,7 @@ 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); @@ -555,14 +556,21 @@ ngx_quic_init_streams(ngx_connection_t *c) return NGX_OK; } - ngx_quic_init_streams_handler(c); - - 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); + } +} + + +static ngx_int_t +ngx_quic_do_init_streams(ngx_connection_t *c) { ngx_queue_t *q; ngx_quic_stream_t *qs; @@ -572,6 +580,12 @@ ngx_quic_init_streams_handler(ngx_connection_t *c) qc = ngx_quic_get_connection(c); + if (qc->conf->init) { + if (qc->conf->init(c) != NGX_OK) { + return NGX_ERROR; + } + } + for (q = ngx_queue_head(&qc->streams.uninitialized); q != ngx_queue_sentinel(&qc->streams.uninitialized); q = ngx_queue_next(q)) @@ -581,6 +595,8 @@ ngx_quic_init_streams_handler(ngx_connection_t *c) } qc->streams.initialized = 1; + + return NGX_OK; } diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 70f397985..b0cf15b76 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -17,7 +17,6 @@ static void ngx_http_v3_cleanup_session(void *data); ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c) { - ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; @@ -25,16 +24,11 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_http_v3_srv_conf_t *h3scf; #endif - pc = c->quic->parent; - hc = pc->data; - - if (hc->v3_session) { - return NGX_OK; - } + hc = c->data; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); - h3c = ngx_pcalloc(pc->pool, sizeof(ngx_http_v3_session_t)); + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); if (h3c == NULL) { goto failed; } @@ -52,15 +46,15 @@ ngx_http_v3_init_session(ngx_connection_t *c) ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); - h3c->keepalive.log = pc->log; - h3c->keepalive.data = pc; + h3c->keepalive.log = c->log; + h3c->keepalive.data = c; h3c->keepalive.handler = ngx_http_v3_keepalive_handler; - h3c->table.send_insert_count.log = pc->log; - h3c->table.send_insert_count.data = pc; + 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; - cln = ngx_pool_cleanup_add(pc->pool, 0); + cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { goto failed; } @@ -70,13 +64,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) hc->v3_session = h3c; -#if (NGX_HTTP_V3_HQ) - if (h3c->hq) { - return NGX_OK; - } -#endif - - return ngx_http_v3_send_settings(c); + return NGX_OK; failed: diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 8c6c57edf..207a8c25b 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -159,6 +159,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); +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); diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index d87a1c9ef..ed6becf31 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -249,6 +249,7 @@ 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; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index cbfede836..2e7dd811d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -57,18 +57,29 @@ static const struct { void ngx_http_v3_init_stream(ngx_connection_t *c) { + ngx_http_v3_session_t *h3c; 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; hc->ssl = 1; clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); if (c->quic == NULL) { + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + h3c = hc->v3_session; + ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); + h3scf->quic.timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; @@ -86,11 +97,6 @@ ngx_http_v3_init_stream(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } - if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_close_connection(c); - return; - } - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ngx_http_v3_init_uni_stream(c); @@ -100,6 +106,28 @@ ngx_http_v3_init_stream(ngx_connection_t *c) } +ngx_int_t +ngx_http_v3_init(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + + 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); + +#if (NGX_HTTP_V3_HQ) + if (h3c->hq) { + return NGX_OK; + } +#endif + + return ngx_http_v3_send_settings(c); +} + + void ngx_http_v3_shutdown(ngx_connection_t *c) { -- cgit v1.2.3 From 36f7b31f9578c0d393cfe82d4e23c76a7539f34e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Oct 2022 12:52:09 +0400 Subject: HTTP/3: implement keepalive for hq. Previously, keepalive timer was deleted in ngx_http_v3_wait_request_handler() and set in request cleanup handler. This worked for HTTP/3 connections, but not for hq connections. Now keepalive timer is deleted in ngx_http_v3_init_request_stream() and set in connection cleanup handler, which works both for HTTP/3 and hq. --- src/http/v3/ngx_http_v3_request.c | 47 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2e7dd811d..f05198903 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -12,6 +12,7 @@ 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); 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, @@ -165,6 +166,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) uint64_t n; ngx_event_t *rev; ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -220,6 +222,21 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) "reached maximum number of requests"); } + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_connection; + cln->data = c; + + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + rev = c->read; #if (NGX_HTTP_V3_HQ) @@ -256,7 +273,6 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_request_t *r; - ngx_http_v3_session_t *h3c; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; @@ -377,13 +393,6 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) cln->handler = ngx_http_v3_cleanup_request; cln->data = r; - h3c = ngx_http_v3_get_session(c); - h3c->nrequests++; - - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); - } - rev->handler = ngx_http_v3_process_request; ngx_http_v3_process_request(rev); } @@ -418,20 +427,13 @@ ngx_http_v3_reset_stream(ngx_connection_t *c) static void -ngx_http_v3_cleanup_request(void *data) +ngx_http_v3_cleanup_connection(void *data) { - ngx_http_request_t *r = data; + ngx_connection_t *c = data; - ngx_connection_t *c; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; - c = r->connection; - - if (!r->response_sent) { - c->error = 1; - } - h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { @@ -441,6 +443,17 @@ ngx_http_v3_cleanup_request(void *data) } +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_http_request_t *r = data; + + if (!r->response_sent) { + r->connection->error = 1; + } +} + + static void ngx_http_v3_process_request(ngx_event_t *rev) { -- cgit v1.2.3 From d04f45ac5bddb034ec8c5b0874a7358a991d1b77 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 3 Jan 2023 16:24:45 +0400 Subject: HTTP/3: handled insertion reference to a going to be evicted entry. As per RFC 9204, section 3.2.2, a new entry can reference an entry in the dynamic table that will be evicted when adding this new entry into the dynamic table. Previously, such inserts resulted in use-after-free since the old entry was evicted before the insertion (ticket #2431). Now it's evicted after the insertion. This change fixes Insert with Name Reference and Duplicate encoder instructions. --- src/http/v3/ngx_http_v3_table.c | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index 7e07a15b9..f49a8fc5e 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -13,7 +13,7 @@ #define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) -static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t need); +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target); static void ngx_http_v3_unblock(void *data); static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); @@ -204,13 +204,15 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) size = ngx_http_v3_table_entry_size(name, value); - if (ngx_http_v3_evict(c, size) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } - h3c = ngx_http_v3_get_session(c); dt = &h3c->table; + if (size > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 insert [%ui] \"%V\":\"%V\", size:%uz", dt->base + dt->nelts, name, value, size); @@ -234,6 +236,10 @@ ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) dt->insert_count++; + if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) { + return NGX_ERROR; + } + ngx_post_event(&dt->send_insert_count, &ngx_posted_events); if (ngx_http_v3_new_entry(c) != NGX_OK) { @@ -293,14 +299,11 @@ ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } - dt = &h3c->table; - - if (dt->size > capacity) { - if (ngx_http_v3_evict(c, dt->size - capacity) != NGX_OK) { - return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; - } + if (ngx_http_v3_evict(c, capacity) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; } + dt = &h3c->table; max = capacity / 32; prev_max = dt->capacity / 32; @@ -345,9 +348,9 @@ ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) static ngx_int_t -ngx_http_v3_evict(ngx_connection_t *c, size_t need) +ngx_http_v3_evict(ngx_connection_t *c, size_t target) { - size_t size, target; + size_t size; ngx_uint_t n; ngx_http_v3_field_t *field; ngx_http_v3_session_t *h3c; @@ -355,14 +358,6 @@ ngx_http_v3_evict(ngx_connection_t *c, size_t need) h3c = ngx_http_v3_get_session(c); dt = &h3c->table; - - if (need > dt->capacity) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, - "not enough dynamic table capacity"); - return NGX_ERROR; - } - - target = dt->capacity - need; n = 0; while (dt->size > target) { -- cgit v1.2.3 From 1fe0913fccedfffade10a88d3fb3033339a42900 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Jan 2023 17:59:16 +0400 Subject: HTTP/3: fixed $connection_time. Previously, start_time wasn't set for a new stream. The fix is to derive it from the parent connection. Also it's used to simplify tracking keepalive_time. --- src/event/quic/ngx_event_quic_streams.c | 1 + src/http/v3/ngx_http_v3_request.c | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 2b613ac74..785625547 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -682,6 +682,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->local_sockaddr = c->local_sockaddr; sc->local_socklen = c->local_socklen; sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sc->start_time = c->start_time; sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; sc->recv = ngx_quic_stream_recv; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f05198903..8a5aeeb14 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -165,7 +165,6 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; - ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; @@ -199,12 +198,10 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) return; } - pc = c->quic->parent; - h3c->next_request_id = c->quic->id + 0x04; if (n + 1 == clcf->keepalive_requests - || ngx_current_msec - pc->start_time > clcf->keepalive_time) + || ngx_current_msec - c->start_time > clcf->keepalive_time) { h3c->goaway = 1; -- cgit v1.2.3 From 77fc6b7fb97f41b900d36a3180cd06c60ae48e52 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Jan 2023 14:05:18 +0400 Subject: QUIC: automatically add and never delete stream events. Previously, stream events were added and deleted by ngx_handle_read_event() and ngx_handle_write_event() in a way similar to level-triggered events. However, QUIC stream events are effectively edge-triggered and can stay active all time. Moreover, the events are now active since the moment a stream is created. --- src/event/ngx_event.c | 4 ++-- src/event/quic/ngx_event_quic.h | 2 -- src/event/quic/ngx_event_quic_streams.c | 35 +++++++-------------------------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index 5a7559777..ef525d93b 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -274,7 +274,7 @@ ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) c = rev->data; if (c->quic) { - return ngx_quic_handle_read_event(rev, flags); + return NGX_OK; } #endif @@ -353,7 +353,7 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) #if (NGX_QUIC) if (c->quic) { - return ngx_quic_handle_write_event(wev, lowat); + return NGX_OK; } #endif diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index d13adbdc9..56713ec4d 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -123,8 +123,6 @@ void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, 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_handle_read_event(ngx_event_t *rev, ngx_uint_t flags); -ngx_int_t ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat); 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_streams.c b/src/event/quic/ngx_event_quic_streams.c index 785625547..e062b1fa1 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -700,9 +700,16 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) { + sc->write->active = 1; sc->write->ready = 1; } + if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + || (id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + sc->read->active = 1; + } + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { qs->send_max_data = qc->ctp.initial_max_stream_data_uni; @@ -1746,31 +1753,3 @@ ngx_quic_set_event(ngx_event_t *ev) ngx_post_event(ev, &ngx_posted_events); } } - - -ngx_int_t -ngx_quic_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) -{ - if (!rev->active && !rev->ready) { - rev->active = 1; - - } else if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) { - rev->active = 0; - } - - return NGX_OK; -} - - -ngx_int_t -ngx_quic_handle_write_event(ngx_event_t *wev, size_t lowat) -{ - if (!wev->active && !wev->ready) { - wev->active = 1; - - } else if (wev->active && wev->ready) { - wev->active = 0; - } - - return NGX_OK; -} -- cgit v1.2.3 From d929470685239762330d61a4be0200edc2ad315f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Jan 2023 17:42:40 +0400 Subject: QUIC: set stream error flag on reset. Now, when RESET_STREAM is sent or received, or when streams are closed, stream connection error flag is set. Previously, only stream state was changed, which resulted in setting the error flag only after calling recv()/send()/send_chain(). However, there are cases when none of these functions is called, but it's still important to know if the stream is being closed. For example, when an HTTP/3 request stream is blocked on insert count, receiving RESET_STREAM should trigger stream closure, which was not the case. The change also fixes ngx_http_upstream_check_broken_connection() and ngx_http_test_reading() with QUIC streams. --- src/event/quic/ngx_event_quic_streams.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index e062b1fa1..87b6caf4e 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -203,6 +203,9 @@ ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) continue; } + sc->read->error = 1; + sc->write->error = 1; + ngx_quic_set_event(sc->read); ngx_quic_set_event(sc->write); @@ -245,6 +248,10 @@ ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; qs->send_final_size = qs->send_offset; + if (qs->connection) { + qs->connection->write->error = 1; + } + pc = qs->parent; qc = ngx_quic_get_connection(pc); @@ -805,7 +812,6 @@ ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) { qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; - rev->error = 1; return NGX_ERROR; } @@ -1383,6 +1389,7 @@ ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) { + ngx_event_t *rev; ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; @@ -1439,7 +1446,10 @@ ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, return ngx_quic_close_stream(qs); } - ngx_quic_set_event(qs->connection->read); + rev = qs->connection->read; + rev->error = 1; + + ngx_quic_set_event(rev); return NGX_OK; } -- cgit v1.2.3 From faa655f211612fcdac938d58095a4ab7f03969a9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 5 Jan 2023 18:15:46 +0400 Subject: HTTP/3: trigger 400 (Bad Request) on stream error while blocked. Previously, stream was closed with NGX_HTTP_CLOSE. However, in a similar case when recv() returns eof or error, status 400 is triggered. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 8a5aeeb14..e8b84eabd 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -551,7 +551,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) if (rc == NGX_BUSY) { if (rev->error) { - ngx_http_close_request(r, NGX_HTTP_CLOSE); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); break; } -- cgit v1.2.3 From 0065ba68b08b8cf4eaf3c18266d1a93182f196ed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 5 Jan 2023 19:03:22 +0400 Subject: HTTP/3: insert count block timeout. Previously, there was no timeout for a request stream blocked on insert count, which could result in infinite wait. Now client_header_timeout is set when stream is first blocked. --- src/http/v3/ngx_http_v3_request.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e8b84eabd..7bf61459f 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -555,6 +555,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } -- cgit v1.2.3 From d76600874c495728b801fd6ec33978194928a6e4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Jan 2023 17:24:10 +0400 Subject: QUIC: relocated ngx_quic_init_streams() for 0-RTT. Previously, streams were initialized in early keys handler. However, client transport parameters may not be available by then. This happens, for example, when using QuicTLS. Now streams are initialized in ngx_quic_crypto_input() after calling SSL_do_handshake() for both 0-RTT and 1-RTT. --- src/event/quic/ngx_event_quic_ssl.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index fd0d8252e..0c982bc62 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -67,12 +67,6 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, return 0; } - if (level == ssl_encryption_early_data) { - if (ngx_quic_init_streams(c) != NGX_OK) { - return 0; - } - } - return 1; } @@ -138,10 +132,6 @@ ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, } if (level == ssl_encryption_early_data) { - if (ngx_quic_init_streams(c) != NGX_OK) { - return 0; - } - return 1; } @@ -455,11 +445,17 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) qc->error_reason = "handshake failed"; return NGX_ERROR; } - - return NGX_OK; } - if (SSL_in_init(ssl_conn)) { + if (n <= 0 || SSL_in_init(ssl_conn)) { + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data) + && qc->client_tp_done) + { + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + } + return NGX_OK; } -- cgit v1.2.3 From b196429fbe5f2b60f464f94e93facb0889741dc6 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 18 Jan 2023 19:20:18 +0400 Subject: QUIC: defer setting the active flag for client stream events. Specifically, now it is kept unset until streams are initialized. Notably, this unbreaks OCSP with client certificates after 35e27117b593. Previously, the read event could be posted prematurely via ngx_quic_set_event() e.g., as part of handling a STREAM frame. --- src/event/quic/ngx_event_quic_streams.c | 39 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 87b6caf4e..db3208ce1 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -46,8 +46,8 @@ ngx_connection_t * ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) { uint64_t id; - ngx_connection_t *pc; - ngx_quic_stream_t *nqs; + ngx_connection_t *pc, *sc; + ngx_quic_stream_t *qs; ngx_quic_connection_t *qc; pc = c->quic ? c->quic->parent : c; @@ -101,12 +101,21 @@ ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) qc->streams.server_streams_uni++; } - nqs = ngx_quic_create_stream(pc, id); - if (nqs == NULL) { + qs = ngx_quic_create_stream(pc, id); + if (qs == NULL) { return NULL; } - return nqs->connection; + sc = qs->connection; + + sc->write->active = 1; + sc->write->ready = 1; + + if (bidi) { + sc->read->active = 1; + } + + return sc; } @@ -534,6 +543,13 @@ ngx_quic_init_stream_handler(ngx_event_t *ev) 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); @@ -704,19 +720,6 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) log->connection = sc->number; - if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - || (id & NGX_QUIC_STREAM_SERVER_INITIATED)) - { - sc->write->active = 1; - sc->write->ready = 1; - } - - if ((id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - || (id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) - { - sc->read->active = 1; - } - if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { qs->send_max_data = qc->ctp.initial_max_stream_data_uni; -- cgit v1.2.3 From c3edcc17b6843bbfeb61f2dc7c51115b6a4272b0 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Tue, 24 Jan 2023 02:57:42 +0300 Subject: QUIC: improved SO_COOKIE configure test. In nginx source code the inttypes.h include, if available, is used to define standard integer types. Changed the SO_COOKIE configure test to follow this. --- auto/os/linux | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auto/os/linux b/auto/os/linux index 87b0e1281..4849baf0e 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -263,7 +263,7 @@ ngx_feature="SO_COOKIE" ngx_feature_name="NGX_HAVE_SO_COOKIE" ngx_feature_run=no ngx_feature_incs="#include - #include " + $NGX_INCLUDE_INTTYPES_H" ngx_feature_path= ngx_feature_libs= ngx_feature_test="socklen_t optlen = sizeof(uint64_t); -- cgit v1.2.3 From 341c21c9f6373ceeb0ad2513e14c5cd97e958b28 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 31 Jan 2023 14:12:18 +0400 Subject: QUIC: ngx_quic_copy_buffer() function. The function copies passed data to QUIC buffer chain and returns it. The chain can be used in ngx_quic_frame_t data field. --- src/event/quic/ngx_event_quic_frames.c | 33 +++++++++++++++++++++++++++++++++ src/event/quic/ngx_event_quic_frames.h | 2 ++ src/event/quic/ngx_event_quic_ssl.c | 23 ++--------------------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 9a1a6afe5..9fcc97e02 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -386,6 +386,39 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) } +ngx_chain_t * +ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len) +{ + ngx_buf_t buf; + ngx_chain_t cl, *out; + ngx_quic_buffer_t qb; + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + + if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + out = ngx_quic_read_buffer(c, &qb, len); + if (out == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + ngx_quic_free_buffer(c, &qb); + + return out; +} + + ngx_chain_t * ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) { diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h index fbff68e5d..2d55af9de 100644 --- a/src/event/quic/ngx_event_quic_frames.h +++ b/src/event/quic/ngx_event_quic_frames.h @@ -26,6 +26,8 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); +ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, + size_t len); ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit); ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 0c982bc62..b3059cb4b 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -160,13 +160,11 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, { u_char *p, *end; size_t client_params_len; - ngx_buf_t buf; - ngx_chain_t *out, cl; + ngx_chain_t *out; const uint8_t *client_params; ngx_quic_tp_t ctp; ngx_quic_frame_t *frame; ngx_connection_t *c; - ngx_quic_buffer_t qb; ngx_quic_send_ctx_t *ctx; ngx_quic_connection_t *qc; #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) @@ -243,28 +241,11 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, ctx = ngx_quic_get_send_ctx(qc, level); - ngx_memzero(&buf, sizeof(ngx_buf_t)); - - buf.pos = (u_char *) data; - buf.last = buf.pos + len; - buf.temporary = 1; - - cl.buf = &buf; - cl.next = NULL; - - ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); - - if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { - return 0; - } - - out = ngx_quic_read_buffer(c, &qb, len); + out = ngx_quic_copy_buffer(c, (u_char *) data, len); if (out == NGX_CHAIN_ERROR) { return 0; } - ngx_quic_free_buffer(c, &qb); - frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return 0; -- cgit v1.2.3 From b7ccca0eb07ebf5f0a78c21cc45f7ac865dda986 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 31 Jan 2023 15:26:33 +0400 Subject: QUIC: fixed broken token in NEW_TOKEN (ticket #2446). Previously, since 3550b00d9dc8, the token was allocated on stack, to get rid of pool usage. Now the token is allocated by ngx_quic_copy_buffer() in QUIC buffers, also used for STREAM, CRYPTO and ACK frames. --- src/event/quic/ngx_event_quic_frames.c | 14 ++++++++++++++ src/event/quic/ngx_event_quic_output.c | 8 +++++++- src/event/quic/ngx_event_quic_transport.c | 19 +++++++++++++------ src/event/quic/ngx_event_quic_transport.h | 1 - 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 9fcc97e02..040b6182c 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -858,6 +858,20 @@ ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) case NGX_QUIC_FT_NEW_TOKEN: p = ngx_slprintf(p, last, "NEW_TOKEN"); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " token:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + break; case NGX_QUIC_FT_HANDSHAKE_DONE: diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index ee64d555e..940432a45 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1076,6 +1076,7 @@ ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) { time_t expires; ngx_str_t token; + ngx_chain_t *out; ngx_quic_frame_t *frame; ngx_quic_connection_t *qc; @@ -1095,6 +1096,11 @@ ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) return NGX_ERROR; } + out = ngx_quic_copy_buffer(c, token.data, token.len); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + frame = ngx_quic_alloc_frame(c); if (frame == NULL) { return NGX_ERROR; @@ -1102,8 +1108,8 @@ ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) frame->level = ssl_encryption_application; frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->data = out; frame->u.token.length = token.len; - frame->u.token.data = token.data; ngx_quic_queue_frame(qc, frame); diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 3838f6463..62566d25a 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -109,7 +109,7 @@ static size_t ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); static size_t ngx_quic_create_hs_done(u_char *p); static size_t ngx_quic_create_new_token(u_char *p, - ngx_quic_new_token_frame_t *token); + ngx_quic_new_token_frame_t *token, ngx_chain_t *data); static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, ngx_chain_t *data); static size_t ngx_quic_create_max_streams(u_char *p, @@ -1301,7 +1301,7 @@ ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) return ngx_quic_create_hs_done(p); case NGX_QUIC_FT_NEW_TOKEN: - return ngx_quic_create_new_token(p, &f->u.token); + return ngx_quic_create_new_token(p, &f->u.token, f->data); case NGX_QUIC_FT_STREAM: return ngx_quic_create_stream(p, &f->u.stream, f->data); @@ -1491,10 +1491,12 @@ ngx_quic_create_hs_done(u_char *p) static size_t -ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token, + ngx_chain_t *data) { - size_t len; - u_char *start; + size_t len; + u_char *start; + ngx_buf_t *b; if (p == NULL) { len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); @@ -1508,7 +1510,12 @@ ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token) ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); ngx_quic_build_int(&p, token->length); - p = ngx_cpymem(p, token->data, token->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } return p - start; } diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h index 6f95f85ad..16d9095ef 100644 --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -167,7 +167,6 @@ typedef struct { typedef struct { uint64_t length; - u_char *data; } ngx_quic_new_token_frame_t; /* -- cgit v1.2.3 From 3b57dcecafd96e0dcbf46a1e5adb46c97d7eb73a Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 8 Feb 2023 12:47:35 +0400 Subject: README: updated building from sources, added directives reference. --- README | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 11 deletions(-) diff --git a/README b/README index 7f545031a..d9a12e273 100644 --- a/README +++ b/README @@ -51,7 +51,16 @@ Experimental QUIC support for nginx + Lost packets are detected and retransmitted properly + Clients may migrate to new address -2. Installing +2. Building from sources + + The build is configured using the configure command. + Refer to http://nginx.org/en/docs/configure.html for details. + + When configuring nginx, it's possible to enable QUIC and HTTP/3 + using the following new configuration options: + + --with-http_v3_module - enable QUIC and HTTP/3 + --with-stream_quic_module - enable QUIC in Stream A library that provides QUIC support is required to build nginx, there are several of those available on the market: @@ -85,12 +94,6 @@ Experimental QUIC support for nginx --with-cc-opt="-I../libressl/build/include" \ --with-ld-opt="-L../libressl/build/lib" - When configuring nginx, it's possible to enable QUIC and HTTP/3 - using the following new configuration options: - - --with-http_v3_module - enable QUIC and HTTP/3 - --with-stream_quic_module - enable QUIC in Stream - 3. Configuration The HTTP "listen" directive got a new option "http3" which enables @@ -174,7 +177,129 @@ Example configuration: } } -4. Clients +4. Directives + + Syntax: quic_bpf on | off; + Default: quic_bpf off; + Context: main + + Enables routing of QUIC packets using eBPF. + When enabled, this allows to support QUIC connection migration. + The directive is only supported on Linux 5.7+. + + + Syntax: quic_retry on | off; + Default: quic_retry off; + Context: http | stream, server + + Enables the QUIC Address Validation feature. This includes: + - sending a new token in a Retry packet or a NEW_TOKEN frame + - validating a token received in the Initial packet + + + Syntax: quic_gso on | off; + Default: quic_gso off; + Context: http | stream, server + + Enables sending in optimized batch mode using segmentation offloading. + Optimized sending is only supported on Linux featuring UDP_SEGMENT. + + + Syntax: quic_mtu size; + Default: quic_mtu 65527; + Context: http | stream, server + + Sets the QUIC max_udp_payload_size transport parameter value. + This is the maximum UDP payload that we are willing to receive. + + + Syntax: quic_host_key file; + Default: - + Context: http | stream, server + + Specifies a file with the secret key used to encrypt stateless reset and + address validation tokens. By default, a randomly generated key is used. + + + Syntax: quic_active_connection_id_limit number; + Default: quic_active_connection_id_limit 2; + Context: http | stream, server + + Sets the QUIC active_connection_id_limit transport parameter value. + This is the maximum number of connection IDs we are willing to store. + + + Syntax: quic_timeout time; + Default: quic_timeout 60s; + Context: stream, server + + Defines a timeout used to negotiate the QUIC idle timeout. + In the http module, it is taken from the keepalive_timeout directive. + + + Syntax: quic_stream_buffer_size size; + Default: quic_stream_buffer_size 64k; + Context: stream, server + + Syntax: http3_stream_buffer_size size; + Default: http3_stream_buffer_size 64k; + Context: http, server + + Sets buffer size for reading and writing of the QUIC STREAM payload. + The buffer size is used to calculate initial flow control limits + in the following QUIC transport parameters: + - initial_max_data + - initial_max_stream_data_bidi_local + - initial_max_stream_data_bidi_remote + - initial_max_stream_data_uni + + + Syntax: http3_max_concurrent_pushes number; + Default: http3_max_concurrent_pushes 10; + Context: http, server + + Limits the maximum number of concurrent push requests in a connection. + + + Syntax: http3_max_concurrent_streams number; + Default: http3_max_concurrent_streams 128; + Context: http, server + + Sets the maximum number of concurrent HTTP/3 streams in a connection. + + + Syntax: http3_push uri | off; + Default: http3_push off; + Context: http, server, location + + Pre-emptively sends (pushes) a request to the specified uri along with + the response to the original request. Only relative URIs with absolute + path will be processed, for example: + + http3_push /static/css/main.css; + + The uri value can contain variables. + + Several http3_push directives can be specified on the same configuration + level. The off parameter cancels the effect of the http3_push directives + inherited from the previous configuration level. + + + Syntax: http3_push_preload on | off; + Default: http3_push_preload off; + Context: http, server, location + + Enables automatic conversion of preload links specified in the “Link” + response header fields into push requests. + + + Syntax: http3_hq on | off; + Default: http3_hq off; + Context: http, server + + Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. + +5. Clients * Browsers @@ -201,7 +326,7 @@ Example configuration: "nghttp3/ngtcp2 client" "quic" -5. Troubleshooting +6. Troubleshooting Here are some tips that may help to identify problems: @@ -231,12 +356,12 @@ Example configuration: #define NGX_QUIC_DEBUG_ALLOC #define NGX_QUIC_DEBUG_CRYPTO -6. Contributing +7. Contributing Please refer to http://nginx.org/en/docs/contributing_changes.html -7. Links +8. Links [1] https://datatracker.ietf.org/doc/html/rfc9000 [2] https://datatracker.ietf.org/doc/html/rfc9114 -- cgit v1.2.3 From f36da1dfa6e864f2d82589e3995e8c2af64688a0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Feb 2023 13:41:35 +0400 Subject: README: fixed toc. While here, updated link to mailman. --- README | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README b/README index d9a12e273..bf64130cf 100644 --- a/README +++ b/README @@ -2,12 +2,13 @@ Experimental QUIC support for nginx ----------------------------------- 1. Introduction -2. Installing +2. Building from sources 3. Configuration -4. Clients -5. Troubleshooting -6. Contributing -7. Links +4. Directives +5. Clients +6. Troubleshooting +7. Contributing +8. Links 1. Introduction @@ -365,7 +366,7 @@ Example configuration: [1] https://datatracker.ietf.org/doc/html/rfc9000 [2] https://datatracker.ietf.org/doc/html/rfc9114 - [3] https://mailman.nginx.org/mailman3/lists/nginx-devel.nginx.org/ + [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel [4] https://boringssl.googlesource.com/boringssl/ [5] https://www.libressl.org/ [6] https://github.com/quictls/openssl -- cgit v1.2.3 From 40af97fdec70604a802a520c6473760daa03e8db Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 13 Feb 2023 14:01:50 +0400 Subject: QUIC: fixed indentation. --- src/event/quic/ngx_event_quic_output.c | 4 ++-- src/event/quic/ngx_event_quic_streams.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 940432a45..d2ced99a1 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -644,7 +644,7 @@ ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, if (len < min_payload) { ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); len = min_payload; - } + } pkt.payload.data = src; pkt.payload.len = len; @@ -1254,7 +1254,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, if (len < (ssize_t) min_payload) { ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); len = min_payload; - } + } pkt.payload.data = src; pkt.payload.len = len; diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index db3208ce1..6f0a752a7 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -1162,7 +1162,7 @@ ngx_quic_can_shutdown(ngx_connection_t *c) return NGX_DECLINED; } } - } + } return NGX_OK; } -- cgit v1.2.3 From 1ccba18f00551c75df0365127ad4e146406fafec Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 23 Feb 2023 15:49:59 +0400 Subject: QUIC: using NGX_QUIC_ERR_CRYPTO macro in ALPN checks. Patch by Jiuzhou Cui. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index b3059cb4b..58b03cb3a 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -190,7 +190,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); if (alpn_len == 0) { - qc->error = 0x100 + SSL_AD_NO_APPLICATION_PROTOCOL; + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); qc->error_reason = "unsupported protocol in ALPN extension"; ngx_log_error(NGX_LOG_INFO, c->log, 0, -- cgit v1.2.3 From 5149620d6d41571306257f001fa5fd412168a866 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 23 Feb 2023 16:16:56 +0400 Subject: QUIC: moved "handshake failed" reason to send_alert. A QUIC handshake failure breaks down into several cases: - a handshake error which leads to a send_alert call - an error triggered by the add_handshake_data callback - internal errors (allocation etc) Previously, in the first case, only error code was set in the send_alert callback. Now the "handshake failed" reason phrase is set there as well. In the second case, both code and reason are set by add_handshake_data. In the last case, setting reason phrase is removed: returning NGX_ERROR now leads to closing the connection with just INTERNAL_ERROR. Reported by Jiuzhou Cui. --- src/event/quic/ngx_event_quic_ssl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 58b03cb3a..595abed89 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -301,6 +301,7 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, } qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "handshake failed"; return 1; } @@ -423,7 +424,6 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); - qc->error_reason = "handshake failed"; return NGX_ERROR; } } -- cgit v1.2.3 From d9610b40a658a6331fc61cb41d7d255f9566cd2e Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 23 Feb 2023 16:17:29 +0400 Subject: QUIC: using ngx_ssl_handshake_log(). --- src/event/ngx_event_openssl.c | 5 +---- src/event/ngx_event_openssl.h | 3 +++ src/event/quic/ngx_event_quic_ssl.c | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 85c52e9dc..d76ce231a 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, #ifdef SSL_READ_EARLY_DATA_SUCCESS static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); #endif -#if (NGX_DEBUG) -static void ngx_ssl_handshake_log(ngx_connection_t *c); -#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); #ifdef SSL_READ_EARLY_DATA_SUCCESS static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, @@ -2052,7 +2049,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c) #if (NGX_DEBUG) -static void +void ngx_ssl_handshake_log(ngx_connection_t *c) { char buf[129], *s, *d; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 3a6a1d2bb..c062f912c 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -310,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); +#if (NGX_DEBUG) +void ngx_ssl_handshake_log(ngx_connection_t *c); +#endif ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size); ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 595abed89..2df38b386 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -440,11 +440,9 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) return NGX_OK; } - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic ssl cipher:%s", SSL_get_cipher(ssl_conn)); - - ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic handshake completed successfully"); +#if (NGX_DEBUG) + ngx_ssl_handshake_log(c); +#endif c->ssl->handshaked = 1; -- cgit v1.2.3 From 76adb919138225b24280bc477ff468fd13cc9e62 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 23 Feb 2023 16:26:38 +0400 Subject: QUIC: improved ssl_reject_handshake error logging. The check follows the ngx_ssl_handshake() change in 59e1c73fe02b. --- src/event/quic/ngx_event_quic_ssl.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 2df38b386..f23260d24 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -423,6 +423,14 @@ ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data) sslerr); if (sslerr != SSL_ERROR_WANT_READ) { + + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + + return NGX_ERROR; + } + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); return NGX_ERROR; } -- cgit v1.2.3 From a36ebf7e95baebf445b0973bd270bc009b0b0e9a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Feb 2023 19:16:53 +0400 Subject: QUIC: OpenSSL compatibility layer. The change allows to compile QUIC with OpenSSL which lacks BoringSSL QUIC API. This implementation does not support 0-RTT. --- README | 7 +- auto/lib/openssl/conf | 54 ++- auto/modules | 6 +- src/event/quic/ngx_event_quic_connection.h | 7 + src/event/quic/ngx_event_quic_openssl_compat.c | 646 +++++++++++++++++++++++++ src/event/quic/ngx_event_quic_openssl_compat.h | 60 +++ src/event/quic/ngx_event_quic_protection.c | 47 +- src/event/quic/ngx_event_quic_protection.h | 39 ++ src/event/quic/ngx_event_quic_ssl.c | 13 +- src/http/modules/ngx_http_ssl_module.c | 16 +- src/stream/ngx_stream_ssl_module.c | 10 + 11 files changed, 829 insertions(+), 76 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_openssl_compat.c create mode 100644 src/event/quic/ngx_event_quic_openssl_compat.h diff --git a/README b/README index bf64130cf..33ab94ebd 100644 --- a/README +++ b/README @@ -63,12 +63,17 @@ Experimental QUIC support for nginx --with-http_v3_module - enable QUIC and HTTP/3 --with-stream_quic_module - enable QUIC in Stream - A library that provides QUIC support is required to build nginx, there + A library that provides QUIC support is recommended to build nginx, there are several of those available on the market: + BoringSSL [4] + LibreSSL [5] + QuicTLS [6] + Alternatively, nginx can be configured with OpenSSL compatibility + layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is + enabled by default if native QUIC support is not detected. + 0-RTT is not supported in OpenSSL compatibility mode. + Clone the NGINX QUIC repository $ hg clone -b quic https://hg.nginx.org/nginx-quic diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 9ab062091..cfa74cf81 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -10,6 +10,7 @@ if [ $OPENSSL != NONE ]; then if [ $USE_OPENSSL_QUIC = YES ]; then have=NGX_QUIC . auto/have + have=NGX_QUIC_OPENSSL_COMPAT . auto/have fi case "$CC" in @@ -124,6 +125,35 @@ else CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" OPENSSL=YES + + if [ $USE_OPENSSL_QUIC = YES ]; then + + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_QUIC" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + + ngx_feature="OpenSSL QUIC compatibility" + ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0, + NULL, NULL, NULL, NULL, NULL)" + . auto/feature + fi + + if [ $ngx_found = no ]; then +cat << END + +$0: error: certain modules require OpenSSL QUIC support. +You can either do not enable the modules, or install the OpenSSL library with +QUIC support into the system, or build the OpenSSL library with QUIC support +statically from the source with nginx by using --with-openssl= option. + +END + exit 1 + fi + fi fi fi @@ -140,28 +170,4 @@ END exit 1 fi - if [ $USE_OPENSSL_QUIC = YES ]; then - - ngx_feature="OpenSSL QUIC support" - ngx_feature_name="NGX_QUIC" - ngx_feature_run=no - ngx_feature_incs="#include " - ngx_feature_path= - ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" - ngx_feature_test="SSL_set_quic_method(NULL, NULL)" - . auto/feature - - if [ $ngx_found = no ]; then - -cat << END - -$0: error: certain modules require OpenSSL QUIC support. -You can either do not enable the modules, or install the OpenSSL library with -QUIC support into the system, or build the OpenSSL library with QUIC support -statically from the source with nginx by using --with-openssl= option. - -END - exit 1 - fi - fi fi diff --git a/auto/modules b/auto/modules index 575fff0b7..08a33cacc 100644 --- a/auto/modules +++ b/auto/modules @@ -1342,7 +1342,8 @@ if [ $USE_OPENSSL_QUIC = YES ]; then src/event/quic/ngx_event_quic_tokens.h \ src/event/quic/ngx_event_quic_ack.h \ src/event/quic/ngx_event_quic_output.h \ - src/event/quic/ngx_event_quic_socket.h" + src/event/quic/ngx_event_quic_socket.h \ + src/event/quic/ngx_event_quic_openssl_compat.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ src/event/quic/ngx_event_quic_udp.c \ src/event/quic/ngx_event_quic_transport.c \ @@ -1355,7 +1356,8 @@ if [ $USE_OPENSSL_QUIC = YES ]; then src/event/quic/ngx_event_quic_tokens.c \ src/event/quic/ngx_event_quic_ack.c \ src/event/quic/ngx_event_quic_output.c \ - src/event/quic/ngx_event_quic_socket.c" + src/event/quic/ngx_event_quic_socket.c \ + src/event/quic/ngx_event_quic_openssl_compat.c" ngx_module_libs= ngx_module_link=YES diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 2b198ac6f..466f90f93 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -25,6 +25,9 @@ typedef struct ngx_quic_socket_s ngx_quic_socket_t; typedef struct ngx_quic_path_s ngx_quic_path_t; typedef struct ngx_quic_keys_s ngx_quic_keys_t; +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif #include #include #include @@ -236,6 +239,10 @@ struct ngx_quic_connection_s { ngx_uint_t nshadowbufs; #endif +#if (NGX_QUIC_OPENSSL_COMPAT) + ngx_quic_compat_t *compat; +#endif + ngx_quic_streams_t streams; ngx_quic_congestion_t congestion; diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c new file mode 100644 index 000000000..51430e4b9 --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -0,0 +1,646 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 + +#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 + +#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" +#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" + + +typedef struct { + ngx_quic_secret_t secret; + ngx_uint_t cipher; +} ngx_quic_compat_keys_t; + + +typedef struct { + ngx_log_t *log; + + u_char type; + ngx_str_t payload; + uint64_t number; + ngx_quic_compat_keys_t *keys; + + enum ssl_encryption_level_t level; +} ngx_quic_compat_record_t; + + +struct ngx_quic_compat_s { + const SSL_QUIC_METHOD *method; + + enum ssl_encryption_level_t write_level; + enum ssl_encryption_level_t read_level; + + uint64_t read_record; + ngx_quic_compat_keys_t keys; + + ngx_str_t tp; + ngx_str_t ctp; +}; + + +static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line); +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char **out, + size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); +static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char *in, + size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); +static void ngx_quic_compat_message_callback(int write_p, int version, + int content_type, const void *buf, size_t len, SSL *ssl, void *arg); +static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, + u_char *out, ngx_uint_t plain); +static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx) +{ + SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); + + if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) { + return NGX_OK; + } + + if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, + SSL_EXT_CLIENT_HELLO + |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + ngx_quic_compat_add_transport_params_callback, + NULL, + NULL, + ngx_quic_compat_parse_transport_params_callback, + NULL) + == 0) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_add_custom_ext() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) +{ + u_char ch, *p, *start, value; + size_t n; + ngx_uint_t write; + const SSL_CIPHER *cipher; + ngx_quic_compat_t *com; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + u_char secret[EVP_MAX_MD_SIZE]; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return; + } + + p = (u_char *) line; + + for (start = p; *p && *p != ' '; p++); + + n = p - start; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat secret %*s", n, start); + + if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 0; + + } 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; + + } 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; + + } 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; + + } else { + return; + } + + if (*p++ == '\0') { + return; + } + + for ( /* void */ ; *p && *p != ' '; p++); + + if (*p++ == '\0') { + return; + } + + for (n = 0, start = p; *p; p++) { + ch = *p; + + if (ch >= '0' && ch <= '9') { + value = ch - '0'; + goto next; + } + + ch = (u_char) (ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + value = ch - 'a' + 10; + goto next; + } + + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "invalid OpenSSL QUIC secret format"); + + return; + + next: + + if ((p - start) % 2) { + secret[n++] += value; + + } else { + if (n >= EVP_MAX_MD_SIZE) { + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "too big OpenSSL QUIC secret"); + return; + } + + secret[n] = (value << 4); + } + } + + qc = ngx_quic_get_connection(c); + com = qc->compat; + cipher = SSL_get_current_cipher(ssl); + + if (write) { + com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); + com->write_level = level; + + } else { + com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); + com->read_level = level; + com->read_record = 0; + + (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level, + cipher, secret, n); + } +} + + +static ngx_int_t +ngx_quic_compat_set_encryption_secret(ngx_log_t *log, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_hkdf_t seq[2]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = &keys->secret; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "unexpected secret len: %uz", secret_len); + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + peer_secret->key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static int +ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat add transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out = com->tp.data; + *outlen = com->tp.len; + + return 1; +} + + +static int +ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg) +{ + u_char *p; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat parse transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + p = ngx_pnalloc(c->pool, inlen); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, in, inlen); + + com->ctp.data = p; + com->ctp.len = inlen; + + return 1; +} + + +int +SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) +{ + BIO *rbio, *wbio; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); + + qc = ngx_quic_get_connection(c); + + qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); + if (qc->compat == NULL) { + return 0; + } + + com = qc->compat; + com->method = quic_method; + + rbio = BIO_new(BIO_s_mem()); + if (rbio == NULL) { + return 0; + } + + wbio = BIO_new(BIO_s_null()); + if (wbio == NULL) { + return 0; + } + + SSL_set_bio(ssl, rbio, wbio); + + SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); + + /* early data is not supported */ + SSL_set_max_early_data(ssl, 0); + + return 1; +} + + +static void +ngx_quic_compat_message_callback(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + ngx_uint_t alert; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + + if (!write_p) { + return; + } + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + /* closing */ + return; + } + + com = qc->compat; + level = com->write_level; + + switch (content_type) { + + case SSL3_RT_HANDSHAKE: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat tx %s len:%uz ", + ngx_quic_level_name(level), len); + + (void) com->method->add_handshake_data(ssl, level, buf, len); + + break; + + case SSL3_RT_ALERT: + if (len >= 2) { + alert = ((u_char *) buf)[1]; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat %s alert:%ui len:%uz ", + ngx_quic_level_name(level), alert, len); + + (void) com->method->send_alert(ssl, level, alert); + } + + break; + } +} + + +int +SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) +{ + BIO *rbio; + size_t n; + u_char *p; + ngx_str_t res; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + ngx_quic_compat_record_t rec; + u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; + u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 + + SSL3_RT_HEADER_LENGTH + + EVP_GCM_TLS_TAG_LEN]; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz", + ngx_quic_level_name(level), len); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + rbio = SSL_get_rbio(ssl); + + while (len) { + ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); + + rec.type = SSL3_RT_HANDSHAKE; + rec.log = c->log; + rec.number = com->read_record++; + rec.keys = &com->keys; + + if (level == ssl_encryption_initial) { + n = ngx_min(len, 65535); + + rec.payload.len = n; + rec.payload.data = (u_char *) data; + + ngx_quic_compat_create_header(&rec, out, 1); + + BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); + BIO_write(rbio, data, n); + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %*xs%*xs", + n + SSL3_RT_HEADER_LENGTH, + (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); +#endif + + } else { + n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); + + p = ngx_cpymem(in, data, n); + *p++ = SSL3_RT_HANDSHAKE; + + rec.payload.len = p - in; + rec.payload.data = in; + + res.data = out; + + if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { + return 0; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %xV", res.len, &res); +#endif + + BIO_write(rbio, res.data, res.len); + } + + data += n; + len -= n; + } + + return 1; +} + + +static size_t +ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, + ngx_uint_t plain) +{ + u_char type; + size_t len; + + len = rec->payload.len; + + if (plain) { + type = rec->type; + + } else { + type = SSL3_RT_APPLICATION_DATA; + len += EVP_GCM_TLS_TAG_LEN; + } + + out[0] = type; + out[1] = 0x03; + out[2] = 0x03; + out[3] = (len >> 8); + out[4] = len; + + return 5; +} + + +static ngx_int_t +ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) +{ + ngx_str_t ad, out; + ngx_quic_secret_t *secret; + ngx_quic_ciphers_t ciphers; + u_char nonce[NGX_QUIC_IV_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); + + out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, + "quic compat ad len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == NGX_ERROR) + { + return NGX_ERROR; + } + + secret = &rec->keys->secret; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); + + if (ngx_quic_tls_seal(ciphers.c, secret, &out, + nonce, &rec->payload, &ad, rec->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +enum ssl_encryption_level_t +SSL_quic_read_level(const SSL *ssl) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + return qc->compat->read_level; +} + + +enum ssl_encryption_level_t +SSL_quic_write_level(const SSL *ssl) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + return qc->compat->write_level; +} + + +int +SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + com->tp.len = params_len; + com->tp.data = (u_char *) params; + + return 1; +} + + +void +SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params, + size_t *out_params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out_params = com->ctp.data; + *out_params_len = com->ctp.len; +} + +#endif /* NGX_QUIC_OPENSSL_COMPAT */ diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h b/src/event/quic/ngx_event_quic_openssl_compat.h new file mode 100644 index 000000000..d9800517c --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.h @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ + +#ifdef TLSEXT_TYPE_quic_transport_parameters +#undef NGX_QUIC_OPENSSL_COMPAT +#else + + +#include +#include + + +typedef struct ngx_quic_compat_s ngx_quic_compat_t; + + +enum ssl_encryption_level_t { + ssl_encryption_initial = 0, + ssl_encryption_early_data, + ssl_encryption_handshake, + ssl_encryption_application +}; + + +typedef struct ssl_quic_method_st { + int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len); + int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len); + int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + int (*flush_flight)(SSL *ssl); + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, + uint8_t alert); +} SSL_QUIC_METHOD; + + +ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx); + +int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); +enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl); +enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl); +int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len); +void SSL_get_peer_quic_transport_params(const SSL *ssl, + const uint8_t **out_params, size_t *out_params_len); + + +#endif /* TLSEXT_TYPE_quic_transport_parameters */ + +#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 91cfc6be3..7f772016b 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -23,37 +23,6 @@ #endif -#ifdef OPENSSL_IS_BORINGSSL -#define ngx_quic_cipher_t EVP_AEAD -#else -#define ngx_quic_cipher_t EVP_CIPHER -#endif - - -typedef struct { - const ngx_quic_cipher_t *c; - const EVP_CIPHER *hp; - const EVP_MD *d; -} ngx_quic_ciphers_t; - - -typedef struct { - size_t out_len; - u_char *out; - - size_t prk_len; - const uint8_t *prk; - - size_t label_len; - const u_char *label; -} ngx_quic_hkdf_t; - -#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ - (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ - (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ - (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); - - static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const u_char *prk, size_t prk_len, const u_char *info, size_t info_len); @@ -63,20 +32,12 @@ static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, uint64_t *largest_pn); -static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); -static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, - ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); -static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, - ngx_str_t *ad, ngx_log_t *log); static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, ngx_quic_secret_t *s, u_char *out, u_char *in); -static ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, - const EVP_MD *digest, ngx_log_t *log); static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res); @@ -84,7 +45,7 @@ static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res); -static ngx_int_t +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level) { @@ -221,7 +182,7 @@ ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, } -static ngx_int_t +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) { size_t info_len; @@ -480,7 +441,7 @@ ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, } -static ngx_int_t +ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) { @@ -961,7 +922,7 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, } -static void +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) { nonce[len - 8] ^= (pn >> 56) & 0x3f; diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index c8dc26bd1..27f8617d9 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -23,6 +23,13 @@ #define NGX_QUIC_MAX_MD_SIZE 48 +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#else +#define ngx_quic_cipher_t EVP_CIPHER +#endif + + typedef struct { size_t len; u_char data[NGX_QUIC_MAX_MD_SIZE]; @@ -56,6 +63,30 @@ struct ngx_quic_keys_s { }; +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +typedef struct { + size_t out_len; + u_char *out; + + size_t prk_len; + const uint8_t *prk; + + size_t label_len; + const u_char *label; +} ngx_quic_hkdf_t; + +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); + + ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, ngx_log_t *log); ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, @@ -70,6 +101,14 @@ void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, + enum ssl_encryption_level_t level); +ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, + ngx_str_t *ad, ngx_log_t *log); +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, + ngx_log_t *log); #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index f23260d24..6b0bae1ed 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -10,6 +10,13 @@ #include +#if defined OPENSSL_IS_BORINGSSL \ + || defined LIBRESSL_VERSION_NUMBER \ + || NGX_QUIC_OPENSSL_COMPAT +#define NGX_QUIC_BORINGSSL_API 1 +#endif + + /* * RFC 9000, 7.5. Cryptographic Message Buffering * @@ -18,7 +25,7 @@ #define NGX_QUIC_MAX_BUFFERED 65535 -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER +#if (NGX_QUIC_BORINGSSL_API) static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); @@ -39,7 +46,7 @@ static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data); -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER +#if (NGX_QUIC_BORINGSSL_API) static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, @@ -523,7 +530,7 @@ ngx_quic_init_connection(ngx_connection_t *c) ssl_conn = c->ssl->connection; if (!quic_method.send_alert) { -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER +#if (NGX_QUIC_BORINGSSL_API) quic_method.set_read_secret = ngx_quic_set_read_secret; quic_method.set_write_secret = ngx_quic_set_write_secret; #else diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 265cb3162..62ec13cf0 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -9,6 +9,10 @@ #include #include +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -1317,16 +1321,22 @@ ngx_http_ssl_init(ngx_conf_t *cf) continue; } + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + if (addr[a].opt.http3) { name = "http3"; +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } +#endif + } else { name = "ssl"; } - cscf = addr[a].default_server; - sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (sscf->certificates) { if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 4b98f2586..4a4e75514 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -9,6 +9,10 @@ #include #include +#if (NGX_QUIC_OPENSSL_COMPAT) +#include +#endif + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -1218,6 +1222,12 @@ ngx_stream_ssl_init(ngx_conf_t *cf) scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } +#endif + if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "\"ssl_protocols\" must enable TLSv1.3 for " -- cgit v1.2.3 From 815ef96124176baef4e940c4beaec158305b368a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Feb 2023 14:00:56 +0400 Subject: HTTP/3: "quic" parameter of "listen" directive. Now "listen" directve has a new "quic" parameter which enables QUIC protocol for the address. Further, to enable HTTP/3, a new directive "http3" is introduced. The hq-interop protocol is enabled by "http3_hq" as before. Now application protocol is chosen by ALPN. Previously used "http3" parameter of "listen" is deprecated. --- README | 18 ++++++--- src/http/modules/ngx_http_ssl_module.c | 29 +++++++++------ src/http/ngx_http.c | 7 +++- src/http/ngx_http_core_module.c | 21 ++++++++++- src/http/ngx_http_core_module.h | 2 + src/http/ngx_http_request.c | 2 +- src/http/v3/ngx_http_v3.c | 16 ++------ src/http/v3/ngx_http_v3.h | 8 ++-- src/http/v3/ngx_http_v3_module.c | 50 ++++++++++++------------- src/http/v3/ngx_http_v3_request.c | 67 ++++++++++++++++++++++------------ src/http/v3/ngx_http_v3_uni.c | 4 -- 11 files changed, 134 insertions(+), 90 deletions(-) diff --git a/README b/README index 33ab94ebd..2e9eb9f0d 100644 --- a/README +++ b/README @@ -102,13 +102,13 @@ Experimental QUIC support for nginx 3. Configuration - The HTTP "listen" directive got a new option "http3" which enables - HTTP/3 over QUIC on the specified port. + The HTTP "listen" directive got a new option "quic" which enables + QUIC as client transport protocol instead of TCP. The Stream "listen" directive got a new option "quic" which enables QUIC as client transport protocol instead of TCP or plain UDP. - Along with "http3" or "quic", it's also possible to specify "reuseport" + Along with "quic", it's also possible to specify "reuseport" option [8] to make it work properly with multiple workers. To enable address validation: @@ -142,12 +142,13 @@ Experimental QUIC support for nginx A number of directives were added that configure HTTP/3: + http3 + http3_hq http3_stream_buffer_size http3_max_concurrent_pushes http3_max_concurrent_streams http3_push http3_push_preload - http3_hq (requires NGX_HTTP_V3_HQ macro) In http, an additional variable is available: $http3. The value of $http3 is "h3" for HTTP/3 connections, @@ -169,7 +170,7 @@ Example configuration: server { # for better compatibility it's recommended # to use the same port for quic and https - listen 8443 http3 reuseport; + listen 8443 quic reuseport; listen 8443 ssl; ssl_certificate certs/example.com.crt; @@ -299,6 +300,13 @@ Example configuration: response header fields into push requests. + Syntax: http3 on | off; + Default: http3 on; + Context: http, server + + Enables HTTP/3 protocol negotiation. + + Syntax: http3_hq on | off; Default: http3_hq off; Context: http, server diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 62ec13cf0..8167157e2 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -431,7 +431,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, #if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_http_connection_t *hc; #endif -#if (NGX_HTTP_V3 && NGX_HTTP_V3_HQ) +#if (NGX_HTTP_V3) ngx_http_v3_srv_conf_t *h3scf; #endif #if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) @@ -459,19 +459,26 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } else #endif #if (NGX_HTTP_V3) - if (hc->addr_conf->http3) { + if (hc->addr_conf->quic) { -#if (NGX_HTTP_V3_HQ) h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); - if (h3scf->hq) { + if (h3scf->enable && h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO + NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO) + - 1; + + } else if (h3scf->enable_hq) { srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; - } else -#endif - { + + } else if (h3scf->enable || hc->addr_conf->http3) { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; + + } else { + return SSL_TLSEXT_ERR_ALERT_FATAL; } } else @@ -1317,15 +1324,15 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl && !addr[a].opt.http3) { + if (!addr[a].opt.ssl && !addr[a].opt.quic) { continue; } cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (addr[a].opt.http3) { - name = "http3"; + if (addr[a].opt.quic) { + name = "quic"; #if (NGX_QUIC_OPENSSL_COMPAT) if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { @@ -1339,7 +1346,7 @@ ngx_http_ssl_init(ngx_conf_t *cf) if (sscf->certificates) { - if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "\"ssl_protocols\" must enable TLSv1.3 for " "the \"listen ... %s\" directive in %s:%ui", diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 134eed944..8ccf3c198 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1242,6 +1242,7 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif #if (NGX_HTTP_V3) ngx_uint_t http3; + ngx_uint_t quic; #endif /* @@ -1280,6 +1281,7 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif #if (NGX_HTTP_V3) http3 = lsopt->http3 || addr[i].opt.http3; + quic = lsopt->quic || addr[i].opt.quic; #endif if (lsopt->set) { @@ -1319,6 +1321,7 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #endif #if (NGX_HTTP_V3) addr[i].opt.http3 = http3; + addr[i].opt.quic = quic; #endif return NGX_OK; @@ -1823,7 +1826,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) #if (NGX_HTTP_V3) - ls->quic = addr->opt.http3; + ls->quic = addr->opt.quic; if (ls->quic) { ngx_rbtree_init(&ls->rbtree, &ls->sentinel, @@ -1866,6 +1869,7 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V3) addrs[i].conf.http3 = addr[i].opt.http3; + addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1934,6 +1938,7 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V3) addrs6[i].conf.http3 = addr[i].opt.http3; + addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 21966e698..f22d3bdab 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4191,6 +4191,10 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[n].data, "http3") == 0) { #if (NGX_HTTP_V3) + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"http3\" parameter is deprecated, " + "use \"quic\" parameter instead"); + lsopt.quic = 1; lsopt.http3 = 1; lsopt.type = SOCK_DGRAM; continue; @@ -4202,6 +4206,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[n].data, "quic") == 0) { +#if (NGX_HTTP_V3) + lsopt.quic = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_http_v3_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { @@ -4304,8 +4321,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } #if (NGX_HTTP_SSL && NGX_HTTP_V3) - if (lsopt.ssl && lsopt.http3) { - return "\"ssl\" parameter is incompatible with \"http3\""; + if (lsopt.ssl && lsopt.quic) { + return "\"ssl\" parameter is incompatible with \"quic\""; } #endif diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 430a70266..1bc84b0df 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -76,6 +76,7 @@ typedef struct { unsigned ssl:1; unsigned http2:1; unsigned http3:1; + unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -240,6 +241,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; unsigned http3:1; + unsigned quic:1; unsigned proxy_protocol:1; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 8cc3cff24..6cfae3435 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -325,7 +325,7 @@ ngx_http_init_connection(ngx_connection_t *c) #endif #if (NGX_HTTP_V3) - if (hc->addr_conf->http3) { + if (hc->addr_conf->quic) { ngx_http_v3_init_stream(c); return; } diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index b0cf15b76..6d4bddb38 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -17,12 +17,9 @@ static void ngx_http_v3_cleanup_session(void *data); 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; -#if (NGX_HTTP_V3_HQ) - ngx_http_v3_srv_conf_t *h3scf; -#endif + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; hc = c->data; @@ -36,13 +33,6 @@ ngx_http_v3_init_session(ngx_connection_t *c) h3c->max_push_id = (uint64_t) -1; h3c->goaway_push_id = (uint64_t) -1; -#if (NGX_HTTP_V3_HQ) - h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); - if (h3scf->hq) { - h3c->hq = 1; - } -#endif - ngx_queue_init(&h3c->blocked); ngx_queue_init(&h3c->pushing); diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 207a8c25b..2bb717cc8 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -21,6 +21,7 @@ #define NGX_HTTP_V3_ALPN_PROTO "\x02h3" #define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_V3_HQ_PROTO "hq-interop" #define NGX_HTTP_V3_VARLEN_INT_LEN 4 #define NGX_HTTP_V3_PREFIX_INT_LEN 11 @@ -101,13 +102,12 @@ typedef struct { + ngx_flag_t enable; + ngx_flag_t enable_hq; size_t max_table_capacity; ngx_uint_t max_blocked_streams; ngx_uint_t max_concurrent_pushes; ngx_uint_t max_concurrent_streams; -#if (NGX_HTTP_V3_HQ) - ngx_flag_t hq; -#endif ngx_quic_conf_t quic; } ngx_http_v3_srv_conf_t; @@ -147,9 +147,7 @@ struct ngx_http_v3_session_s { off_t payload_bytes; unsigned goaway:1; -#if (NGX_HTTP_V3_HQ) unsigned hq:1; -#endif ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; }; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index ed6becf31..02b88b479 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -32,6 +32,20 @@ static ngx_conf_post_t ngx_http_quic_mtu_post = static ngx_command_t ngx_http_v3_commands[] = { + { ngx_string("http3"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable), + NULL }, + + { ngx_string("http3_hq"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable_hq), + NULL }, + { ngx_string("http3_max_concurrent_pushes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -46,15 +60,6 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), NULL }, -#if (NGX_HTTP_V3_HQ) - { ngx_string("http3_hq"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, hq), - NULL }, -#endif - { ngx_string("http3_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_http_v3_push, @@ -160,14 +165,12 @@ static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - if (r->connection->quic) { -#if (NGX_HTTP_V3_HQ) + ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; - - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + if (r->connection->quic) { + h3c = ngx_http_v3_get_session(r->connection); - if (h3scf->hq) { + if (h3c->hq) { v->len = sizeof("hq") - 1; v->valid = 1; v->no_cacheable = 0; @@ -177,8 +180,6 @@ ngx_http_v3_variable(ngx_http_request_t *r, return NGX_OK; } -#endif - v->len = sizeof("h3") - 1; v->valid = 1; v->no_cacheable = 0; @@ -232,12 +233,12 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) * h3scf->quic.timeout = 0; * h3scf->max_blocked_streams = 0; */ + + h3scf->enable = NGX_CONF_UNSET; + h3scf->enable_hq = NGX_CONF_UNSET; h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; -#if (NGX_HTTP_V3_HQ) - h3scf->hq = NGX_CONF_UNSET; -#endif h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; @@ -264,6 +265,10 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_ssl_srv_conf_t *sscf; + ngx_conf_merge_value(conf->enable, prev->enable, 1); + + ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + ngx_conf_merge_uint_value(conf->max_concurrent_pushes, prev->max_concurrent_pushes, 10); @@ -272,11 +277,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) conf->max_blocked_streams = conf->max_concurrent_streams; -#if (NGX_HTTP_V3_HQ) - ngx_conf_merge_value(conf->hq, prev->hq, 0); -#endif - - ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7bf61459f..ff6b40734 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -110,7 +110,10 @@ ngx_http_v3_init_stream(ngx_connection_t *c) ngx_int_t ngx_http_v3_init(ngx_connection_t *c) { + unsigned int len; + const unsigned char *data; ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_core_loc_conf_t *clcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); @@ -119,11 +122,23 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); -#if (NGX_HTTP_V3_HQ) - if (h3c->hq) { - return NGX_OK; + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->enable_hq) { + if (!h3scf->enable) { + h3c->hq = 1; + return NGX_OK; + } + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 + && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) + { + h3c->hq = 1; + return NGX_OK; + } } -#endif return ngx_http_v3_send_settings(c); } @@ -147,10 +162,7 @@ ngx_http_v3_shutdown(ngx_connection_t *c) if (!h3c->goaway) { h3c->goaway = 1; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); } @@ -205,10 +217,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) { h3c->goaway = 1; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { ngx_http_close_connection(c); return; @@ -236,10 +245,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) rev = c->read; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { rev->handler = ngx_http_v3_wait_request_handler; c->write->handler = ngx_http_empty_handler; } @@ -398,14 +404,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_stream(ngx_connection_t *c) { + ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (h3scf->max_table_capacity > 0 && !c->read->eof -#if (NGX_HTTP_V3_HQ) - && !h3scf->hq -#endif + h3c = ngx_http_v3_get_session(c); + + if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); @@ -993,9 +999,11 @@ failed: static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r) { - ssize_t n; - ngx_buf_t *b; - ngx_connection_t *c; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; c = r->connection; @@ -1003,6 +1011,19 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + if (!r->http_connection->addr_conf->http3) { + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; + } + } + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { return NGX_ERROR; } diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index d0e392de4..f00caaad8 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -37,12 +37,9 @@ void ngx_http_v3_init_uni_stream(ngx_connection_t *c) { uint64_t n; -#if (NGX_HTTP_V3_HQ) ngx_http_v3_session_t *h3c; -#endif ngx_http_v3_uni_stream_t *us; -#if (NGX_HTTP_V3_HQ) h3c = ngx_http_v3_get_session(c); if (h3c->hq) { ngx_http_v3_finalize_connection(c, @@ -52,7 +49,6 @@ ngx_http_v3_init_uni_stream(ngx_connection_t *c) ngx_http_v3_close_uni_stream(c); return; } -#endif ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); -- cgit v1.2.3 From a5f9b45aee3c2bdbd3fcd4f8fc6b6903b1214705 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 26 Jan 2023 15:25:33 +0400 Subject: HTTP/3: trigger more compatibility errors for "listen quic". Now "ssl", "proxy_protocol" and "http2" are not allowed with "quic" in "listen" directive. Previously, only "ssl" was not allowed. --- src/http/ngx_http_core_module.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index f22d3bdab..3251e6da9 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4320,10 +4320,26 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } -#if (NGX_HTTP_SSL && NGX_HTTP_V3) - if (lsopt.ssl && lsopt.quic) { - return "\"ssl\" parameter is incompatible with \"quic\""; +#if (NGX_HTTP_V3) + + if (lsopt.quic) { +#if (NGX_HTTP_SSL) + if (lsopt.ssl) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_V2) + if (lsopt.http2) { + return "\"http2\" parameter is incompatible with \"quic\""; + } +#endif + + if (lsopt.proxy_protocol) { + return "\"proxy_protocol\" parameter is incompatible with \"quic\""; + } } + #endif for (n = 0; n < u.naddrs; n++) { -- cgit v1.2.3 From 4d472cd792cc9699e014995c9f41c3e3e048e975 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 24 Mar 2023 19:49:50 +0400 Subject: HTTP/3: fixed OpenSSL compatibility layer initialization. SSL context is not present if the default server has neither certificates nor ssl_reject_handshake enabled. Previously, this led to null pointer dereference before it would be caught with configuration checks. Additionally, non-default servers with distinct SSL contexts need to initialize compatibility layer in order to complete a QUIC handshake. --- src/http/modules/ngx_http_ssl_module.c | 40 ++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index 8167157e2..d92ec403e 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -56,6 +56,10 @@ static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); +#if (NGX_QUIC_OPENSSL_COMPAT) +static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, + ngx_http_conf_addr_t *addr); +#endif static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { @@ -1328,14 +1332,11 @@ ngx_http_ssl_init(ngx_conf_t *cf) continue; } - cscf = addr[a].default_server; - sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (addr[a].opt.quic) { name = "quic"; #if (NGX_QUIC_OPENSSL_COMPAT) - if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) { return NGX_ERROR; } #endif @@ -1344,6 +1345,9 @@ ngx_http_ssl_init(ngx_conf_t *cf) name = "ssl"; } + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + if (sscf->certificates) { if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { @@ -1391,3 +1395,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) return NGX_OK; } + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +static ngx_int_t +ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) +{ + ngx_uint_t s; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t **cscfp, *cscf; + + cscfp = addr->servers.elts; + for (s = 0; s < addr->servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + +#endif -- cgit v1.2.3 From 25d8ab363b7ac63c37f21b35edc92b02bd0a74cc Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 15 Mar 2023 19:57:15 +0400 Subject: QUIC: style. --- src/event/quic/ngx_event_quic_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 62566d25a..46456db1d 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -576,7 +576,7 @@ ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) { size_t len; - if ngx_quic_short_pkt(pkt->flags) { + if (ngx_quic_short_pkt(pkt->flags)) { len = 1 + pkt->dcid.len + pkt->num_len + EVP_GCM_TLS_TAG_LEN; if (len > pkt_len) { -- cgit v1.2.3 From c1363247216bc5ac2862fa501d3f741ce7696422 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 3 Apr 2023 16:17:12 +0400 Subject: QUIC: optimized sending stream response. When a stream is created by client, it's often the case that nginx will send immediate response on that stream. An example is HTTP/3 request stream, which in most cases quickly replies with at least HTTP headers. QUIC stream init handlers are called from a posted event. Output QUIC frames are also sent to client from a posted event, called the push event. If the push event is posted before the stream init event, then output produced by stream may trigger sending an extra UDP datagram. To address this, push event is now re-posted when a new stream init event is posted. An example is handling 0-RTT packets. Client typically sends an init packet coalesced with a 0-RTT packet. Previously, nginx replied with a padded CRYPTO datagram, followed by a 1-RTT stream reply datagram. Now CRYPTO and STREAM packets are coalesced in one reply datagram, which saves bandwidth. Other examples include coalescing 1-RTT first stream response, and MAX_STREAMS/STREAM sent in response to ACK/STREAM. --- src/event/quic/ngx_event_quic_streams.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 6f0a752a7..3b72f8339 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -472,6 +472,17 @@ ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) 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); + } } } -- cgit v1.2.3 From ba15b2af1bc130725a1069c5c263779db350e9b0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 6 Apr 2023 18:18:41 +0400 Subject: HTTP/3: fixed CANCEL_PUSH handling. --- src/http/v3/ngx_http_v3_uni.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index f00caaad8..22ddaf3eb 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -747,7 +747,7 @@ ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) for (q = ngx_queue_head(&h3c->pushing); q != ngx_queue_sentinel(&h3c->pushing); - q = ngx_queue_next(&h3c->pushing)) + q = ngx_queue_next(q)) { push = (ngx_http_v3_push_t *) q; -- cgit v1.2.3 From cc00acfe74237710163eec7aeb89cc7374441af4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 6 Apr 2023 15:39:48 +0400 Subject: Stream: allow waiting on a blocked QUIC stream (ticket #2479). Previously, waiting on a shared connection was not allowed, because the only type of such connection was plain UDP. However, QUIC stream connections are also shared since they share socket descriptor with the listen connection. Meanwhile, it's perfectly normal to wait on such connections. The issue manifested itself with stream write errors when the amount of data exceeded stream buffer size or flow control. Now no error is triggered and Stream write module is allowed to wait for buffer space to become available. --- src/stream/ngx_stream_write_filter_module.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c index 07dc7b52e..d8a72f966 100644 --- a/src/stream/ngx_stream_write_filter_module.c +++ b/src/stream/ngx_stream_write_filter_module.c @@ -277,7 +277,12 @@ ngx_stream_write_filter(ngx_stream_session_t *s, ngx_chain_t *in, *out = chain; if (chain) { - if (c->shared) { + if (c->shared +#if (NGX_STREAM_QUIC) + && c->quic == NULL +#endif + ) + { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "shared connection is busy"); return NGX_ERROR; -- cgit v1.2.3 From 4746ec2b626dd8dd9550809f125e5887c4c0d445 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 11 Apr 2023 18:29:20 +0400 Subject: README: revised TLSv1.3 requirement for QUIC. TLSv1.3 is enabled by default since d1cf09451ae8. --- README | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README b/README index 2e9eb9f0d..7470457c3 100644 --- a/README +++ b/README @@ -119,10 +119,6 @@ Experimental QUIC support for nginx ssl_early_data on; - Make sure that TLS 1.3 is configured which is required for QUIC: - - ssl_protocols TLSv1.3; - To enable GSO (Generic Segmentation Offloading): quic_gso on; @@ -135,6 +131,8 @@ Experimental QUIC support for nginx quic_host_key ; + QUIC requires TLSv1.3 protocol, which is enabled by the default + by "ssl_protocols" directive. By default, GSO Linux-specific optimization [10] is disabled. Enable it in case a corresponding network interface is configured to @@ -175,7 +173,6 @@ Example configuration: ssl_certificate certs/example.com.crt; ssl_certificate_key certs/example.com.key; - ssl_protocols TLSv1.3; location / { # required for browsers to direct them into quic port -- cgit v1.2.3 From ea51d2fce8798c4bfc0d56a566ea73a024b3b125 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 4 May 2023 15:52:22 +0400 Subject: HTTP/3: fixed ngx_http_v3_init_session() error handling. A QUIC connection is not usable yet at this early stage of spin up. --- src/http/v3/ngx_http_v3.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index 6d4bddb38..db0be75da 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -59,9 +59,6 @@ ngx_http_v3_init_session(ngx_connection_t *c) failed: ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "failed to create http3 session"); return NGX_ERROR; } -- cgit v1.2.3 From af18ce35060288a393c3b3c0e30474353779bd77 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 4 May 2023 15:52:23 +0400 Subject: QUIC: fixed split frames error handling. Do not corrupt frame data chain pointer on ngx_quic_read_buffer() error. The error leads to closing a QUIC connection where the frame may be used as part of the QUIC connection tear down, which envolves writing pending frames, including this one. --- src/event/quic/ngx_event_quic_frames.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c index 040b6182c..7bcfb3211 100644 --- a/src/event/quic/ngx_event_quic_frames.c +++ b/src/event/quic/ngx_event_quic_frames.c @@ -319,6 +319,7 @@ ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) { size_t shrink; + ngx_chain_t *out; ngx_quic_frame_t *nf; ngx_quic_buffer_t qb; ngx_quic_ordered_frame_t *of, *onf; @@ -359,11 +360,13 @@ ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); qb.chain = f->data; - f->data = ngx_quic_read_buffer(c, &qb, of->length); - if (f->data == NGX_CHAIN_ERROR) { + out = ngx_quic_read_buffer(c, &qb, of->length); + if (out == NGX_CHAIN_ERROR) { return NGX_ERROR; } + f->data = out; + nf = ngx_quic_alloc_frame(c); if (nf == NULL) { return NGX_ERROR; -- cgit v1.2.3 From 2187e5e1d9667aa5d0c137186a3e91c3c88dfa23 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 2 May 2023 17:54:53 +0400 Subject: QUIC: optimized immediate close. Previously, before sending CONNECTION_CLOSE to client, all pending frames were sent. This is redundant and could prevent CONNECTION_CLOSE from being sent due to congestion control. Now pending frames are freed and CONNECTION_CLOSE is sent without congestion control, as advised by RFC 9002: Packets containing frames besides ACK or CONNECTION_CLOSE frames count toward congestion control limits and are considered to be in flight. --- src/event/quic/ngx_event_quic.c | 1 + src/event/quic/ngx_event_quic_output.c | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 9b342d7de..b4033bc3f 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -482,6 +482,7 @@ ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) /* drop packets from retransmit queues, no ack is expected */ for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); ngx_quic_free_frames(c, &qc->send_ctx[i].sent); } diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index d2ced99a1..8cf844460 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -882,7 +882,7 @@ ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_int_t ngx_quic_send_cc(ngx_connection_t *c) { - ngx_quic_frame_t *frame; + ngx_quic_frame_t frame; ngx_quic_connection_t *qc; qc = ngx_quic_get_connection(c); @@ -898,27 +898,22 @@ ngx_quic_send_cc(ngx_connection_t *c) return NGX_OK; } - frame = ngx_quic_alloc_frame(c); - if (frame == NULL) { - return NGX_ERROR; - } + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); - frame->level = qc->error_level; - frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP - : NGX_QUIC_FT_CONNECTION_CLOSE; - frame->u.close.error_code = qc->error; - frame->u.close.frame_type = qc->error_ftype; + frame.level = qc->error_level; + frame.type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = qc->error; + frame.u.close.frame_type = qc->error_ftype; if (qc->error_reason) { - frame->u.close.reason.len = ngx_strlen(qc->error_reason); - frame->u.close.reason.data = (u_char *) qc->error_reason; + frame.u.close.reason.len = ngx_strlen(qc->error_reason); + frame.u.close.reason.data = (u_char *) qc->error_reason; } - ngx_quic_queue_frame(qc, frame); - qc->last_cc = ngx_current_msec; - return ngx_quic_output(c); + return ngx_quic_frame_sendto(c, &frame, 0, qc->path); } -- cgit v1.2.3 From 13f81f9b88dd9de6d2ff10c3e4aa9a4a160ee7ed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 4 May 2023 19:29:34 +0400 Subject: QUIC: fixed encryption level in ngx_quic_frame_sendto(). Previously, ssl_encryption_application was hardcoded. Before 9553eea74f2a, ngx_quic_frame_sendto() was used only for PATH_CHALLENGE/PATH_RESPONSE sent at the application level only. Since 9553eea74f2a, ngx_quic_frame_sendto() is also used for CONNECTION_CLOSE, which can be sent at initial level after SSL handshake error or rejection. This resulted in packet encryption error. Now level is copied from frame, which fixes the error. --- src/event/quic/ngx_event_quic_output.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c index 8cf844460..38006a9a5 100644 --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -1223,7 +1223,7 @@ ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; qc = ngx_quic_get_connection(c); - ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + ctx = ngx_quic_get_send_ctx(qc, frame->level); ngx_quic_init_packet(c, ctx, &pkt, path); -- cgit v1.2.3 From 1465a34067b927963b311136bf15a79981cb9d6e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 6 May 2023 16:23:27 +0400 Subject: QUIC: disabled datagram fragmentation. As per RFC 9000, Section 14: UDP datagrams MUST NOT be fragmented at the IP layer. --- auto/unix | 48 +++++++++++++++++++++++++++++++ src/core/ngx_connection.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/auto/unix b/auto/unix index 867101982..f29e69c61 100644 --- a/auto/unix +++ b/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_IPV6, IPV6_RECVPKTINFO, NULL, 0)" . auto/feature +# IP packet fragmentation + +ngx_feature="IP_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IP_PMTUDISC_DO; + setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IPV6_PMTUDISC_DO; + setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include + #include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 57c5a8aa1..5e5683928 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -1009,6 +1009,78 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle) } } +#endif + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + #endif } -- cgit v1.2.3 From f0537cf17cec8a823c5b81c8f07a2508d7366720 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 9 May 2023 19:42:38 +0400 Subject: QUIC: removed check for in-flight packets in computing PTO. The check is needed for clients in order to unblock a server due to anti-amplification limits, and it seems to make no sense for servers. See RFC 9002, A.6 and A.8 for a further explanation. This makes max_ack_delay to now always account, notably including PATH_CHALLENGE timers as noted in the last paragraph of 9000, 9.4, unlike when it was only used when there are packets in flight. While here, fixed nearby style. --- src/event/quic/ngx_event_quic_ack.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index d236deb59..2458c9aea 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -782,15 +782,11 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) qc = ngx_quic_get_connection(c); /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ - duration = qc->avg_rtt; + duration = qc->avg_rtt; duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); duration <<= qc->pto_count; - if (qc->congestion.in_flight == 0) { /* no in-flight packets */ - return duration; - } - if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { duration += qc->ctp.max_ack_delay << qc->pto_count; } -- cgit v1.2.3 From f706744165bc5f5b597ec84dc55effcdc21312f9 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 9 May 2023 19:42:39 +0400 Subject: QUIC: separated path validation retransmit backoff. Path validation packets containing PATH_CHALLENGE frames are sent separately from regular frame queue, because of the need to use a decicated path and pad the packets. The packets are sent periodically, separately from the regular probe/lost detection mechanism. A path validation packet is resent up to 3 times, each time after PTO expiration, with increasing per-path PTO backoff. --- src/event/quic/ngx_event_quic_ack.c | 10 ++++++---- src/event/quic/ngx_event_quic_migration.c | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c index 2458c9aea..062b065f9 100644 --- a/src/event/quic/ngx_event_quic_ack.c +++ b/src/event/quic/ngx_event_quic_ack.c @@ -736,7 +736,8 @@ ngx_quic_set_lost_timer(ngx_connection_t *c) q = ngx_queue_last(&ctx->sent); f = ngx_queue_data(q, ngx_quic_frame_t, queue); - w = (ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now); + w = (ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) + - now); if (w < 0) { w = 0; @@ -785,10 +786,9 @@ ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) duration = qc->avg_rtt; duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); - duration <<= qc->pto_count; if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { - duration += qc->ctp.max_ack_delay << qc->pto_count; + duration += qc->ctp.max_ack_delay; } return duration; @@ -846,7 +846,9 @@ ngx_quic_pto_handler(ngx_event_t *ev) continue; } - if ((ngx_msec_int_t) (f->last + ngx_quic_pto(c, ctx) - now) > 0) { + if ((ngx_msec_int_t) (f->last + (ngx_quic_pto(c, ctx) << qc->pto_count) + - now) > 0) + { continue; } diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 37c7b8675..4b337ee6c 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -496,6 +496,7 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) "quic initiated validation of path seq:%uL", path->seqnum); path->validating = 1; + path->tries = 0; if (RAND_bytes(path->challenge1, 8) != 1) { return NGX_ERROR; @@ -513,7 +514,6 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) pto = ngx_quic_pto(c, ctx); path->expires = ngx_current_msec + pto; - path->tries = NGX_QUIC_PATH_RETRIES; if (!qc->path_validation.timer_set) { ngx_add_timer(&qc->path_validation, pto); @@ -578,7 +578,6 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) qc = ngx_quic_get_connection(c); ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - pto = ngx_quic_pto(c, ctx); next = -1; now = ngx_current_msec; @@ -605,7 +604,9 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) continue; } - if (--path->tries) { + if (++path->tries < NGX_QUIC_PATH_RETRIES) { + pto = ngx_quic_pto(c, ctx) << path->tries; + path->expires = ngx_current_msec + pto; if (next == -1 || pto < next) { -- cgit v1.2.3 From 894679804bc6b4d118ea8bec4ae79c919ef4b9c0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 9 May 2023 19:42:40 +0400 Subject: QUIC: lower bound path validation PTO. According to RFC 9000, 8.2.4. Failed Path Validation, the following value is recommended as a validation timeout: A value of three times the larger of the current PTO or the PTO for the new path (using kInitialRtt, as defined in [QUIC-RECOVERY]) is RECOMMENDED. The change adds PTO of the new path to the equation as the lower bound. --- src/event/quic/ngx_event_quic_migration.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 4b337ee6c..735245cbf 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -511,7 +511,7 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) } ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); - pto = ngx_quic_pto(c, ctx); + pto = ngx_max(ngx_quic_pto(c, ctx), 1000); path->expires = ngx_current_msec + pto; @@ -605,7 +605,7 @@ ngx_quic_path_validation_handler(ngx_event_t *ev) } if (++path->tries < NGX_QUIC_PATH_RETRIES) { - pto = ngx_quic_pto(c, ctx) << path->tries; + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; path->expires = ngx_current_msec + pto; -- cgit v1.2.3 From 12fa86dd928a22ab6f07a1e73f3af7f703507337 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 9 May 2023 19:42:40 +0400 Subject: QUIC: reschedule path validation on path insertion/removal. Two issues fixed: - new path validation could be scheduled late - a validated path could leave a spurious timer --- src/event/quic/ngx_event_quic_migration.c | 48 +++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 735245cbf..035318764 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -16,6 +16,7 @@ static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path); static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path); +static void ngx_quic_set_path_timer(ngx_connection_t *c); static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); @@ -169,6 +170,8 @@ valid: path->validating = 0; path->limited = 0; + ngx_quic_set_path_timer(c); + return NGX_OK; } @@ -515,9 +518,7 @@ ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) path->expires = ngx_current_msec + pto; - if (!qc->path_validation.timer_set) { - ngx_add_timer(&qc->path_validation, pto); - } + ngx_quic_set_path_timer(c); return NGX_OK; } @@ -563,6 +564,47 @@ ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) } +static void +ngx_quic_set_path_timer(ngx_connection_t *c) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + next = -1; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (!path->validating) { + continue; + } + + left = path->expires - now; + left = ngx_max(left, 1); + + if (next == -1 || left < next) { + next = left; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + + } else if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } +} + + void ngx_quic_path_validation_handler(ngx_event_t *ev) { -- cgit v1.2.3 From 906e3b5dca2352ba3799b41b181a3ca617dc3329 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 27 Apr 2023 19:52:40 +0400 Subject: QUIC: fixed addr_text after migration (ticket #2488). Previously, the post-migration value of addr_text could be truncated, if it was longer than the previous one. Also, the new value always included port, which should not be there. --- src/event/quic/ngx_event_quic_migration.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c index 035318764..cf0438fbc 100644 --- a/src/event/quic/ngx_event_quic_migration.c +++ b/src/event/quic/ngx_event_quic_migration.c @@ -387,16 +387,13 @@ ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) static void ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) { - size_t len; - ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); c->socklen = path->socklen; if (c->addr_text.data) { - len = ngx_min(c->addr_text.len, path->addr_text.len); - - ngx_memcpy(c->addr_text.data, path->addr_text.data, len); - c->addr_text.len = len; + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + c->listening->addr_text_max_len, 0); } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, -- cgit v1.2.3 From a4319bc496264f94ea8a85702d9b5b8098d9d18c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 27 Apr 2023 19:49:05 +0400 Subject: QUIC: set c->socklen for streams. Previously, the value was not set and remained zero. While in nginx code the value of c->sockaddr is accessed without taking c->socklen into account, invalid c->socklen could lead to unexpected results in third-party modules. --- src/event/quic/ngx_event_quic_streams.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index 3b72f8339..ded94af87 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -711,6 +711,7 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->pool = pool; sc->ssl = c->ssl; sc->sockaddr = c->sockaddr; + sc->socklen = c->socklen; sc->listening = c->listening; sc->addr_text = c->addr_text; sc->local_sockaddr = c->local_sockaddr; -- cgit v1.2.3 From 885c4881915e28661f341b9ac3807afb84c8b779 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 May 2023 19:40:11 +0400 Subject: QUIC: keep stream sockaddr and addr_text constant. HTTP and Stream variables $remote_addr and $binary_remote_addr rely on constant client address, particularly because they are cacheable. However, QUIC client may migrate to a new address. While there's no perfect way to handle this, the proposed solution is to copy client address to QUIC stream at stream creation. The change also fixes truncated $remote_addr if migration happened while the stream was active. The reason is addr_text string was copied to stream by value. --- src/event/quic/ngx_event_quic_streams.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c index ded94af87..6664dfd7b 100644 --- a/src/event/quic/ngx_event_quic_streams.c +++ b/src/event/quic/ngx_event_quic_streams.c @@ -637,10 +637,12 @@ 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) { + ngx_str_t addr_text; ngx_log_t *log; ngx_pool_t *pool; ngx_uint_t reusable; ngx_queue_t *q; + struct sockaddr *sockaddr; ngx_connection_t *sc; ngx_quic_stream_t *qs; ngx_pool_cleanup_t *cln; @@ -692,6 +694,31 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) *log = *c->log; pool->log = log; + sockaddr = ngx_palloc(pool, c->socklen); + if (sockaddr == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(sockaddr, c->sockaddr, c->socklen); + + if (c->addr_text.data) { + addr_text.data = ngx_pnalloc(pool, c->addr_text.len); + if (addr_text.data == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(addr_text.data, c->addr_text.data, c->addr_text.len); + addr_text.len = c->addr_text.len; + + } else { + addr_text.len = 0; + addr_text.data = NULL; + } + reusable = c->reusable; ngx_reusable_connection(c, 0); @@ -710,10 +737,10 @@ ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) sc->type = SOCK_STREAM; sc->pool = pool; sc->ssl = c->ssl; - sc->sockaddr = c->sockaddr; + sc->sockaddr = sockaddr; sc->socklen = c->socklen; sc->listening = c->listening; - sc->addr_text = c->addr_text; + sc->addr_text = addr_text; sc->local_sockaddr = c->local_sockaddr; sc->local_socklen = c->local_socklen; sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); -- cgit v1.2.3 From 9ab5d15379a26b32d93c706d63fd3f9f241459e0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 May 2023 09:49:34 +0400 Subject: QUIC: resized input datagram buffer from 65535 to 65527. The value of 65527 is the maximum permitted UDP payload size. --- src/event/quic/ngx_event_quic_udp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c index f7253a27d..0721ca8b6 100644 --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -34,7 +34,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) ngx_event_conf_t *ecf; ngx_connection_t *c, *lc; ngx_quic_socket_t *qsock; - static u_char buffer[65535]; + static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; #if (NGX_HAVE_ADDRINFO_CMSG) u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; -- cgit v1.2.3 From 6cc803e713698b4b09ab46ccd7ae986faa55c386 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 May 2023 10:37:51 +0400 Subject: QUIC: removed "quic_mtu" directive. The directive used to set the value of the "max_udp_payload_size" transport parameter. According to RFC 9000, Section 18.2, the value specifies the size of buffer for reading incoming datagrams: This limit does act as an additional constraint on datagram size in the same way as the path MTU, but it is a property of the endpoint and not the path; see Section 14. It is expected that this is the space an endpoint dedicates to holding incoming packets. Current QUIC implementation uses the maximum possible buffer size (65527) for reading datagrams. --- README | 12 ---------- src/event/quic/ngx_event_quic.h | 1 - src/event/quic/ngx_event_quic_transport.c | 2 +- src/http/v3/ngx_http_v3_module.c | 37 ------------------------------- src/stream/ngx_stream_quic_module.c | 34 ---------------------------- 5 files changed, 1 insertion(+), 85 deletions(-) diff --git a/README b/README index 7470457c3..cde316a7d 100644 --- a/README +++ b/README @@ -123,10 +123,6 @@ Experimental QUIC support for nginx quic_gso on; - To limit maximum UDP payload size on receive path: - - quic_mtu ; - To set host key for various tokens: quic_host_key ; @@ -209,14 +205,6 @@ Example configuration: Optimized sending is only supported on Linux featuring UDP_SEGMENT. - Syntax: quic_mtu size; - Default: quic_mtu 65527; - Context: http | stream, server - - Sets the QUIC max_udp_payload_size transport parameter value. - This is the maximum UDP payload that we are willing to receive. - - Syntax: quic_host_key file; Default: - Context: http | stream, server diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 56713ec4d..0c68d68f0 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -69,7 +69,6 @@ typedef struct { ngx_flag_t disable_active_migration; ngx_msec_t timeout; ngx_str_t host_key; - size_t mtu; size_t stream_buffer_size; ngx_uint_t max_concurrent_streams_bidi; ngx_uint_t max_concurrent_streams_uni; diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c index 46456db1d..b663efbe1 100644 --- a/src/event/quic/ngx_event_quic_transport.c +++ b/src/event/quic/ngx_event_quic_transport.c @@ -1987,7 +1987,7 @@ ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) tp->max_idle_timeout = qcf->timeout; - tp->max_udp_payload_size = qcf->mtu; + tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; nstreams = qcf->max_concurrent_streams_bidi + qcf->max_concurrent_streams_uni; diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 02b88b479..8569f07a0 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -16,8 +16,6 @@ static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_http_quic_mtu(ngx_conf_t *cf, void *post, - void *data); static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); @@ -26,10 +24,6 @@ static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_conf_post_t ngx_http_quic_mtu_post = - { ngx_http_quic_mtu }; - - static ngx_command_t ngx_http_v3_commands[] = { { ngx_string("http3"), @@ -95,13 +89,6 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), NULL }, - { ngx_string("quic_mtu"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, quic.mtu), - &ngx_http_quic_mtu_post }, - { ngx_string("quic_host_key"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_quic_host_key, @@ -240,7 +227,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; - h3scf->quic.mtu = NGX_CONF_UNSET_SIZE; h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; @@ -277,9 +263,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) conf->max_blocked_streams = conf->max_concurrent_streams; - ngx_conf_merge_size_value(conf->quic.mtu, prev->quic.mtu, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->quic.stream_buffer_size, prev->quic.stream_buffer_size, 65536); @@ -334,26 +317,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } -static char * -ngx_http_quic_mtu(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_mtu\" must be between %d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - static char * ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c index 644fa0380..42f5fe060 100644 --- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -16,12 +16,9 @@ static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data); static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_conf_post_t ngx_stream_quic_mtu_post = - { ngx_stream_quic_mtu }; static ngx_command_t ngx_stream_quic_commands[] = { @@ -32,13 +29,6 @@ static ngx_command_t ngx_stream_quic_commands[] = { offsetof(ngx_quic_conf_t, timeout), NULL }, - { ngx_string("quic_mtu"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, mtu), - &ngx_stream_quic_mtu_post }, - { ngx_string("quic_stream_buffer_size"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -175,7 +165,6 @@ ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) */ conf->timeout = NGX_CONF_UNSET_MSEC; - conf->mtu = NGX_CONF_UNSET_SIZE; conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; @@ -199,9 +188,6 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); - ngx_conf_merge_size_value(conf->mtu, prev->mtu, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - ngx_conf_merge_size_value(conf->stream_buffer_size, prev->stream_buffer_size, 65536); @@ -259,26 +245,6 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } -static char * -ngx_stream_quic_mtu(ngx_conf_t *cf, void *post, void *data) -{ - size_t *sp = data; - - if (*sp < NGX_QUIC_MIN_INITIAL_SIZE - || *sp > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) - { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"quic_mtu\" must be between %d and %d", - NGX_QUIC_MIN_INITIAL_SIZE, - NGX_QUIC_MAX_UDP_PAYLOAD_SIZE); - - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} - - static char * ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { -- cgit v1.2.3 From 2ce3eeeeb76318e414b62d399da70872d2de23d8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 May 2023 13:22:10 +0400 Subject: HTTP/3: removed "http3" parameter of "listen" directive. The parameter has been deprecated since c851a2ed5ce8. --- src/http/modules/ngx_http_ssl_module.c | 2 +- src/http/ngx_http.c | 5 ----- src/http/ngx_http_core_module.c | 17 ----------------- src/http/ngx_http_core_module.h | 2 -- src/http/v3/ngx_http_v3_request.c | 14 ++++++-------- 5 files changed, 7 insertions(+), 33 deletions(-) diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index c0d6bae06..d2ca475d3 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -477,7 +477,7 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; - } else if (h3scf->enable || hc->addr_conf->http3) { + } else if (h3scf->enable) { srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index d08002b81..34bfc539e 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1242,7 +1242,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_uint_t http2; #endif #if (NGX_HTTP_V3) - ngx_uint_t http3; ngx_uint_t quic; #endif @@ -1287,7 +1286,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, protocols_prev |= addr[i].opt.http2 << 2; #endif #if (NGX_HTTP_V3) - http3 = lsopt->http3 || addr[i].opt.http3; quic = lsopt->quic || addr[i].opt.quic; #endif @@ -1378,7 +1376,6 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, addr[i].opt.http2 = http2; #endif #if (NGX_HTTP_V3) - addr[i].opt.http3 = http3; addr[i].opt.quic = quic; #endif @@ -1929,7 +1926,6 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, addrs[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs[i].conf.http3 = addr[i].opt.http3; addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1998,7 +1994,6 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, addrs6[i].conf.http2 = addr[i].opt.http2; #endif #if (NGX_HTTP_V3) - addrs6[i].conf.http3 = addr[i].opt.http3; addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 1f9fec4fe..bd8f7666a 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4186,23 +4186,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - if (ngx_strcmp(value[n].data, "http3") == 0) { -#if (NGX_HTTP_V3) - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"http3\" parameter is deprecated, " - "use \"quic\" parameter instead"); - lsopt.quic = 1; - lsopt.http3 = 1; - lsopt.type = SOCK_DGRAM; - continue; -#else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the \"http3\" parameter requires " - "ngx_http_v3_module"); - return NGX_CONF_ERROR; -#endif - } - if (ngx_strcmp(value[n].data, "quic") == 0) { #if (NGX_HTTP_V3) lsopt.quic = 1; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index b612a8d4d..765e7ff60 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -75,7 +75,6 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; - unsigned http3:1; unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; @@ -240,7 +239,6 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; - unsigned http3:1; unsigned quic:1; unsigned proxy_protocol:1; }; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ff6b40734..6f72dc402 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1014,14 +1014,12 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) h3c = ngx_http_v3_get_session(c); h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - if (!r->http_connection->addr_conf->http3) { - if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client attempted to request the server name " - "for which the negotiated protocol is disabled"); - ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); - return NGX_ERROR; - } + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; } if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { -- cgit v1.2.3 From 089d1f653001419ea9d0b463434a89007ec805bd Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Thu, 11 May 2023 18:48:01 +0300 Subject: QUIC: style. --- src/http/v3/ngx_http_v3.h | 4 ++-- src/http/v3/ngx_http_v3_parse.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 2bb717cc8..2c05f089f 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -85,11 +85,11 @@ #define ngx_http_v3_get_session(c) ngx_http_quic_get_connection(c)->v3_session #define ngx_http_v3_get_module_loc_conf(c, module) \ - ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ module) #define ngx_http_v3_get_module_srv_conf(c, module) \ - ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ module) #define ngx_http_v3_finalize_connection(c, code, reason) \ diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 7dc53493c..7ea284f8a 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -868,7 +868,8 @@ ngx_http_v3_parse_field_l(ngx_connection_t *c, case sw_start: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse field l"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l"); if (b->pos == b->last) { return NGX_AGAIN; -- cgit v1.2.3 From 779bfcff5f7544494c7c85ac73f41a033e749528 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 14 May 2023 12:05:35 +0400 Subject: Stream: removed QUIC support. --- README | 30 +-- auto/modules | 14 -- auto/options | 3 - src/stream/ngx_stream.c | 23 +- src/stream/ngx_stream.h | 6 - src/stream/ngx_stream_core_module.c | 29 --- src/stream/ngx_stream_handler.c | 19 -- src/stream/ngx_stream_proxy_module.c | 15 -- src/stream/ngx_stream_quic_module.c | 343 ---------------------------- src/stream/ngx_stream_quic_module.h | 20 -- src/stream/ngx_stream_ssl_module.c | 31 --- src/stream/ngx_stream_write_filter_module.c | 7 +- 12 files changed, 8 insertions(+), 532 deletions(-) delete mode 100644 src/stream/ngx_stream_quic_module.c delete mode 100644 src/stream/ngx_stream_quic_module.h diff --git a/README b/README index cde316a7d..ec5efa7ac 100644 --- a/README +++ b/README @@ -58,10 +58,9 @@ Experimental QUIC support for nginx Refer to http://nginx.org/en/docs/configure.html for details. When configuring nginx, it's possible to enable QUIC and HTTP/3 - using the following new configuration options: + using the following new configuration option: --with-http_v3_module - enable QUIC and HTTP/3 - --with-stream_quic_module - enable QUIC in Stream A library that provides QUIC support is recommended to build nginx, there are several of those available on the market: @@ -105,9 +104,6 @@ Experimental QUIC support for nginx The HTTP "listen" directive got a new option "quic" which enables QUIC as client transport protocol instead of TCP. - The Stream "listen" directive got a new option "quic" which enables - QUIC as client transport protocol instead of TCP or plain UDP. - Along with "quic", it's also possible to specify "reuseport" option [8] to make it work properly with multiple workers. @@ -148,10 +144,6 @@ Experimental QUIC support for nginx The value of $http3 is "h3" for HTTP/3 connections, "hq" for hq connections, or an empty string otherwise. - In stream, an additional variable is available: $quic. - The value of $quic is "quic" if QUIC connection is used, - or an empty string otherwise. - Example configuration: http { @@ -190,7 +182,7 @@ Example configuration: Syntax: quic_retry on | off; Default: quic_retry off; - Context: http | stream, server + Context: http, server Enables the QUIC Address Validation feature. This includes: - sending a new token in a Retry packet or a NEW_TOKEN frame @@ -199,7 +191,7 @@ Example configuration: Syntax: quic_gso on | off; Default: quic_gso off; - Context: http | stream, server + Context: http, server Enables sending in optimized batch mode using segmentation offloading. Optimized sending is only supported on Linux featuring UDP_SEGMENT. @@ -207,7 +199,7 @@ Example configuration: Syntax: quic_host_key file; Default: - - Context: http | stream, server + Context: http, server Specifies a file with the secret key used to encrypt stateless reset and address validation tokens. By default, a randomly generated key is used. @@ -215,24 +207,12 @@ Example configuration: Syntax: quic_active_connection_id_limit number; Default: quic_active_connection_id_limit 2; - Context: http | stream, server + Context: http, server Sets the QUIC active_connection_id_limit transport parameter value. This is the maximum number of connection IDs we are willing to store. - Syntax: quic_timeout time; - Default: quic_timeout 60s; - Context: stream, server - - Defines a timeout used to negotiate the QUIC idle timeout. - In the http module, it is taken from the keepalive_timeout directive. - - - Syntax: quic_stream_buffer_size size; - Default: quic_stream_buffer_size 64k; - Context: stream, server - Syntax: http3_stream_buffer_size size; Default: http3_stream_buffer_size 64k; Context: http, server diff --git a/auto/modules b/auto/modules index 08a33cacc..76e6531c5 100644 --- a/auto/modules +++ b/auto/modules @@ -1075,20 +1075,6 @@ if [ $STREAM != NO ]; then ngx_module_incs= - if [ $STREAM_QUIC = YES ]; then - USE_OPENSSL_QUIC=YES - have=NGX_STREAM_QUIC . auto/have - STREAM_SSL=YES - - ngx_module_name=ngx_stream_quic_module - ngx_module_deps=src/stream/ngx_stream_quic_module.h - ngx_module_srcs=src/stream/ngx_stream_quic_module.c - ngx_module_libs= - ngx_module_link=$STREAM_QUIC - - . auto/module - fi - if [ $STREAM_SSL = YES ]; then USE_OPENSSL=YES have=NGX_STREAM_SSL . auto/have diff --git a/auto/options b/auto/options index ced563806..552ef837e 100644 --- a/auto/options +++ b/auto/options @@ -119,7 +119,6 @@ MAIL_SMTP=YES STREAM=NO STREAM_SSL=NO -STREAM_QUIC=NO STREAM_REALIP=NO STREAM_LIMIT_CONN=YES STREAM_ACCESS=YES @@ -324,7 +323,6 @@ use the \"--with-mail_ssl_module\" option instead" --with-stream) STREAM=YES ;; --with-stream=dynamic) STREAM=DYNAMIC ;; --with-stream_ssl_module) STREAM_SSL=YES ;; - --with-stream_quic_module) STREAM_QUIC=YES ;; --with-stream_realip_module) STREAM_REALIP=YES ;; --with-stream_geoip_module) STREAM_GEOIP=YES ;; --with-stream_geoip_module=dynamic) @@ -547,7 +545,6 @@ cat << END --with-stream enable TCP/UDP proxy module --with-stream=dynamic enable dynamic TCP/UDP proxy module --with-stream_ssl_module enable ngx_stream_ssl_module - --with-stream_quic_module enable ngx_stream_quic_module --with-stream_realip_module enable ngx_stream_realip_module --with-stream_geoip_module enable ngx_stream_geoip_module --with-stream_geoip_module=dynamic enable dynamic ngx_stream_geoip_module diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index 4c41af173..2762786e2 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -518,22 +518,9 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ls->reuseport = addr[i].opt.reuseport; #endif -#if (NGX_STREAM_QUIC) - - ls->quic = addr[i].opt.quic; - - if (ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_quic_rbtree_insert_value); - } - -#endif - #if !(NGX_WIN32) - if (!ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_udp_rbtree_insert_value); - } + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, + ngx_udp_rbtree_insert_value); #endif stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); @@ -593,9 +580,6 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, addrs[i].conf.ctx = addr[i].opt.ctx; #if (NGX_STREAM_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; -#endif -#if (NGX_STREAM_QUIC) - addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs[i].conf.addr_text = addr[i].opt.addr_text; @@ -631,9 +615,6 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, addrs6[i].conf.ctx = addr[i].opt.ctx; #if (NGX_STREAM_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; -#endif -#if (NGX_STREAM_QUIC) - addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; addrs6[i].conf.addr_text = addr[i].opt.addr_text; diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h index 8cc95a3ab..46c362296 100644 --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -16,10 +16,6 @@ #include #endif -#if (NGX_STREAM_QUIC) -#include -#endif - typedef struct ngx_stream_session_s ngx_stream_session_t; @@ -55,7 +51,6 @@ typedef struct { unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; - unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -81,7 +76,6 @@ typedef struct { ngx_stream_conf_ctx_t *ctx; ngx_str_t addr_text; unsigned ssl:1; - unsigned quic:1; unsigned proxy_protocol:1; } ngx_stream_addr_conf_t; diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 9be1a71cd..f0b79341d 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -760,29 +760,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - if (ngx_strcmp(value[i].data, "quic") == 0) { -#if (NGX_STREAM_QUIC) - ngx_stream_ssl_conf_t *sslcf; - - sslcf = ngx_stream_conf_get_module_srv_conf(cf, - ngx_stream_ssl_module); - - sslcf->listen = 1; - sslcf->file = cf->conf_file->file.name.data; - sslcf->line = cf->conf_file->line; - - ls->quic = 1; - ls->type = SOCK_DGRAM; - - continue; -#else - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the \"quic\" parameter requires " - "ngx_stream_quic_module"); - return NGX_CONF_ERROR; -#endif - } - if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { @@ -894,12 +871,6 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } #endif -#if (NGX_STREAM_SSL && NGX_STREAM_QUIC) - if (ls->ssl && ls->quic) { - return "\"ssl\" parameter is incompatible with \"quic\""; - } -#endif - if (ls->so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 3b95bf812..669b6a18d 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -129,10 +129,6 @@ ngx_stream_init_connection(ngx_connection_t *c) s->ssl = addr_conf->ssl; #endif -#if (NGX_STREAM_QUIC) - s->ssl |= addr_conf->quic; -#endif - if (c->buffer) { s->received += c->buffer->last - c->buffer->pos; } @@ -177,21 +173,6 @@ ngx_stream_init_connection(ngx_connection_t *c) s->start_sec = tp->sec; s->start_msec = tp->msec; -#if (NGX_STREAM_QUIC) - - if (addr_conf->quic) { - ngx_quic_conf_t *qcf; - - if (c->quic == NULL) { - qcf = ngx_stream_get_module_srv_conf(addr_conf->ctx, - ngx_stream_quic_module); - ngx_quic_run(c, qcf); - return; - } - } - -#endif - rev = c->read; rev->handler = ngx_stream_session_handler; diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 20b1adce3..ed275c009 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -1772,21 +1772,6 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, if (dst->type == SOCK_STREAM && pscf->half_close && src->read->eof && !u->half_closed && !dst->buffered) { - -#if (NGX_STREAM_QUIC) - if (dst->quic) { - - if (ngx_quic_shutdown_stream(dst, NGX_WRITE_SHUTDOWN) - != NGX_OK) - { - ngx_stream_proxy_finalize(s, - NGX_STREAM_INTERNAL_SERVER_ERROR); - return; - } - - } else -#endif - if (ngx_shutdown_socket(dst->fd, NGX_WRITE_SHUTDOWN) == -1) { ngx_connection_error(c, ngx_socket_errno, ngx_shutdown_socket_n " failed"); diff --git a/src/stream/ngx_stream_quic_module.c b/src/stream/ngx_stream_quic_module.c deleted file mode 100644 index 42f5fe060..000000000 --- a/src/stream/ngx_stream_quic_module.c +++ /dev/null @@ -1,343 +0,0 @@ - -/* - * Copyright (C) Nginx, Inc. - * Copyright (C) Roman Arutyunyan - */ - - -#include -#include -#include - - -static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, - ngx_stream_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_stream_quic_add_variables(ngx_conf_t *cf); -static void *ngx_stream_quic_create_srv_conf(ngx_conf_t *cf); -static char *ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, - void *child); -static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); - - -static ngx_command_t ngx_stream_quic_commands[] = { - - { ngx_string("quic_timeout"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_msec_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, timeout), - NULL }, - - { ngx_string("quic_stream_buffer_size"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_size_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, stream_buffer_size), - NULL }, - - { ngx_string("quic_retry"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, retry), - NULL }, - - { ngx_string("quic_gso"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, gso_enabled), - NULL }, - - { ngx_string("quic_host_key"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_stream_quic_host_key, - NGX_STREAM_SRV_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("quic_active_connection_id_limit"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, active_connection_id_limit), - NULL }, - - ngx_null_command -}; - - -static ngx_stream_module_t ngx_stream_quic_module_ctx = { - ngx_stream_quic_add_variables, /* preconfiguration */ - NULL, /* postconfiguration */ - - NULL, /* create main configuration */ - NULL, /* init main configuration */ - - ngx_stream_quic_create_srv_conf, /* create server configuration */ - ngx_stream_quic_merge_srv_conf, /* merge server configuration */ -}; - - -ngx_module_t ngx_stream_quic_module = { - NGX_MODULE_V1, - &ngx_stream_quic_module_ctx, /* module context */ - ngx_stream_quic_commands, /* module directives */ - NGX_STREAM_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 -}; - - -static ngx_stream_variable_t ngx_stream_quic_vars[] = { - - { ngx_string("quic"), NULL, ngx_stream_variable_quic, 0, 0, 0 }, - - ngx_stream_null_variable -}; - -static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); - - -static ngx_int_t -ngx_stream_variable_quic(ngx_stream_session_t *s, - ngx_stream_variable_value_t *v, uintptr_t data) -{ - if (s->connection->quic) { - - v->len = 4; - v->valid = 1; - v->no_cacheable = 1; - v->not_found = 0; - v->data = (u_char *) "quic"; - return NGX_OK; - } - - v->not_found = 1; - - return NGX_OK; -} - - -static ngx_int_t -ngx_stream_quic_add_variables(ngx_conf_t *cf) -{ - ngx_stream_variable_t *var, *v; - - for (v = ngx_stream_quic_vars; v->name.len; v++) { - var = ngx_stream_add_variable(cf, &v->name, v->flags); - if (var == NULL) { - return NGX_ERROR; - } - - var->get_handler = v->get_handler; - var->data = v->data; - } - - return NGX_OK; -} - - -static void * -ngx_stream_quic_create_srv_conf(ngx_conf_t *cf) -{ - ngx_quic_conf_t *conf; - - conf = ngx_pcalloc(cf->pool, sizeof(ngx_quic_conf_t)); - if (conf == NULL) { - return NULL; - } - - /* - * set by ngx_pcalloc(): - * - * conf->host_key = { 0, NULL } - * conf->stream_close_code = 0; - * conf->stream_reject_code_uni = 0; - * conf->stream_reject_code_bidi= 0; - */ - - conf->timeout = NGX_CONF_UNSET_MSEC; - conf->stream_buffer_size = NGX_CONF_UNSET_SIZE; - conf->max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; - conf->max_concurrent_streams_uni = NGX_CONF_UNSET_UINT; - - conf->retry = NGX_CONF_UNSET; - conf->gso_enabled = NGX_CONF_UNSET; - - conf->active_connection_id_limit = NGX_CONF_UNSET_UINT; - - return conf; -} - - -static char * -ngx_stream_quic_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_quic_conf_t *prev = parent; - ngx_quic_conf_t *conf = child; - - ngx_stream_ssl_conf_t *scf; - - ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); - - ngx_conf_merge_size_value(conf->stream_buffer_size, - prev->stream_buffer_size, - 65536); - - ngx_conf_merge_uint_value(conf->max_concurrent_streams_bidi, - prev->max_concurrent_streams_bidi, 16); - - ngx_conf_merge_uint_value(conf->max_concurrent_streams_uni, - prev->max_concurrent_streams_uni, 3); - - ngx_conf_merge_value(conf->retry, prev->retry, 0); - ngx_conf_merge_value(conf->gso_enabled, prev->gso_enabled, 0); - - ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); - - ngx_conf_merge_uint_value(conf->active_connection_id_limit, - conf->active_connection_id_limit, - 2); - - if (conf->host_key.len == 0) { - - conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; - conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); - if (conf->host_key.data == NULL) { - return NGX_CONF_ERROR; - } - - if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) - <= 0) - { - return NGX_CONF_ERROR; - } - } - - if (ngx_quic_derive_key(cf->log, "av_token_key", - &conf->host_key, &ngx_stream_quic_salt, - conf->av_token_key, NGX_QUIC_AV_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - if (ngx_quic_derive_key(cf->log, "sr_token_key", - &conf->host_key, &ngx_stream_quic_salt, - conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); - conf->ssl = &scf->ssl; - - return NGX_CONF_OK; -} - - -static char * -ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_quic_conf_t *qcf = conf; - - u_char *buf; - size_t size; - ssize_t n; - ngx_str_t *value; - ngx_file_t file; - ngx_file_info_t fi; - - if (qcf->host_key.len) { - return "is duplicate"; - } - - buf = NULL; -#if (NGX_SUPPRESS_WARN) - size = 0; -#endif - - value = cf->args->elts; - - if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&file, sizeof(ngx_file_t)); - file.name = value[1]; - file.log = cf->log; - - file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); - - if (file.fd == NGX_INVALID_FILE) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, - ngx_open_file_n " \"%V\" failed", &file.name); - return NGX_CONF_ERROR; - } - - if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_fd_info_n " \"%V\" failed", &file.name); - goto failed; - } - - size = ngx_file_size(&fi); - - if (size == 0) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"%V\" zero key size", &file.name); - goto failed; - } - - buf = ngx_pnalloc(cf->pool, size); - if (buf == NULL) { - goto failed; - } - - n = ngx_read_file(&file, buf, size, 0); - - if (n == NGX_ERROR) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, - ngx_read_file_n " \"%V\" failed", &file.name); - goto failed; - } - - if ((size_t) n != size) { - ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, - ngx_read_file_n " \"%V\" returned only " - "%z bytes instead of %uz", &file.name, n, size); - goto failed; - } - - qcf->host_key.data = buf; - qcf->host_key.len = n; - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - return NGX_CONF_OK; - -failed: - - if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { - ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, - ngx_close_file_n " \"%V\" failed", &file.name); - } - - if (buf) { - ngx_explicit_memzero(buf, size); - } - - return NGX_CONF_ERROR; -} diff --git a/src/stream/ngx_stream_quic_module.h b/src/stream/ngx_stream_quic_module.h deleted file mode 100644 index 6ac4d96f0..000000000 --- a/src/stream/ngx_stream_quic_module.h +++ /dev/null @@ -1,20 +0,0 @@ - -/* - * Copyright (C) Roman Arutyunyan - * Copyright (C) Nginx, Inc. - */ - - -#ifndef _NGX_STREAM_QUIC_H_INCLUDED_ -#define _NGX_STREAM_QUIC_H_INCLUDED_ - - -#include -#include -#include - - -extern ngx_module_t ngx_stream_quic_module; - - -#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */ diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 288d9e9d2..1ba1825ce 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -9,10 +9,6 @@ #include #include -#if (NGX_QUIC_OPENSSL_COMPAT) -#include -#endif - typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -1199,10 +1195,7 @@ ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { - ngx_uint_t i; - ngx_stream_listen_t *listen; ngx_stream_handler_pt *h; - ngx_stream_ssl_conf_t *scf; ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1214,29 +1207,5 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; - listen = cmcf->listen.elts; - - for (i = 0; i < cmcf->listen.nelts; i++) { - if (!listen[i].quic) { - continue; - } - - scf = listen[i].ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; - -#if (NGX_QUIC_OPENSSL_COMPAT) - if (ngx_quic_compat_init(cf, scf->ssl.ctx) != NGX_OK) { - return NGX_ERROR; - } -#endif - - if (scf->certificates && !(scf->protocols & NGX_SSL_TLSv1_3)) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "\"ssl_protocols\" must enable TLSv1.3 for " - "the \"listen ... quic\" directive in %s:%ui", - scf->file, scf->line); - return NGX_ERROR; - } - } - return NGX_OK; } diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c index d8a72f966..07dc7b52e 100644 --- a/src/stream/ngx_stream_write_filter_module.c +++ b/src/stream/ngx_stream_write_filter_module.c @@ -277,12 +277,7 @@ ngx_stream_write_filter(ngx_stream_session_t *s, ngx_chain_t *in, *out = chain; if (chain) { - if (c->shared -#if (NGX_STREAM_QUIC) - && c->quic == NULL -#endif - ) - { + if (c->shared) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "shared connection is busy"); return NGX_ERROR; -- cgit v1.2.3 From 0a3c79614521d7612b63eff4e09c25ed219fb65b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 14 May 2023 12:30:11 +0400 Subject: Common tree insert function for QUIC and UDP connections. Previously, ngx_udp_rbtree_insert_value() was used for plain UDP and ngx_quic_rbtree_insert_value() was used for QUIC. Because of this it was impossible to initialize connection tree in ngx_create_listening() since this function is not aware what kind of listening it creates. Now ngx_udp_rbtree_insert_value() is used for both QUIC and UDP. To make is possible, a generic key field is added to ngx_udp_connection_t. It keeps client address for UDP and connection ID for QUIC. --- src/core/ngx_connection.c | 4 +++ src/event/ngx_event_udp.c | 6 ++-- src/event/ngx_event_udp.h | 1 + src/event/quic/ngx_event_quic.h | 2 -- src/event/quic/ngx_event_quic_socket.c | 1 + src/event/quic/ngx_event_quic_udp.c | 53 ---------------------------------- src/http/ngx_http.c | 7 ----- src/stream/ngx_stream.c | 5 ---- 8 files changed, 10 insertions(+), 69 deletions(-) diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index 5e5683928..10f4d9b91 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -72,6 +72,10 @@ ngx_create_listening(ngx_conf_t *cf, struct sockaddr *sockaddr, ngx_memcpy(ls->addr_text.data, text, len); +#if !(NGX_WIN32) + ngx_rbtree_init(&ls->rbtree, &ls->sentinel, ngx_udp_rbtree_insert_value); +#endif + ls->fd = (ngx_socket_t) -1; ls->type = SOCK_STREAM; diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index ec86fdfbc..43fa621b0 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -417,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -471,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection_t *c) ngx_crc32_final(hash); udp->node.key = hash; + udp->key.data = (u_char *) c->sockaddr; + udp->key.len = c->socklen; cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h index d7a64ee03..e5ddf1b31 100644 --- a/src/event/ngx_event_udp.h +++ b/src/event/ngx_event_udp.h @@ -27,6 +27,7 @@ struct ngx_udp_connection_s { ngx_rbtree_node_t node; ngx_connection_t *connection; ngx_buf_t *buffer; + ngx_str_t key; }; diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index 0c68d68f0..ca15200b4 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -111,8 +111,6 @@ struct ngx_quic_stream_s { void ngx_quic_recvmsg(ngx_event_t *ev); -void ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); 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, diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c index 6813fcd0a..6652523b7 100644 --- a/src/event/quic/ngx_event_quic_socket.c +++ b/src/event/quic/ngx_event_quic_socket.c @@ -179,6 +179,7 @@ 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; ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c index 0721ca8b6..71754a48b 100644 --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -365,59 +365,6 @@ ngx_quic_close_accepted_connection(ngx_connection_t *c) } -void -ngx_quic_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) -{ - ngx_int_t rc; - ngx_connection_t *c, *ct; - ngx_rbtree_node_t **p; - ngx_quic_socket_t *qsock, *qsockt; - - for ( ;; ) { - - if (node->key < temp->key) { - - p = &temp->left; - - } else if (node->key > temp->key) { - - p = &temp->right; - - } else { /* node->key == temp->key */ - - qsock = (ngx_quic_socket_t *) node; - c = qsock->udp.connection; - - qsockt = (ngx_quic_socket_t *) temp; - ct = qsockt->udp.connection; - - rc = ngx_memn2cmp(qsock->sid.id, qsockt->sid.id, - qsock->sid.len, qsockt->sid.len); - - if (rc == 0 && c->listening->wildcard) { - rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, - ct->local_sockaddr, ct->local_socklen, 1); - } - - p = (rc < 0) ? &temp->left : &temp->right; - } - - if (*p == sentinel) { - break; - } - - temp = *p; - } - - *p = node; - node->parent = temp; - node->left = sentinel; - node->right = sentinel; - ngx_rbt_red(node); -} - - static ngx_connection_t * ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen) diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 34bfc539e..d835f896e 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1883,14 +1883,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->wildcard = addr->opt.wildcard; #if (NGX_HTTP_V3) - ls->quic = addr->opt.quic; - - if (ls->quic) { - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_quic_rbtree_insert_value); - } - #endif return ls; diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index 2762786e2..3304c843c 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -518,11 +518,6 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ls->reuseport = addr[i].opt.reuseport; #endif -#if !(NGX_WIN32) - ngx_rbtree_init(&ls->rbtree, &ls->sentinel, - ngx_udp_rbtree_insert_value); -#endif - stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); if (stport == NULL) { return NGX_CONF_ERROR; -- cgit v1.2.3 From e4edf78bac950213e00bb97ac46ece807f3cc073 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 12 May 2023 10:02:10 +0400 Subject: HTTP/3: removed server push support. --- README | 35 -- src/http/v3/ngx_http_v3.c | 4 - src/http/v3/ngx_http_v3.h | 13 - src/http/v3/ngx_http_v3_filter_module.c | 685 -------------------------------- src/http/v3/ngx_http_v3_module.c | 132 +----- src/http/v3/ngx_http_v3_parse.c | 91 +---- src/http/v3/ngx_http_v3_uni.c | 157 -------- src/http/v3/ngx_http_v3_uni.h | 6 - 8 files changed, 6 insertions(+), 1117 deletions(-) diff --git a/README b/README index ec5efa7ac..7bb7f14fb 100644 --- a/README +++ b/README @@ -135,10 +135,7 @@ Experimental QUIC support for nginx http3 http3_hq http3_stream_buffer_size - http3_max_concurrent_pushes http3_max_concurrent_streams - http3_push - http3_push_preload In http, an additional variable is available: $http3. The value of $http3 is "h3" for HTTP/3 connections, @@ -226,13 +223,6 @@ Example configuration: - initial_max_stream_data_uni - Syntax: http3_max_concurrent_pushes number; - Default: http3_max_concurrent_pushes 10; - Context: http, server - - Limits the maximum number of concurrent push requests in a connection. - - Syntax: http3_max_concurrent_streams number; Default: http3_max_concurrent_streams 128; Context: http, server @@ -240,31 +230,6 @@ Example configuration: Sets the maximum number of concurrent HTTP/3 streams in a connection. - Syntax: http3_push uri | off; - Default: http3_push off; - Context: http, server, location - - Pre-emptively sends (pushes) a request to the specified uri along with - the response to the original request. Only relative URIs with absolute - path will be processed, for example: - - http3_push /static/css/main.css; - - The uri value can contain variables. - - Several http3_push directives can be specified on the same configuration - level. The off parameter cancels the effect of the http3_push directives - inherited from the previous configuration level. - - - Syntax: http3_push_preload on | off; - Default: http3_push_preload off; - Context: http, server, location - - Enables automatic conversion of preload links specified in the “Link” - response header fields into push requests. - - Syntax: http3 on | off; Default: http3 on; Context: http, server diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c index db0be75da..eb86b2da5 100644 --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -30,11 +30,7 @@ ngx_http_v3_init_session(ngx_connection_t *c) goto failed; } - h3c->max_push_id = (uint64_t) -1; - h3c->goaway_push_id = (uint64_t) -1; - ngx_queue_init(&h3c->blocked); - ngx_queue_init(&h3c->pushing); h3c->keepalive.log = c->log; h3c->keepalive.data = c; diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h index 2c05f089f..94b0d3e78 100644 --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -106,19 +106,11 @@ typedef struct { ngx_flag_t enable_hq; size_t max_table_capacity; ngx_uint_t max_blocked_streams; - ngx_uint_t max_concurrent_pushes; ngx_uint_t max_concurrent_streams; ngx_quic_conf_t quic; } ngx_http_v3_srv_conf_t; -typedef struct { - ngx_flag_t push_preload; - ngx_flag_t push; - ngx_array_t *pushes; -} ngx_http_v3_loc_conf_t; - - struct ngx_http_v3_parse_s { size_t header_limit; ngx_http_v3_parse_headers_t headers; @@ -136,11 +128,6 @@ struct ngx_http_v3_session_s { ngx_queue_t blocked; ngx_uint_t nblocked; - ngx_queue_t pushing; - ngx_uint_t npushing; - uint64_t next_push_id; - uint64_t max_push_id; - uint64_t goaway_push_id; uint64_t next_request_id; off_t total_bytes; diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 41b1704bf..7addf275d 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -36,17 +36,6 @@ typedef struct { static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, - ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_create_push_request( - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, - const char *name, ngx_str_t *value); -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, - ngx_str_t *path, uint64_t push_id); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, @@ -155,14 +144,6 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) out = NULL; ll = &out; - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { - return NGX_ERROR; - } - } - len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { @@ -606,672 +587,6 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) } -static ngx_int_t -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) -{ - u_char *start, *end, *last; - ngx_str_t path; - ngx_int_t rc; - ngx_uint_t i, push; - ngx_table_elt_t *h; - ngx_http_v3_loc_conf_t *h3lcf; - ngx_http_complex_value_t *pushes; - - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); - - if (h3lcf->pushes) { - pushes = h3lcf->pushes->elts; - - for (i = 0; i < h3lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h3lcf->push_preload) { - return NGX_OK; - } - - for (h = r->headers_out.link; h; h = h->next) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 parse link: \"%V\"", &h->value); - - start = h->value.data; - end = h->value.data + h->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_chain_t ***ll) -{ - uint64_t push_id; - ngx_int_t rc; - ngx_chain_t *cl; - ngx_connection_t *c; - ngx_http_v3_session_t *h3c; - ngx_http_v3_srv_conf_t *h3scf; - - c = r->connection; - h3c = ngx_http_v3_get_session(c); - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%L", - path, h3c->npushing, h3scf->max_concurrent_pushes, - h3c->next_push_id, h3c->max_push_id); - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, c->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - if (h3c->max_push_id == (uint64_t) -1 - || h3c->next_push_id > h3c->max_push_id) - { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_push_id"); - return NGX_ABORT; - } - - if (h3c->goaway_push_id != (uint64_t) -1) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to goaway"); - return NGX_ABORT; - } - - if (h3c->npushing >= h3scf->max_concurrent_pushes) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_concurrent_pushes"); - return NGX_ABORT; - } - - if (r->headers_in.server.len == 0) { - return NGX_ABORT; - } - - push_id = h3c->next_push_id++; - - rc = ngx_http_v3_create_push_request(r, path, push_id); - if (rc != NGX_OK) { - return rc; - } - - cl = ngx_http_v3_create_push_promise(r, path, push_id); - if (cl == NULL) { - return NGX_ERROR; - } - - for (**ll = cl; **ll; *ll = &(**ll)->next); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, - uint64_t push_id) -{ - ngx_connection_t *c, *pc; - ngx_http_request_t *r; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc, *phc; - ngx_http_core_srv_conf_t *cscf; - - pc = pr->connection; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "http3 create push request id:%uL", push_id); - - c = ngx_http_v3_create_push_stream(pc, push_id); - if (c == NULL) { - return NGX_ABORT; - } - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); -#endif - - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); - if (hc == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - phc = ngx_http_quic_get_connection(pc); - ngx_memcpy(hc, phc, sizeof(ngx_http_connection_t)); - c->data = hc; - - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); - if (ctx == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - ctx->connection = c; - ctx->request = NULL; - ctx->current_request = NULL; - - c->log->handler = pc->log->handler; - c->log->data = ctx; - c->log->action = "processing pushed request headers"; - - c->log_error = NGX_ERROR_INFO; - - r = ngx_http_create_request(c); - if (r == NULL) { - ngx_http_close_connection(c); - return NGX_ERROR; - } - - c->data = r; - - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - r->http_version = NGX_HTTP_VERSION_30; - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - r->header_in = ngx_create_temp_buf(r->pool, - cscf->client_header_buffer_size); - if (r->header_in == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; - - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); - if (r->schema.data == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->schema.len = pr->schema.len; - - r->uri_start = ngx_pstrdup(r->pool, path); - if (r->uri_start == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - r->uri_end = r->uri_start + path->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return NGX_ERROR; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - return NGX_ERROR; - } - - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) - != NGX_OK) - { - return NGX_ERROR; - } - - if (pr->headers_in.accept_encoding) { - if (ngx_http_v3_set_push_header(r, "accept-encoding", - &pr->headers_in.accept_encoding->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - if (pr->headers_in.accept_language) { - if (ngx_http_v3_set_push_header(r, "accept-language", - &pr->headers_in.accept_language->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - if (pr->headers_in.user_agent) { - if (ngx_http_v3_set_push_header(r, "user-agent", - &pr->headers_in.user_agent->value) - != NGX_OK) - { - return NGX_ERROR; - } - } - - c->read->handler = ngx_http_v3_push_request_handler; - c->read->handler = ngx_http_v3_push_request_handler; - - ngx_post_event(c->read, &ngx_posted_events); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, - ngx_str_t *value) -{ - u_char *p; - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 push header \"%s\": \"%V\"", name, value); - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - p = ngx_pnalloc(r->pool, value->len + 1); - if (p == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - ngx_memcpy(p, value->data, value->len); - p[value->len] = '\0'; - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - h->key.data = (u_char *) name; - h->key.len = ngx_strlen(name); - h->hash = ngx_hash_key(h->key.data, h->key.len); - h->lowcase_key = (u_char *) name; - h->value.data = p; - h->value.len = value->len; - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static void -ngx_http_v3_push_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - ngx_http_request_t *r; - - c = ev->data; - r = c->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); - - ngx_http_process_request(r); -} - - -static ngx_chain_t * -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, - uint64_t push_id) -{ - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(r->connection); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create push promise id:%uL", push_id); - - len = ngx_http_v3_encode_varlen_int(NULL, push_id); - - len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); - - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - NULL, r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - NULL, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - len += ngx_http_v3_encode_field_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - NULL, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, - r->headers_in.user_agent->value.len); - } - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); - - b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, - 0, 0, 0); - - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - r->headers_in.server.data, - r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - path->data, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - r->schema.data, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, - r->headers_in.accept_encoding->value.data, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, - r->headers_in.accept_language->value.data, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, - r->headers_in.user_agent->value.data, - r->headers_in.user_agent->value.len); - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - h3c->payload_bytes += n; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, - NGX_HTTP_V3_FRAME_PUSH_PROMISE); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - - hl = ngx_alloc_chain_link(r->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - return hl; -} - - static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c index 8569f07a0..875e5f29b 100644 --- a/src/http/v3/ngx_http_v3_module.c +++ b/src/http/v3/ngx_http_v3_module.c @@ -18,10 +18,6 @@ static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static void *ngx_http_v3_create_loc_conf(ngx_conf_t *cf); -static char *ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, - void *child); -static char *ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_command_t ngx_http_v3_commands[] = { @@ -40,13 +36,6 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, enable_hq), NULL }, - { ngx_string("http3_max_concurrent_pushes"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v3_srv_conf_t, max_concurrent_pushes), - NULL }, - { ngx_string("http3_max_concurrent_streams"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, @@ -54,20 +43,6 @@ static ngx_command_t ngx_http_v3_commands[] = { offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), NULL }, - { ngx_string("http3_push"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v3_push, - NGX_HTTP_LOC_CONF_OFFSET, - 0, - NULL }, - - { ngx_string("http3_push_preload"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_v3_loc_conf_t, push_preload), - NULL }, - { ngx_string("http3_stream_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -117,8 +92,8 @@ static ngx_http_module_t ngx_http_v3_module_ctx = { ngx_http_v3_create_srv_conf, /* create server configuration */ ngx_http_v3_merge_srv_conf, /* merge server configuration */ - ngx_http_v3_create_loc_conf, /* create location configuration */ - ngx_http_v3_merge_loc_conf /* merge location configuration */ + NULL, /* create location configuration */ + NULL /* merge location configuration */ }; @@ -224,7 +199,6 @@ ngx_http_v3_create_srv_conf(ngx_conf_t *cf) h3scf->enable = NGX_CONF_UNSET; h3scf->enable_hq = NGX_CONF_UNSET; h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; - h3scf->max_concurrent_pushes = NGX_CONF_UNSET_UINT; h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; @@ -255,9 +229,6 @@ ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); - ngx_conf_merge_uint_value(conf->max_concurrent_pushes, - prev->max_concurrent_pushes, 10); - ngx_conf_merge_uint_value(conf->max_concurrent_streams, prev->max_concurrent_streams, 128); @@ -416,102 +387,3 @@ failed: return NGX_CONF_ERROR; } - - -static void * -ngx_http_v3_create_loc_conf(ngx_conf_t *cf) -{ - ngx_http_v3_loc_conf_t *h3lcf; - - h3lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_loc_conf_t)); - if (h3lcf == NULL) { - return NULL; - } - - /* - * set by ngx_pcalloc(): - * - * h3lcf->pushes = NULL; - */ - - h3lcf->push_preload = NGX_CONF_UNSET; - h3lcf->push = NGX_CONF_UNSET; - - return h3lcf; -} - - -static char * -ngx_http_v3_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) -{ - ngx_http_v3_loc_conf_t *prev = parent; - ngx_http_v3_loc_conf_t *conf = child; - - ngx_conf_merge_value(conf->push, prev->push, 1); - - if (conf->push && conf->pushes == NULL) { - conf->pushes = prev->pushes; - } - - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); - - return NGX_CONF_OK; -} - - -static char * -ngx_http_v3_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_v3_loc_conf_t *h3lcf = conf; - - ngx_str_t *value; - ngx_http_complex_value_t *cv; - ngx_http_compile_complex_value_t ccv; - - value = cf->args->elts; - - if (ngx_strcmp(value[1].data, "off") == 0) { - - if (h3lcf->pushes) { - return "\"off\" parameter cannot be used with URI"; - } - - if (h3lcf->push == 0) { - return "is duplicate"; - } - - h3lcf->push = 0; - return NGX_CONF_OK; - } - - if (h3lcf->push == 0) { - return "URI cannot be used with \"off\" parameter"; - } - - h3lcf->push = 1; - - if (h3lcf->pushes == NULL) { - h3lcf->pushes = ngx_array_create(cf->pool, 1, - sizeof(ngx_http_complex_value_t)); - if (h3lcf->pushes == NULL) { - return NGX_CONF_ERROR; - } - } - - cv = ngx_array_push(h3lcf->pushes); - if (cv == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); - - ccv.cf = cf; - ccv.value = &value[1]; - ccv.complex_value = cv; - - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { - return NGX_CONF_ERROR; - } - - return NGX_CONF_OK; -} diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index 7ea284f8a..c51a486c3 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1159,10 +1159,7 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, sw_first_type, sw_type, sw_length, - sw_cancel_push, sw_settings, - sw_max_push_id, - sw_goaway, sw_skip }; @@ -1212,6 +1209,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; } + if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + st->state = sw_length; break; @@ -1233,22 +1234,10 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, switch (st->type) { - case NGX_HTTP_V3_FRAME_CANCEL_PUSH: - st->state = sw_cancel_push; - break; - case NGX_HTTP_V3_FRAME_SETTINGS: st->state = sw_settings; break; - case NGX_HTTP_V3_FRAME_MAX_PUSH_ID: - st->state = sw_max_push_id; - break; - - case NGX_HTTP_V3_FRAME_GOAWAY: - st->state = sw_goaway; - break; - default: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse skip unknown frame"); @@ -1257,30 +1246,6 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, break; - case sw_cancel_push: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_cancel_push(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - case sw_settings: ngx_http_v3_parse_start_local(b, &loc, st->length); @@ -1303,54 +1268,6 @@ ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, break; - case sw_max_push_id: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_set_max_push_id(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - - case sw_goaway: - - ngx_http_v3_parse_start_local(b, &loc, st->length); - - rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, &loc); - - ngx_http_v3_parse_end_local(b, &loc, &st->length); - - if (st->length == 0 && rc == NGX_AGAIN) { - return NGX_HTTP_V3_ERR_FRAME_ERROR; - } - - if (rc != NGX_DONE) { - return rc; - } - - rc = ngx_http_v3_goaway(c, st->vlint.value); - if (rc != NGX_OK) { - return rc; - } - - st->state = sw_type; - break; - case sw_skip: rc = ngx_http_v3_parse_skip(b, &st->length); diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c index 22ddaf3eb..2fc5b07a4 100644 --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -16,19 +16,10 @@ typedef struct { } ngx_http_v3_uni_stream_t; -typedef struct { - ngx_queue_t queue; - uint64_t id; - ngx_connection_t *connection; - ngx_uint_t *npushing; -} ngx_http_v3_push_t; - - static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); 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 void ngx_http_v3_push_cleanup(void *data); static ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type); @@ -316,78 +307,6 @@ ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) } -ngx_connection_t * -ngx_http_v3_create_push_stream(ngx_connection_t *c, uint64_t push_id) -{ - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 2]; - size_t n; - ngx_connection_t *sc; - ngx_pool_cleanup_t *cln; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 create push stream id:%uL", push_id); - - sc = ngx_quic_open_stream(c, 0); - if (sc == NULL) { - goto failed; - } - - p = buf; - p = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_STREAM_PUSH); - p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); - n = p - buf; - - h3c = ngx_http_v3_get_session(c); - h3c->total_bytes += n; - - if (sc->send(sc, buf, n) != (ssize_t) n) { - goto failed; - } - - cln = ngx_pool_cleanup_add(sc->pool, sizeof(ngx_http_v3_push_t)); - if (cln == NULL) { - goto failed; - } - - h3c->npushing++; - - cln->handler = ngx_http_v3_push_cleanup; - - push = cln->data; - push->id = push_id; - push->connection = sc; - push->npushing = &h3c->npushing; - - ngx_queue_insert_tail(&h3c->pushing, &push->queue); - - return sc; - -failed: - - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create push stream"); - - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "failed to create push stream"); - if (sc) { - ngx_http_v3_close_uni_stream(sc); - } - - return NULL; -} - - -static void -ngx_http_v3_push_cleanup(void *data) -{ - ngx_http_v3_push_t *push = data; - - ngx_queue_remove(&push->queue); - (*push->npushing)--; -} - - static ngx_connection_t * ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) { @@ -693,82 +612,6 @@ failed: } -ngx_int_t -ngx_http_v3_set_max_push_id(ngx_connection_t *c, uint64_t max_push_id) -{ - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 MAX_PUSH_ID:%uL", max_push_id); - - if (h3c->max_push_id != (uint64_t) -1 && max_push_id < h3c->max_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - h3c->max_push_id = max_push_id; - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id) -{ - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 GOAWAY:%uL", push_id); - - h3c->goaway_push_id = push_id; - - return NGX_OK; -} - - -ngx_int_t -ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id) -{ - ngx_queue_t *q; - ngx_http_request_t *r; - ngx_http_v3_push_t *push; - ngx_http_v3_session_t *h3c; - - h3c = ngx_http_v3_get_session(c); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 CANCEL_PUSH:%uL", push_id); - - if (push_id >= h3c->next_push_id) { - return NGX_HTTP_V3_ERR_ID_ERROR; - } - - for (q = ngx_queue_head(&h3c->pushing); - q != ngx_queue_sentinel(&h3c->pushing); - q = ngx_queue_next(q)) - { - push = (ngx_http_v3_push_t *) q; - - if (push->id != push_id) { - continue; - } - - r = push->connection->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 cancel push"); - - ngx_http_finalize_request(r, NGX_HTTP_CLOSE); - - break; - } - - return NGX_OK; -} - - ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h index 8c1eb99d3..911e153d7 100644 --- a/src/http/v3/ngx_http_v3_uni.h +++ b/src/http/v3/ngx_http_v3_uni.h @@ -17,12 +17,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_connection_t *ngx_http_v3_create_push_stream(ngx_connection_t *c, - uint64_t push_id); -ngx_int_t ngx_http_v3_set_max_push_id(ngx_connection_t *c, - uint64_t max_push_id); -ngx_int_t ngx_http_v3_goaway(ngx_connection_t *c, uint64_t push_id); -ngx_int_t ngx_http_v3_cancel_push(ngx_connection_t *c, uint64_t push_id); ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); -- cgit v1.2.3 From 94941bd840ce7b011a36f7fe33f3fc7f4c600688 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sun, 14 May 2023 10:31:24 +0400 Subject: Removed README. --- README | 319 ----------------------------------------------------------------- 1 file changed, 319 deletions(-) delete mode 100644 README diff --git a/README b/README deleted file mode 100644 index 7bb7f14fb..000000000 --- a/README +++ /dev/null @@ -1,319 +0,0 @@ -Experimental QUIC support for nginx ------------------------------------ - -1. Introduction -2. Building from sources -3. Configuration -4. Directives -5. Clients -6. Troubleshooting -7. Contributing -8. Links - -1. Introduction - - This is an experimental QUIC [1] / HTTP/3 [2] support for nginx. - - The code is developed in a separate "quic" branch available - at https://hg.nginx.org/nginx-quic. Currently it is based - on nginx mainline 1.23.x. We merge new nginx releases into - this branch regularly. - - The project code base is under the same BSD license as nginx. - - The code is currently at a beta level of quality, however - there are several production deployments with it. - - NGINX Development Team is working on improving HTTP/3 support to - integrate it into the main NGINX codebase. Thus, expect further - updates of this code, including features, changes in behaviour, - bug fixes, and refactoring. NGINX Development team will be - grateful for any feedback and code submissions. - - Please contact NGINX Development Team via nginx-devel mailing list [3]. - - What works now: - - IETF QUIC version 1 is supported. Internet drafts are no longer supported. - - nginx should be able to respond to HTTP/3 requests over QUIC and - it should be possible to upload and download big files without errors. - - + The handshake completes successfully - + One endpoint can update keys and its peer responds correctly - + 0-RTT data is being received and acted on - + Connection is established using TLS Resume Ticket - + A handshake that includes a Retry packet completes successfully - + Stream data is being exchanged and ACK'ed - + An H3 transaction succeeded - + One or both endpoints insert entries into dynamic table and - subsequently reference them from header blocks - + Version Negotiation packet is sent to client with unknown version - + Lost packets are detected and retransmitted properly - + Clients may migrate to new address - -2. Building from sources - - The build is configured using the configure command. - Refer to http://nginx.org/en/docs/configure.html for details. - - When configuring nginx, it's possible to enable QUIC and HTTP/3 - using the following new configuration option: - - --with-http_v3_module - enable QUIC and HTTP/3 - - A library that provides QUIC support is recommended to build nginx, there - are several of those available on the market: - + BoringSSL [4] - + LibreSSL [5] - + QuicTLS [6] - - Alternatively, nginx can be configured with OpenSSL compatibility - layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is - enabled by default if native QUIC support is not detected. - 0-RTT is not supported in OpenSSL compatibility mode. - - Clone the NGINX QUIC repository - - $ hg clone -b quic https://hg.nginx.org/nginx-quic - $ cd nginx-quic - - Use the following command to configure nginx with BoringSSL [4] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../boringssl/include" \ - --with-ld-opt="-L../boringssl/build/ssl \ - -L../boringssl/build/crypto" - $ make - - Alternatively, nginx can be configured with QuicTLS [6] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../quictls/build/include" \ - --with-ld-opt="-L../quictls/build/lib" - - Alternatively, nginx can be configured with a modern version - of LibreSSL [7] - - $ ./auto/configure --with-debug --with-http_v3_module \ - --with-cc-opt="-I../libressl/build/include" \ - --with-ld-opt="-L../libressl/build/lib" - -3. Configuration - - The HTTP "listen" directive got a new option "quic" which enables - QUIC as client transport protocol instead of TCP. - - Along with "quic", it's also possible to specify "reuseport" - option [8] to make it work properly with multiple workers. - - To enable address validation: - - quic_retry on; - - To enable 0-RTT: - - ssl_early_data on; - - To enable GSO (Generic Segmentation Offloading): - - quic_gso on; - - To set host key for various tokens: - - quic_host_key ; - - QUIC requires TLSv1.3 protocol, which is enabled by the default - by "ssl_protocols" directive. - - By default, GSO Linux-specific optimization [10] is disabled. - Enable it in case a corresponding network interface is configured to - support GSO. - - A number of directives were added that configure HTTP/3: - - http3 - http3_hq - http3_stream_buffer_size - http3_max_concurrent_streams - - In http, an additional variable is available: $http3. - The value of $http3 is "h3" for HTTP/3 connections, - "hq" for hq connections, or an empty string otherwise. - -Example configuration: - - http { - log_format quic '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$http3"'; - - access_log logs/access.log quic; - - server { - # for better compatibility it's recommended - # to use the same port for quic and https - listen 8443 quic reuseport; - listen 8443 ssl; - - ssl_certificate certs/example.com.crt; - ssl_certificate_key certs/example.com.key; - - location / { - # required for browsers to direct them into quic port - add_header Alt-Svc 'h3=":8443"; ma=86400'; - } - } - } - -4. Directives - - Syntax: quic_bpf on | off; - Default: quic_bpf off; - Context: main - - Enables routing of QUIC packets using eBPF. - When enabled, this allows to support QUIC connection migration. - The directive is only supported on Linux 5.7+. - - - Syntax: quic_retry on | off; - Default: quic_retry off; - Context: http, server - - Enables the QUIC Address Validation feature. This includes: - - sending a new token in a Retry packet or a NEW_TOKEN frame - - validating a token received in the Initial packet - - - Syntax: quic_gso on | off; - Default: quic_gso off; - Context: http, server - - Enables sending in optimized batch mode using segmentation offloading. - Optimized sending is only supported on Linux featuring UDP_SEGMENT. - - - Syntax: quic_host_key file; - Default: - - Context: http, server - - Specifies a file with the secret key used to encrypt stateless reset and - address validation tokens. By default, a randomly generated key is used. - - - Syntax: quic_active_connection_id_limit number; - Default: quic_active_connection_id_limit 2; - Context: http, server - - Sets the QUIC active_connection_id_limit transport parameter value. - This is the maximum number of connection IDs we are willing to store. - - - Syntax: http3_stream_buffer_size size; - Default: http3_stream_buffer_size 64k; - Context: http, server - - Sets buffer size for reading and writing of the QUIC STREAM payload. - The buffer size is used to calculate initial flow control limits - in the following QUIC transport parameters: - - initial_max_data - - initial_max_stream_data_bidi_local - - initial_max_stream_data_bidi_remote - - initial_max_stream_data_uni - - - Syntax: http3_max_concurrent_streams number; - Default: http3_max_concurrent_streams 128; - Context: http, server - - Sets the maximum number of concurrent HTTP/3 streams in a connection. - - - Syntax: http3 on | off; - Default: http3 on; - Context: http, server - - Enables HTTP/3 protocol negotiation. - - - Syntax: http3_hq on | off; - Default: http3_hq off; - Context: http, server - - Enables HTTP/0.9 protocol negotiation used in QUIC interoperability tests. - -5. Clients - - * Browsers - - Known to work: Firefox 90+ and Chrome 92+ (QUIC version 1) - - Beware of strange issues: sometimes browser may decide to ignore QUIC - Cache clearing/restart might help. Always check access.log and - error.log to make sure the browser is using HTTP/3 and not TCP https. - - * Console clients - - Known to work: ngtcp2, firefox's neqo and chromium's console clients: - - $ examples/client 127.0.0.1 8443 https://example.com:8443/index.html - - $ ./neqo-client https://127.0.0.1:8443/ - - $ chromium-build/out/my_build/quic_client http://example.com:8443 - - - In case everyhing is right, the access log should show something like: - - 127.0.0.1 - - [24/Apr/2020:11:27:29 +0300] "GET / HTTP/3" 200 805 "-" - "nghttp3/ngtcp2 client" "quic" - - -6. Troubleshooting - - Here are some tips that may help to identify problems: - - + Ensure nginx is built with proper SSL library that supports QUIC - - + Ensure nginx is using the proper SSL library in runtime - (`nginx -V` shows what it's using) - - + Ensure a client is actually sending requests over QUIC - (see "Clients" section about browsers and cache) - - We recommend to start with simple console client like ngtcp2 - to ensure the server is configured properly before trying - with real browsers that may be very picky with certificates, - for example. - - + Build nginx with debug support [9] and check the debug log. - It should contain all details about connection and why it - failed. All related messages contain "quic " prefix and can - be easily filtered out. - - + For a deeper investigation, please enable additional debugging - in src/event/quic/ngx_event_quic_connection.h: - - #define NGX_QUIC_DEBUG_PACKETS - #define NGX_QUIC_DEBUG_FRAMES - #define NGX_QUIC_DEBUG_ALLOC - #define NGX_QUIC_DEBUG_CRYPTO - -7. Contributing - - Please refer to - http://nginx.org/en/docs/contributing_changes.html - -8. Links - - [1] https://datatracker.ietf.org/doc/html/rfc9000 - [2] https://datatracker.ietf.org/doc/html/rfc9114 - [3] https://mailman.nginx.org/mailman/listinfo/nginx-devel - [4] https://boringssl.googlesource.com/boringssl/ - [5] https://www.libressl.org/ - [6] https://github.com/quictls/openssl - [7] https://github.com/libressl-portable/portable/releases/tag/v3.6.0 - [8] https://nginx.org/en/docs/http/ngx_http_core_module.html#listen - [9] https://nginx.org/en/docs/debugging_log.html - [10] http://vger.kernel.org/lpc_net2018_talks/willemdebruijn-lpc2018-udpgso-paper-DRAFT-1.pdf -- cgit v1.2.3