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.
This commit is contained in:
Roman Arutyunyan 2023-02-27 14:00:56 +04:00
parent a36ebf7e95
commit 815ef96124
11 changed files with 134 additions and 90 deletions

18
README
View File

@ -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

View File

@ -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",

View File

@ -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;

View File

@ -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

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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];
};

View File

@ -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)
{
ngx_http_v3_session_t *h3c;
if (r->connection->quic) {
#if (NGX_HTTP_V3_HQ)
h3c = ngx_http_v3_get_session(r->connection);
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module);
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);

View File

@ -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;
}

View File

@ -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");