Compare commits

...

921 commits
master ... quic

Author SHA1 Message Date
Sergey Kandaurov
cf616abc3b Merged with the default branch. 2023-03-29 11:14:25 +04:00
Roman Arutyunyan
b1a0c01112 QUIC: style. 2023-03-15 19:57:15 +04:00
Sergey Kandaurov
71e9770303 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.
2023-03-24 19:49:50 +04:00
Roman Arutyunyan
4a41efe418 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.
2023-01-26 15:25:33 +04:00
Roman Arutyunyan
eea23ac250 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.
2023-02-27 14:00:56 +04:00
Roman Arutyunyan
fe0c3d7310 QUIC: OpenSSL compatibility layer.
The change allows to compile QUIC with OpenSSL which lacks BoringSSL QUIC API.

This implementation does not support 0-RTT.
2023-02-22 19:16:53 +04:00
Sergey Kandaurov
8db8943ec3 QUIC: improved ssl_reject_handshake error logging.
The check follows the ngx_ssl_handshake() change in 59e1c73fe02b.
2023-02-23 16:26:38 +04:00
Sergey Kandaurov
ab4347c710 QUIC: using ngx_ssl_handshake_log(). 2023-02-23 16:17:29 +04:00
Sergey Kandaurov
367b5b9230 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.
2023-02-23 16:16:56 +04:00
Sergey Kandaurov
20d9744ba3 QUIC: using NGX_QUIC_ERR_CRYPTO macro in ALPN checks.
Patch by Jiuzhou Cui.
2023-02-23 15:49:59 +04:00
Sergey Kandaurov
23257650b1 QUIC: fixed indentation. 2023-02-13 14:01:50 +04:00
Sergey Kandaurov
ea97f3a0e3 README: fixed toc.
While here, updated link to mailman.
2023-02-13 13:41:35 +04:00
Sergey Kandaurov
a0bcb2042e README: updated building from sources, added directives reference. 2023-02-08 12:47:35 +04:00
Roman Arutyunyan
b9ce6d5074 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.
2023-01-31 15:26:33 +04:00
Roman Arutyunyan
204f0f10cd 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.
2023-01-31 14:12:18 +04:00
Maxim Dounin
c95da93677 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.
2023-01-24 02:57:42 +03:00
Sergey Kandaurov
bdc9726c1b 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.
2023-01-18 19:20:18 +04:00
Roman Arutyunyan
994f4ef06c 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.
2023-01-10 17:24:10 +04:00
Roman Arutyunyan
1565a79f8a 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.
2023-01-05 19:03:22 +04:00
Roman Arutyunyan
42e1233601 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.
2023-01-05 18:15:46 +04:00
Roman Arutyunyan
abd52b27e1 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.
2023-01-10 17:42:40 +04:00
Roman Arutyunyan
1253ac84df 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.
2023-01-10 14:05:18 +04:00
Sergey Kandaurov
5f8fa53775 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.
2023-01-10 17:59:16 +04:00
Roman Arutyunyan
bc654726e1 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.
2023-01-03 16:24:45 +04:00
Sergey Kandaurov
8d5850da1f Merged with the default branch. 2023-01-02 17:10:22 +04:00
Sergey Kandaurov
68afb2f973 Merged with the default branch. 2022-12-15 19:40:44 +04:00
Roman Arutyunyan
62700a2ed2 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.
2022-10-25 12:52:09 +04:00
Roman Arutyunyan
aa58c6457a 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().
2022-11-30 12:51:15 +04:00
Roman Arutyunyan
2fb971a6b9 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().
2022-08-22 14:09:03 +04:00
Roman Arutyunyan
1ff821f800 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.
2022-11-30 14:09:08 +04:00
Roman Arutyunyan
595a642018 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.
2022-10-19 17:45:18 +04:00
Roman Arutyunyan
7cccd64670 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.
2022-10-19 17:45:30 +04:00
Roman Arutyunyan
f24d60c54d QUIC: do not send MAX_STREAMS in shutdown state.
No more streams are expected from client.
2022-09-07 13:12:56 +04:00
Roman Arutyunyan
75f37d3fc6 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.
2022-08-22 15:33:23 +04:00
Roman Arutyunyan
6949b4b4c2 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.
2022-11-29 17:46:46 +04:00
Roman Arutyunyan
fbd48371fe 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.
2022-09-07 19:25:13 +04:00
Roman Arutyunyan
5ae19ab1a5 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.
2022-08-22 15:28:51 +04:00
Roman Arutyunyan
be17320c91 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.
2022-09-07 12:37:15 +04:00
Sergey Kandaurov
6c843d19f2 QUIC: fixed computation of nonce with packet numbers beyond 2^32.
Prodded by Yu Zhu.
2022-11-25 15:56:33 +04:00
Jiuzhou Cui
a7bda07bda HTTP/3: fixed build without NGX_PCRE (broken by 0f5fc7a320db). 2022-11-25 15:07:23 +08:00
Roman Arutyunyan
7702f830dd 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.
2022-11-23 18:50:26 +04:00
Sergey Kandaurov
a53a72f079 QUIC: fixed C4334 MSVC warning about 32 to 64 bits conversion. 2022-11-22 18:05:37 +04:00
Sergey Kandaurov
3cf42a4a1d QUIC: plug MSVC warning about potentially uninitialized variable. 2022-11-22 18:05:37 +04:00
Sergey Kandaurov
c71b6810be Added shutdown macros for win32 required for QUIC. 2022-11-22 18:05:36 +04:00
Sergey Kandaurov
690e72d8b6 QUIC: fixed C4389 MSVC warning about signed/unsigned mismatch. 2022-11-22 18:05:36 +04:00
Sergey Kandaurov
41a5fad87b 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.
2022-11-22 18:05:35 +04:00
Sergey Kandaurov
8e422fd5e8 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.
2022-11-22 18:05:35 +04:00
Sergey Kandaurov
5a00ab5877 QUIC: fixed C4706 warnings with MSVC 2010.
The fix is to avoid assignments within conditional expression.
2022-11-22 18:05:34 +04:00
Sergey Kandaurov
4d1689d053 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.
2022-11-22 14:10:04 +04:00
Roman Arutyunyan
0854b8073f 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.
2022-11-01 17:00:35 +04:00
Sergey Kandaurov
de23dc786b Merged with the default branch. 2022-10-20 16:41:36 +04:00
Sergey Kandaurov
628d0113ae README: converted to passive voice, LibreSSL support. 2022-10-20 16:30:43 +04:00
Sergey Kandaurov
993631dea7 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.
2022-10-20 16:21:07 +04:00
Sergey Kandaurov
d0bd68de53 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.
2022-10-20 16:21:06 +04:00
Sergey Kandaurov
e5ebf25fcc 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.
2022-10-20 16:21:06 +04:00
Sergey Kandaurov
6b7d3d64c3 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
2022-10-20 16:21:05 +04:00
Sergey Kandaurov
03a1a94d18 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.
2022-09-30 17:24:47 +04:00
Sergey Kandaurov
26c9efed33 README: updated the current status. 2022-09-12 18:37:36 +04:00
Roman Arutyunyan
2d72193dc0 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.
2022-08-03 16:59:51 +04:00
Vladimir Homutov
d3d5a9b8a4 QUIC: avoided pool usage in token calculation. 2022-05-31 11:05:22 +04:00
Vladimir Homutov
fe6cac822c QUIC: removed ngx_quic_keys_new().
The ngx_quic_keys_t structure is now exposed.
2022-07-27 17:31:16 +04:00
Vladimir Homutov
da18efff87 QUIC: avoided pool usage in ngx_quic_protection.c. 2022-07-27 17:16:40 +04:00
Vladimir Homutov
17c9506b58 QUIC: fixed-length buffers for secrets. 2022-07-27 17:15:33 +04:00
Sergey Kandaurov
03b740ba06 Merged with the default branch. 2022-07-26 19:54:11 +04:00
Sergey Kandaurov
f2bc2e05d0 Merged with the default branch. 2022-06-22 18:34:58 +04:00
Sergey Kandaurov
658e350aae HTTP/3: updated SETTINGS_MAX_FIELD_SECTION_SIZE name. 2022-06-08 16:19:01 +04:00
Sergey Kandaurov
62e6a35da7 README: updated after HTTP/3 RFC publication, minor refinements. 2022-06-08 15:30:08 +04:00
Roman Arutyunyan
b752b1ec26 HTTP/3: require that field section base index is not negative.
RFC 9204 explicitly requires that.
2022-05-26 16:17:56 +04:00
Roman Arutyunyan
86a3380000 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.
2022-04-20 16:01:17 +04:00
Roman Arutyunyan
c5f5a571d9 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.
2022-02-17 22:38:42 +03:00
Sergey Kandaurov
4ce2114724 QUIC: fixed indentation. 2022-02-16 15:45:47 +03:00
Roman Arutyunyan
418ce3b294 QUIC: optimize insertion at the end of QUIC buffer. 2022-02-14 14:54:34 +03:00
Roman Arutyunyan
ab1adbbc08 QUIC: eliminated ngx_quic_copy_buf().
Its only call is substituted with QUIC buffer write/read pair.
2022-02-14 14:53:46 +03:00
Roman Arutyunyan
45c5af421b 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.
2022-02-14 14:51:10 +03:00
Roman Arutyunyan
8363d84edd 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().
2022-02-14 15:27:59 +03:00
Roman Arutyunyan
28919d3e59 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.
2022-02-05 12:54:54 +03:00
Sergey Kandaurov
6e67500606 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.
2022-02-15 14:12:34 +03:00
Sergey Kandaurov
45e76acd51 Merged with the default branch. 2022-02-14 10:14:07 +03:00
Vladimir Homutov
d261bc2b0b 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.
2022-02-09 15:51:42 +03:00
Vladimir Homutov
615dbe6b64 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.
2022-02-09 15:53:21 +03:00
Roman Arutyunyan
6920deb708 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.
2022-02-09 14:49:05 +03:00
Vladimir Homutov
e2b85c16d0 QUIC: fixed the "quic_stream_buffer_size" directive.
The default value is now correctly set and the configuration
is properly merged.
2022-02-08 23:00:12 +03:00
Roman Arutyunyan
2ddbea69f1 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.
2022-02-03 18:11:59 +03:00
Roman Arutyunyan
89b9a30c3c 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.
2022-02-03 21:29:05 +03:00
Sergey Kandaurov
2e7f031ba8 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.
2022-02-02 15:57:08 +03:00
Vladimir Homutov
144c6f6aa7 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.
2022-02-02 14:16:48 +03:00
Sergey Kandaurov
afe8ad39f0 QUIC: do not declare SSL buffering, it's not used.
No functional changes.
2022-02-01 20:46:32 +03:00
Vladimir Homutov
e16352881a 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
2022-02-01 15:43:56 +03:00
Vladimir Homutov
bda9e27f29 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".
2022-01-28 14:57:33 +03:00
Vladimir Homutov
4f37d2d295 QUIC: dead code removed.
The ngx_quic_parse_packet() now returns NGX_OK, NGX_ERROR (parsing failed)
and NGX_ABORT (unsupported version).
2022-02-01 13:01:10 +03:00
Vladimir Homutov
2e249af96e QUIC: merged ngx_quic_close_quic() and ngx_quic_close_connection().
The separate ngx_quic_close_quic() doesn't make much sense.
2022-02-01 13:05:38 +03:00
Vladimir Homutov
03fa9875a6 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.
2022-02-01 14:35:31 +03:00
Roman Arutyunyan
0b7051b4f3 QUIC: stream event setting function.
The function ngx_quic_set_event() is now called instead of posting events
directly.
2022-01-26 12:01:31 +03:00
Roman Arutyunyan
b97e7a75a7 QUIC: style. 2022-01-31 18:09:03 +03:00
Roman Arutyunyan
c2e9c35718 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.
2022-01-31 09:46:30 +03:00
Roman Arutyunyan
6e7c3ad42c QUIC: introduced explicit stream states.
This allows to eliminate the usage of stream connection event flags for tracking
stream state.
2022-01-31 09:46:02 +03:00
Roman Arutyunyan
6850f6e935 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.
2022-01-27 12:20:47 +03:00
Roman Arutyunyan
d503544196 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.
2022-01-31 09:16:47 +03:00
Sergey Kandaurov
1d39bb83db 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.
2022-01-27 13:14:01 +03:00
Roman Arutyunyan
1e056aced9 QUIC: style. 2022-01-26 18:03:45 +03:00
Vladimir Homutov
b3fd447923 QUIC: fixed handling of initial source connection id.
This was broken in 1e2f4e9c8195.

While there, adjusted formatting of debug message with socket seqnum.
2022-01-26 15:48:12 +03:00
Sergey Kandaurov
af0552eb37 README: updated link to nginx-devel mailman. 2022-01-26 14:15:40 +03:00
Sergey Kandaurov
1c65bfc630 README: updated info about incomplete features. 2022-01-26 14:15:40 +03:00
Sergey Kandaurov
abcf055579 README: updated to QUICv1.
While here, removed old browsers tips.
2022-01-26 14:15:40 +03:00
Sergey Kandaurov
fcf955fdf8 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.
2022-01-26 14:15:40 +03:00
Sergey Kandaurov
d1b929cc71 QUIC: removed draft versions support. 2022-01-26 14:15:40 +03:00
Sergey Kandaurov
6c4bd8acd6 HTTP/3: removed draft versions support in ALPN. 2022-01-26 14:15:40 +03:00
Roman Arutyunyan
930ff068d4 QUIC: changed debug message. 2022-01-21 11:20:18 +03:00
Sergey Kandaurov
99d696f0da Merged with the default branch. 2022-01-25 23:42:48 +03:00
Vladimir Homutov
d54a5b5884 QUIC: fixed macro style. 2022-01-25 15:48:05 +03:00
Roman Arutyunyan
14a87fa1fa 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.
2022-01-25 09:45:50 +03:00
Vladimir Homutov
4e07aec877 QUIC: removed stale declaration.
The ngx_quic_get_unconnected_socket() was removed in 1e2f4e9c8195.
2022-01-21 11:41:39 +03:00
Vladimir Homutov
31d0317338 QUIC: avoid logging error in case of version negotiation.
Previously, "early error" message was logged in this case.
2022-01-23 21:29:36 +03:00
Vladimir Homutov
9ff3d71a97 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).
2022-01-20 22:00:25 +03:00
Vladimir Homutov
006a271f72 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
2022-01-19 22:39:24 +03:00
Vladimir Homutov
339eb9ad8b QUIC: the "quic_active_connection_id_limit" directive.
The directive sets corresponding transport parameter and limits number of
created client ids.
2022-01-18 12:49:55 +03:00
Roman Arutyunyan
bad85f3f8d 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.
2022-01-17 14:39:04 +03:00
Roman Arutyunyan
bb98e475b6 QUIC: fixed format specifier after 3789f4a56d65. 2022-01-16 00:28:13 +03:00
Roman Arutyunyan
7e2e280495 QUIC: return written size from ngx_quic_write_chain().
This allows to escape calculating it before calling the function.
2022-01-13 11:34:42 +03:00
Sergey Kandaurov
f6048da13b README: documented QuicTLS support. 2022-01-13 16:56:07 +03:00
Sergey Kandaurov
5a825889b6 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.
2022-01-13 15:57:21 +03:00
Sergey Kandaurov
ee55da0516 HTTP/3: removed useless warning regarding OpenSSL library.
After 0e6528551f26, it became impossible to run into this path.
2022-01-13 15:57:15 +03:00
Roman Arutyunyan
63a5f45fe4 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.
2022-01-13 11:23:53 +03:00
Roman Arutyunyan
67e147aacc 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.
2022-01-11 18:57:02 +03:00
Roman Arutyunyan
f3327857af HTTP/3: set c->error on read error in ngx_http_test_reading().
Similar to other error/eof cases.
2022-01-12 11:57:46 +03:00
Roman Arutyunyan
8b88be5ed0 HTTP/3: simplified code. 2022-01-12 11:57:06 +03:00
Roman Arutyunyan
668f43ca38 QUIC: modified HTTP version test.
The new condition produces smaller diff to the default branch and is similar to
HTTP/2 case.
2022-01-12 11:54:39 +03:00
Sergey Kandaurov
d98314233f 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.
2021-12-30 12:59:32 +03:00
Roman Arutyunyan
22eb20ae31 Style. 2021-12-29 15:33:51 +03:00
Sergey Kandaurov
217bec97be Merged with the default branch. 2021-12-29 15:17:26 +03:00
Vladimir Homutov
e13ef94157 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().
2021-12-27 13:49:56 +03:00
Ruslan Ermilov
38b5a6065f Fixed a mismerge in 5c86189a1c1b. 2021-12-28 15:01:02 +03:00
Roman Arutyunyan
7ceefcdb91 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()
2021-12-27 16:15:28 +03:00
Roman Arutyunyan
95824195ef QUIC: fixed format specifier after 6ccf3867959a. 2021-12-28 13:50:01 +03:00
Vladimir Homutov
04cb5fa243 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.
2021-12-28 13:24:58 +03:00
Roman Arutyunyan
4a60b40678 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.
2021-12-24 18:39:22 +03:00
Roman Arutyunyan
83d57cbffa QUIC: removed ngx_quic_copy_chain().
The function is unused.
2021-12-16 17:07:11 +03:00
Roman Arutyunyan
fa3c56e16a 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()
2021-12-16 17:06:35 +03:00
Roman Arutyunyan
6ad7e2eb04 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.
2021-12-24 18:17:23 +03:00
Roman Arutyunyan
8ba0591205 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.
2021-12-24 18:13:51 +03:00
Vladimir Homutov
c09c4c058b 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()).
2021-12-27 13:52:57 +03:00
Vladimir Homutov
15a3e8cd6e QUIC: refactored ngx_quic_validate_path().
The function now accepts path argument, as suggested by the name. Socket is
not really needed inside.
2021-12-16 11:49:08 +03:00
Vladimir Homutov
09e77a9751 QUIC: added missing check for backup path existence. 2021-12-16 11:42:28 +03:00
Ruslan Ermilov
da7d48ca9f Merged with the default branch. 2021-12-24 15:53:59 +03:00
Roman Arutyunyan
e6b7f80fb5 QUIC: added path limiting function ngx_quic_path_limit(). 2021-12-14 16:24:20 +03:00
Vladimir Homutov
6f57aada90 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).
2021-12-13 09:48:33 +03:00
Vladimir Homutov
36f796fd76 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.
2021-12-13 17:27:29 +03:00
Roman Arutyunyan
fb6c936dad QUIC: write and full stream shutdown support.
Full stream shutdown is now called from stream cleanup handler instead of
explicitly sending frames.
2021-12-13 14:49:42 +03:00
Roman Arutyunyan
dca7a44179 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.
2021-12-10 19:43:50 +03:00
Roman Arutyunyan
2e04ad69ca QUIC: post stream events instead of calling their handlers.
This potentially reduces the number of handler calls.
2021-11-23 21:39:51 +03:00
Ruslan Ermilov
926e3d1fa2 QUIC: removed configure time test for BPF sockhash.
The test verifies kernel version on a build machine,
but actually used kernel may be different.
2021-12-09 15:30:50 +03:00
Ruslan Ermilov
8802f709d7 QUIC: configure cleanup.
Renamed and removed some macros.
2021-12-09 15:30:01 +03:00
Vladimir Homutov
6c742f75bc QUIC: added missing frame initialization.
Currently, all used fields are initialized, but usage may change in future.
2021-12-06 11:04:55 +03:00
Vladimir Homutov
5d7fa710ca QUIC: refactored ngx_quic_frame_sendto() function.
The function now takes path as an argument to deal with associated
restrictions and update sent counter.
2021-12-09 12:40:14 +03:00
Sergey Kandaurov
5917a86f5b QUIC: fixed e06283038ec8 mis-merge.
The NGX_HTTP_QUIC macro was removed in 33226ac61076.
2021-12-09 11:15:25 +03:00
Sergey Kandaurov
83a7622d32 HTTP/3: cleanup after "listen .. quic" removal in be08b858086a. 2021-12-08 17:04:56 +03:00
Vladimir Homutov
7519cf88b7 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.
2021-12-07 16:07:47 +03:00
Sergey Kandaurov
d0b788c0bd 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
2021-12-07 15:49:51 +03:00
Sergey Kandaurov
41b87485eb HTTP/3: avoid sending stream cancellation for pushed streams. 2021-12-07 15:49:30 +03:00
Sergey Kandaurov
8a0a6a85f4 QUIC: converted ngx_quic_keys_set_encryption_secret() to NGX codes.
While here, removed check for encryption level zero, redundant by its nature.
2021-12-07 15:42:10 +03:00
Roman Arutyunyan
bd8e49cbc6 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.
2021-12-07 13:01:28 +03:00
Vladimir Homutov
c1d88961cb 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.
2021-12-06 15:19:54 +03:00
Roman Arutyunyan
52b891d39c 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.
2021-12-01 11:02:17 +03:00
Roman Arutyunyan
88d2f21fc9 HTTP/3: http3_hq directive and NGX_HTTP_V3_HQ macro.
Listen quic parameter is no longer supported.
2021-12-04 10:52:55 +03:00
Roman Arutyunyan
6dc747f5ff HTTP/3: merged ngx_http_quic_module into ngx_http_v3_module. 2021-12-06 13:02:36 +03:00
Vladimir Homutov
56feb8f3ca 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.
2021-12-02 14:09:52 +03:00
Sergey Kandaurov
bde585656a QUIC: logging of CRYPTO frame payload under NGX_QUIC_DEBUG_FRAMES. 2021-12-02 13:59:56 +03:00
Sergey Kandaurov
1cf5df0781 HTTP/3: adjusted ALPN macro names to align with 61abb35bb8cf. 2021-12-02 13:59:09 +03:00
Vladimir Homutov
9b92d9600f 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.
2021-12-01 18:33:29 +03:00
Sergey Kandaurov
0d8ddc57e8 QUIC: ngx_quic_send_alert() callback moved to its place. 2021-11-30 14:30:59 +03:00
Sergey Kandaurov
a9546f2161 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.
2021-11-30 14:30:59 +03:00
Vladimir Homutov
5ddebcaaff 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).
2021-11-18 14:33:21 +03:00
Vladimir Homutov
70907fdbe0 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 <ip> <port> <url>

ngtcp2/client --change-local-addr=200ms --delay-stream=500ms --nat-rebinding \
              <ip> <port> <url>
2021-11-29 11:51:14 +03:00
Vladimir Homutov
1c29db5dba 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.
2021-11-29 11:49:09 +03:00
Vladimir Homutov
2730e38d8b 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.
2021-11-18 14:19:36 +03:00
Vladimir Homutov
64488e4e6f QUIC: additional checks for the RETIRE_CONNECTION_ID frame. 2021-11-18 14:19:31 +03:00
Roman Arutyunyan
52021bf4a0 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.
2021-11-17 23:07:51 +03:00
Roman Arutyunyan
75c17858cc 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.
2021-11-17 23:07:38 +03:00
Roman Arutyunyan
7bd866a9a5 HTTP/3: fixed compilation with QUIC, but without HTTP/3. 2021-11-17 18:49:48 +03:00
Roman Arutyunyan
07a983f8df 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.
2021-11-11 19:07:00 +03:00
Sergey Kandaurov
8d62635537 QUIC: stop processing new client streams at the closing state. 2021-11-12 16:29:07 +03:00
Roman Arutyunyan
56bbbf72d0 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.
2021-10-18 14:48:11 +03:00
Roman Arutyunyan
f72a2bb3f6 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.
2021-10-18 15:47:06 +03:00
Roman Arutyunyan
261439aed2 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.
2021-10-18 15:22:33 +03:00
Vladimir Homutov
a6eadf8dc0 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.
2021-11-11 15:15:07 +03:00
Vladimir Homutov
f5c17cf3b7 QUIC: removed ngx_quic_error_text() declaration.
This is a leftover from cab3b7a070ef.
2021-11-10 14:36:36 +03:00
Vladimir Homutov
d7aaf8eb18 QUIC: fixed GSO packets count.
Thanks to Andrey Kolyshkin <a.kolyshkin@corp.vk.com>
2021-11-09 21:17:05 +03:00
Vladimir Homutov
ce10c373e8 QUIC: removed dead code.
The function is no longer used since b3d9e57d0f62.
2021-11-10 13:49:01 +03:00
Vladimir Homutov
9a035adb05 QUIC: converted client_tp_done to bitfield. 2021-11-08 15:41:12 +03:00
Vladimir Homutov
f765b64594 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.
2021-10-13 14:48:33 +03:00
Vladimir Homutov
e945e92ece 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.
2021-11-03 13:36:21 +03:00
Vladimir Homutov
6c1a6d7bb3 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.
2021-10-07 13:48:29 +03:00
Vladimir Homutov
273b23d5a7 QUIC: removed unused argument in ngx_quic_create_short_header(). 2021-10-07 12:24:47 +03:00
Vladimir Homutov
c13ab56118 QUIC: added function to initialize packet. 2021-09-30 12:02:29 +03:00
Vladimir Homutov
2e001d8708 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.
2021-10-22 12:59:44 +03:00
Vladimir Homutov
9ff755cd7c QUIC: added shutdown support in stream proxy. 2021-09-23 16:25:49 +03:00
Sergey Kandaurov
965f51cde3 Merged with the default branch. 2021-11-03 11:22:07 +03:00
Sergey Kandaurov
69805192b4 QUIC: style. 2021-10-26 18:05:57 +03:00
Sergey Kandaurov
369804cacb 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.
2021-10-26 17:43:10 +03:00
Sergey Kandaurov
63aa8908c5 QUIC: refactored OCSP validation in preparation for 0-RTT support. 2021-10-26 17:43:10 +03:00
Vladimir Homutov
d5e61b4c8c 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.
2021-10-19 14:32:50 +03:00
Vladimir Homutov
a07e6d352e 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).
2021-10-15 12:26:42 +03:00
Roman Arutyunyan
b35afd4e4a QUIC: limited the total number of frames.
Exceeding 10000 allocated frames is considered a flood.
2021-10-13 14:46:51 +03:00
Roman Arutyunyan
49b5584de3 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.
2021-10-13 14:41:46 +03:00
Roman Arutyunyan
6fe2069e12 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.
2021-10-07 13:22:42 +03:00
Roman Arutyunyan
334d204baf HTTP/3: fixed request length calculation.
Previously, when request was blocked, r->request_length was not updated.
2021-10-06 14:51:16 +03:00
Roman Arutyunyan
513f850061 HTTP/3: removed client-side encoder support.
Dynamic tables are not used when generating responses anyway.
2021-10-06 14:48:59 +03:00
Martin Duke
d3f0dd0321 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.
2021-10-12 11:57:50 +03:00
Martin Duke
1f523e0d47 QUIC: Check if CID has been used in stateless reset check
Section 10.3.1 of RFC9000 requires this check.
2021-10-12 11:56:49 +03:00
Roman Arutyunyan
c5069b7f27 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.
2021-09-21 16:24:33 +03:00
Roman Arutyunyan
59ba20b65e QUIC: reset stream only once. 2021-09-22 14:02:56 +03:00
Roman Arutyunyan
a27409d50c HTTP/3: reset streams with incomplete responses or timeouts.
This prevents client from closing the QUIC connection due to response parse
error.
2021-09-27 17:08:48 +03:00
Roman Arutyunyan
599d02f027 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.
2021-09-30 17:14:42 +03:00
Sergey Kandaurov
494d3fddb2 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
2021-09-29 15:06:28 +03:00
Sergey Kandaurov
63d2ab4a0d 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
2021-09-29 15:01:59 +03:00
Sergey Kandaurov
e48d428d75 Stream: detect "listen .. quic" without TLSv1.3. 2021-09-29 15:01:56 +03:00
Sergey Kandaurov
f210fb7953 Fixed mismerge of ssl_reject_handshake in 71b7453fb11f.
In particular, this fixes rejecting "listen .. quic|http3" configurations
without TLSv1.3 configured.
2021-09-29 15:01:53 +03:00
Sergey Kandaurov
517e44fe5b HTTP/3: fixed server push after ea9b645472b5.
Unlike in HTTP/2, both "host" and ":authority" reside in r->headers_in.server.
2021-09-27 17:42:53 +03:00
Sergey Kandaurov
7b5283b003 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.
2021-09-27 15:38:55 +03:00
Ruslan Ermilov
6e1487496d 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.
2021-09-27 10:10:38 +03:00
Ruslan Ermilov
d116018bf7 Configure: check for QUIC 0-RTT support at compile time. 2021-09-27 10:10:37 +03:00
Sergey Kandaurov
9d1f7d142f HTTP/3: fixed null pointer dereference with server push.
See details for HTTP/2 fix in 8b0553239592 for a complete description.
2021-09-22 14:10:43 +03:00
Roman Arutyunyan
296f54ff65 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.
2021-09-22 14:08:21 +03:00
Roman Arutyunyan
60991ababd 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.
2021-09-17 15:28:31 +03:00
Sergey Kandaurov
d2b5c8ad58 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.
2021-09-22 14:01:18 +03:00
Roman Arutyunyan
145268fe63 QUIC: simplified stream fd initialization. 2021-09-21 18:25:26 +03:00
Ruslan Ermilov
d0e0fb02ba Configure: USE_OPENSSL_QUIC=YES implies USE_OPENSSL=YES. 2021-09-21 14:46:30 +03:00
Ruslan Ermilov
69fbd46f02 Configure: ordered directories. 2021-09-21 14:46:25 +03:00
Ruslan Ermilov
63f265eaa9 Configure: simplified condition. 2021-09-21 14:46:17 +03:00
Roman Arutyunyan
5a3cca487d 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.
2021-09-17 16:32:23 +03:00
Roman Arutyunyan
d2463a2dc3 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.
2021-09-09 16:55:00 +03:00
Sergey Kandaurov
542d5f7996 HTTP/3: added CONNECT and TRACE methods rejection.
It has got lost in e1eb7f4ca9f1, let alone a subsequent update in 63c66b7cc07c.
2021-09-16 13:13:22 +03:00
Ruslan Ermilov
ec9069206a Removed NGX_OPENSSL_QUIC macro, NGX_QUIC is enough. 2021-09-14 12:09:13 +03:00
Sergey Kandaurov
bd9900a70f 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.
2021-09-13 16:25:37 +03:00
Sergey Kandaurov
33eac933f5 HTTP/3: Huffman encoding for the Location response field. 2021-09-13 16:25:32 +03:00
Sergey Kandaurov
6437be8849 HTTP/3: Huffman encoding for the Last-Modified response field. 2021-09-13 16:25:31 +03:00
Sergey Kandaurov
65ad235948 HTTP/3: Huffman encoding for the Content-Type response field. 2021-09-13 16:25:23 +03:00
Sergey Kandaurov
5cc25926d1 HTTP/3: implemented QPACK Huffman encoding for response fields. 2021-09-13 16:25:08 +03:00
Roman Arutyunyan
5cb81675d5 HTTP/3: reading body buffering in filters.
This change follows similar changes in HTTP/1 and HTTP/2 in 9cf043a5d9ca.
2021-09-09 15:47:29 +03:00
Sergey Kandaurov
3b47302e94 QUIC: removed Firefox workaround for trailing zeroes in datagrams.
This became unnecessary after discarding invalid packets since a6784cf32c13.
2021-09-09 19:12:27 +03:00
Ruslan Ermilov
ea78a549ed QUIC: macro style. 2021-09-09 15:40:08 +03:00
Ruslan Ermilov
7372cd0fae Changed the OpenSSL QUIC support detection.
As was changed in 253cf267f95a.
2021-09-09 15:34:00 +03:00
Sergey Kandaurov
c530e2624e Merged with the default branch. 2021-09-08 15:53:00 +03:00
Roman Arutyunyan
77990eb5b9 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().
2021-09-06 16:59:00 +03:00
Mariano Di Martino
c0ab3094ae 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.
2021-09-03 14:23:50 +03:00
Sergey Kandaurov
0bdfcc0fdd README: HTTP/3 trailers are now supported. 2021-09-01 11:12:23 +03:00
Sergey Kandaurov
ddf508aef8 Merged with the default branch. 2021-09-01 10:57:25 +03:00
Roman Arutyunyan
7c9cdcd3f9 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.
2021-07-08 21:52:47 +03:00
Sergey Kandaurov
d4fae36970 QUIC: Stateless Reset Token debug logging cleanup. 2021-08-24 14:41:31 +03:00
Sergey Kandaurov
640fd08e71 QUIC: removed duplicate logging of Stateless Reset Token. 2021-08-24 14:40:33 +03:00
Sergey Kandaurov
caa3e48d23 HTTP/3: fixed dead store assignment.
Found by Clang Static Analyzer.
2021-08-24 13:03:48 +03:00
Sergey Kandaurov
675c700e7b QUIC: fixed dead store assignment.
Found by Clang Static Analyzer.
2021-08-24 13:03:46 +03:00
Sergey Kandaurov
94e092c897 QUIC: fixed format specifiers in ngx_quic_bpf module. 2021-08-17 11:41:11 +03:00
Sergey Kandaurov
34db4b5a52 HTTP/3: disabled control characters and space in header names.
This is a follow up to 41f4bd4c51f1.
2021-08-10 12:35:12 +03:00
Vladimir Homutov
9a632808a3 QUIC: better ordering in auto/modules. 2021-08-05 11:13:29 +03:00
Vladimir Homutov
9eae4cc19a HTTP/3: got rid of HTTP/2 module dependency.
The Huffman encoder/decoder now can be built separately from HTTP/2 module.
2021-08-05 11:09:13 +03:00
Roman Arutyunyan
ecadcc5186 HTTP/3: replaced macros with values. 2021-08-04 17:35:11 +03:00
Roman Arutyunyan
923de21472 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.
2021-08-05 09:20:32 +03:00
Sergey Kandaurov
4d7ce86ff5 QUIC: client certificate validation with OCSP. 2021-08-04 15:49:18 +03:00
Roman Arutyunyan
671aa053d5 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.
2021-07-29 16:01:37 +03:00
Roman Arutyunyan
bc073e9460 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.
2021-08-02 15:48:21 +03:00
Roman Arutyunyan
a63a5cfdc0 HTTP/3: http3_max_uni_streams directive.
The directive limits the number of uni streams client is allowed to create.
2021-07-29 12:17:56 +03:00
Roman Arutyunyan
080d689d18 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.
2021-07-29 12:49:16 +03:00
Vladimir Homutov
bac84680d8 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.
2021-07-28 17:23:18 +03:00
Roman Arutyunyan
6180a7c9f7 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.
2021-07-29 10:03:36 +03:00
Roman Arutyunyan
8b946e8575 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.
2021-07-28 13:21:47 +03:00
Vladimir Homutov
8ad7e74e1e QUIC: updated README with GSO details. 2021-07-23 11:25:16 +03:00
Roman Arutyunyan
017801b303 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.
2021-07-16 15:43:01 +03:00
Roman Arutyunyan
66991ee03a HTTP/3: response trailers support. 2021-07-13 22:44:03 +03:00
Sergey Kandaurov
7703098c6f 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.
2021-07-22 15:00:37 +03:00
Vladimir Homutov
0cde3f4f6e 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).
2021-07-20 12:37:12 +03:00
Vladimir Homutov
f59b487a69 Core: fixed errno clobbering in ngx_sendmsg().
This was broken by 2dfd313f22f2.
2021-07-20 12:04:58 +03:00
Sergey Kandaurov
6750340391 Merged with the default branch. 2021-07-15 16:28:21 +03:00
Vladimir Homutov
5224f4a67b Core: added separate function for local source address cmsg. 2021-07-15 14:22:54 +03:00
Vladimir Homutov
b828c7f3c6 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.
2021-07-15 14:22:00 +03:00
Vladimir Homutov
ab83c52df6 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.
2021-07-15 14:21:39 +03:00
Vladimir Homutov
906023d724 Core: the ngx_event_udp.h header file. 2021-07-12 16:40:57 +03:00
Vladimir Homutov
8f9794c09f 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.
2021-07-05 13:17:10 +03:00
Sergey Kandaurov
53e0bd538c 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
2021-07-01 15:37:53 +03:00
Roman Arutyunyan
13dc9039b0 QUIC: consider max_ack_delay=16384 invalid.
As per RFC 9000:

   Values of 2^14 or greater are invalid.
2021-06-30 13:47:38 +03:00
Vladimir Homutov
bee065a7bd QUIC: fixed client certificates verification in stream.
The stream session requires 'ssl' flag to be set in order to perform
certificate verification.
2021-06-23 13:22:00 +03:00
Sergey Kandaurov
8c3ff38e77 README: updated path after moving QUIC sources. 2021-06-25 12:41:58 +03:00
Sergey Kandaurov
d674a2345d QUIC: fixed double memzero of new frames in ngx_quic_alloc_frame(). 2021-06-21 12:47:46 +03:00
Sergey Kandaurov
3090dcd82d QUIC: compact initial secrets table. 2021-06-17 12:35:38 +03:00
Sergey Kandaurov
e6f8a9447f QUIC: using compile time block/iv length for tokens.
Reference values can be found in RFC 3602, 2.1, 2.4.
2021-06-16 18:03:33 +03:00
Sergey Kandaurov
c15dc73439 QUIC: optimized initial secrets key length computation.
AES-128 key length is known in compile time.
2021-06-16 17:55:57 +03:00
Sergey Kandaurov
4e89fd3673 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.
2021-06-16 17:54:21 +03:00
Sergey Kandaurov
68bb1f1aa9 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.
2021-06-16 17:53:18 +03:00
Sergey Kandaurov
8d69124e36 QUIC: updated specification references.
This includes updating citations and further clarification.
2021-06-16 11:55:12 +03:00
Roman Arutyunyan
a3b881bf6c HTTP/3: client GOAWAY support. 2021-06-11 13:24:24 +03:00
Roman Arutyunyan
5ad55a50c9 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.
2021-06-11 12:11:08 +03:00
Roman Arutyunyan
7d9375245d 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.
2021-06-11 10:56:51 +03:00
Vladimir Homutov
03dd5599cd QUIC: improved errors readability. 2021-06-10 23:17:51 +03:00
Vladimir Homutov
03f59dd9f9 QUIC: persistent congestion calculation.
According to RFC 9002 (quic-recovery) 7.6.
2021-06-09 15:11:43 +03:00
Roman Arutyunyan
cd7eb8fb5c 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.
2021-06-07 10:12:46 +03:00
Sergey Kandaurov
1c80b63a7e 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().
2021-06-01 12:02:08 +03:00
Sergey Kandaurov
7181dd0571 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.
2021-06-01 11:41:38 +03:00
Sergey Kandaurov
5572c38e66 HTTP/3: removed $http3 that served its purpose.
To specify final protocol version by hand:

    add_header Alt-Svc h3=":443";
2021-05-31 11:54:47 +03:00
Sergey Kandaurov
6fae135f9a README: updated after QUIC RFC publication, nginx 1.21 rebase. 2021-05-28 13:45:09 +03:00
Sergey Kandaurov
a3e072bf8b Merged with the default branch. 2021-05-28 13:33:08 +03:00
Sergey Kandaurov
1d599c463a HTTP/3: fixed Insert With Name Reference index processing.
Based on a patch by Zhiyong Sun.
2021-05-27 13:29:00 +03:00
Roman Arutyunyan
adca608327 QUIC: call stream read handler on new data arrival.
This was broken in b3f6ad181df4.
2021-05-26 13:07:06 +03:00
Roman Arutyunyan
b5dab0b4c3 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.
2021-05-25 16:41:59 +03:00
Roman Arutyunyan
f3b1e134a9 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.
2021-05-25 13:55:12 +03:00
Sergey Kandaurov
ee4e5edf35 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.
2021-05-22 18:40:45 +03:00
Roman Arutyunyan
69441d941a 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.
2021-05-18 18:17:25 +03:00
Roman Arutyunyan
49e955c402 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.
2021-05-05 17:15:20 +03:00
Sergey Kandaurov
5426ba6412 QUIC: simplified sending 1-RTT only frames. 2021-05-05 19:32:49 +03:00
Vladimir Homutov
b775ee18c1 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.
2021-05-05 18:11:55 +03:00
Vladimir Homutov
3412185e83 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".
2021-05-06 12:36:14 +03:00
Roman Arutyunyan
c0766c7e56 HTTP/3: clean up table from session cleanup handler.
Previously table had a separate cleanup handler.
2021-04-28 11:30:27 +03:00
Roman Arutyunyan
c79e86564d HTTP/3: moved session initialization to a separate file.
Previously it was in ngx_http_v3_streams.c, but it's unrelated to streams.
2021-05-05 15:15:48 +03:00
Roman Arutyunyan
2afd4c612e HTTP/3: separate header files for existing source files. 2021-05-05 15:09:23 +03:00
Roman Arutyunyan
a830f1da3b 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.
2021-05-05 15:00:17 +03:00
Roman Arutyunyan
65121fd06d 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().
2021-04-27 21:32:50 +03:00
Roman Arutyunyan
193e598f14 HTTP/3: renamed ngx_http_v3_connection_t to ngx_http_v3_session_t. 2021-05-05 12:54:10 +03:00
Roman Arutyunyan
968daa6a69 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.
2021-05-05 14:53:36 +03:00
Roman Arutyunyan
041da3d5a7 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.
2021-04-30 19:10:11 +03:00
Roman Arutyunyan
c3a7f51abf HTTP/3: moved Stream Cancellation stub to ngx_http_v3_streams.c. 2021-05-05 15:15:17 +03:00
Roman Arutyunyan
c6227e3ae2 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.
2021-05-04 13:38:59 +03:00
Roman Arutyunyan
f4175aae92 HTTP/3: reject empty DATA and HEADERS frames on control stream.
Previously only non-empty frames were rejected.
2021-05-05 13:28:05 +03:00
Vladimir Homutov
55e67f217a QUIC: fixed build with NGX_QUIC_DEBUG_ALLOC enabled. 2021-04-28 13:37:18 +03:00
Vladimir Homutov
543d528973 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.
2021-04-29 15:35:02 +03:00
Vladimir Homutov
c257acfe21 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.
2021-04-22 13:49:18 +03:00
Roman Arutyunyan
26ce781c5b 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).
2021-04-19 17:25:56 +03:00
Roman Arutyunyan
91367fa2e1 QUIC: renamed stream field from c to connection. 2021-04-19 17:21:07 +03:00
Sergey Kandaurov
d8d491bf6a 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.
2021-04-16 23:03:59 +03:00
Vladimir Homutov
977a00685d QUIC: added missing checks for limits in stream frames parsing. 2021-04-19 09:46:37 +03:00
Vladimir Homutov
4f66362f15 QUIC: fixed parsing of unknown frame types.
The ngx_quic_frame_allowed() function only expects known frame types.
2021-04-19 11:36:41 +03:00
Vladimir Homutov
b49ac616a9 QUIC: avoid sending extra frames in case of error. 2021-04-15 12:17:19 +03:00
Sergey Kandaurov
30de7c444d 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.
2021-04-13 12:38:34 +03:00
Sergey Kandaurov
cab188a0a1 QUIC: ngx_quic_frames_stream_t made opaque. 2021-04-13 11:49:52 +03:00
Vladimir Homutov
0d1149dce5 QUIC: separate files for SSL library interfaces. 2021-04-14 14:47:04 +03:00
Vladimir Homutov
47575035a0 QUIC: separate files for tokens related processing. 2021-04-13 14:41:52 +03:00
Vladimir Homutov
a737b266cb QUIC: separate files for output and ack related processing. 2021-04-13 14:41:20 +03:00
Vladimir Homutov
ef1bf4102f QUIC: separate files for stream related processing. 2021-04-13 14:40:00 +03:00
Vladimir Homutov
062d66b818 QUIC: separate files for frames related processing. 2021-04-13 14:38:46 +03:00
Vladimir Homutov
4106995b34 QUIC: separate files for connection id related processing. 2021-04-13 14:37:41 +03:00
Vladimir Homutov
0da176b67b 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.
2021-04-14 14:47:37 +03:00
Vladimir Homutov
32244fabef 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.
2021-04-09 11:33:10 +03:00
Vladimir Homutov
27968b8458 QUIC: fixed ngx_quic_send_ack_range() function.
Created frame was not added to the output queue.
2021-04-07 13:09:26 +03:00
Vladimir Homutov
c182a5db6c QUIC: fixed debug message macro. 2021-04-05 11:35:46 +03:00
Vladimir Homutov
3cc73c257d 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.
2021-04-05 11:31:03 +03:00
Sergey Kandaurov
73b9640ea3 HTTP/3: keepalive_time support. 2021-04-16 19:42:03 +03:00
Sergey Kandaurov
df562f3cb1 Merged with the default branch. 2021-04-16 19:35:55 +03:00
Sergey Kandaurov
ecbde796e8 HTTP/3: removed h3scf->quic leftover after 0d2b2664b41c. 2021-04-12 12:30:30 +03:00
Sergey Kandaurov
43052283fe QUIC: fixed memory leak in ngx_hkdf_extract()/ngx_hkdf_expand().
This fixes leak on successful path when built with OpenSSL.
2021-04-07 15:14:41 +03:00
Vladimir Homutov
27b8e164cb QUIC: PATH_CHALLENGE frame creation. 2021-03-23 11:58:43 +03:00
Vladimir Homutov
c994e056f8 QUIC: distinct files for connection migration.
The connection migration-related code from quic.c with dependencies is moved
into separate file.
2021-03-31 14:57:15 +03:00
Vladimir Homutov
82f778119b QUIC: separate header for ngx_quic_connection_t. 2021-03-31 14:56:16 +03:00
Vladimir Homutov
624f1ea5c9 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.
2021-04-02 11:31:37 +03:00
Vladimir Homutov
05ea5ebae9 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.
2021-04-02 18:58:19 +03:00
Vladimir Homutov
da2a0632dd 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.
2021-03-30 14:33:43 +03:00
Roman Arutyunyan
7cac9a6096 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.
2021-03-30 14:33:47 +03:00
Roman Arutyunyan
3e4aca7509 QUIC: do not reallocate c->sockaddr.
When a connection is created, enough memory is allocated to accomodate
any future address change.
2021-03-11 15:22:18 +03:00
Roman Arutyunyan
5719790dcd 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.
2021-03-11 15:25:11 +03:00
Sergey Kandaurov
65beb99539 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
2021-03-31 21:43:17 +03:00
Sergey Kandaurov
5fa81bbd88 Merged with the default branch. 2021-03-30 23:34:51 +03:00
Roman Arutyunyan
9548c57590 HTTP/3: fixed $connection_requests.
Previously, the value was always "1".
2021-03-15 16:25:54 +03:00
Roman Arutyunyan
227afaea32 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.
2021-03-22 15:51:14 +03:00
Roman Arutyunyan
529409c0a0 HTTP/3: keepalive timeout.
This timeout limits the time when no client request streams exist.
2021-03-30 16:48:38 +03:00
Roman Arutyunyan
6b36b11167 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.
2021-03-15 16:39:33 +03:00
Roman Arutyunyan
67c58e696f 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.
2021-03-15 19:26:04 +03:00
Vladimir Homutov
0502cd8079 Core: fixed build with BPF on non-64bit platforms (ticket #2152). 2021-03-23 10:58:18 +03:00
Vladimir Homutov
fca753b4ed QUIC: bpf code regenerated. 2021-03-16 18:17:25 +03:00
Vladimir Homutov
79d71916a4 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 <gaoyan09@baidu.com>.
2021-03-15 19:05:38 +03:00
Sergey Kandaurov
e1aca15496 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.
2021-03-16 13:48:29 +03:00
Sergey Kandaurov
141d5113a5 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
2021-03-16 13:48:28 +03:00
Sergey Kandaurov
1aea7657b4 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
2021-03-16 13:48:28 +03:00
Vladimir Homutov
3603fc6b23 QUIC: added error handling to ngx_hkdf_extract()/ngx_hkdf_expand().
The OpenSSL variant of functions lacked proper error processing.
2021-03-11 14:43:01 +03:00
Sergey Kandaurov
9e0943cf32 HTTP/3: fixed server push. 2021-03-10 17:56:34 +03:00
Sergey Kandaurov
6757269a6f Merged with the default branch. 2021-03-10 15:39:01 +03:00
Sergey Kandaurov
8dfe492b99 README: http3_max_field_size was removed in ae2e68f206f9. 2021-03-07 00:23:25 +03:00
Sergey Kandaurov
efd5719654 README: bump browsers' version after 81bb3a690c10 (old drafts rip). 2021-03-07 00:23:23 +03:00
Sergey Kandaurov
0ba0a2d785 Updated the list of supported drafts. 2021-02-19 17:27:41 +03:00
Sergey Kandaurov
dc0b6961ad QUIC: multiple versions support.
Draft-29 and beyond are now supported simultaneously, no need to recompile.
2021-02-19 17:27:19 +03:00
Sergey Kandaurov
f8942c14dc QUIC: removed support prior to draft-29. 2021-02-18 19:21:09 +03:00
Roman Arutyunyan
d047870046 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.
2021-02-18 12:22:28 +03:00
Roman Arutyunyan
f9f6ded228 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.
2021-02-17 11:58:32 +03:00
Roman Arutyunyan
e33795e354 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.
2021-02-17 15:56:34 +03:00
Roman Arutyunyan
189d24c21b HTTP/3: removed http3_max_field_size.
Instead, size of one large_client_header_buffers buffer is used.
2021-02-16 18:50:01 +03:00
Sergey Kandaurov
9e38ab3ab3 Merged with the default branch. 2021-02-17 14:48:35 +03:00
Sergey Kandaurov
06a6c572d0 QUIC: added ability to reset a stream. 2021-02-17 14:25:07 +03:00
Sergey Kandaurov
6453aafa2c QUIC: fixed indentation. 2021-02-15 14:54:28 +03:00
Vladimir Homutov
ea84e91474 QUIC: added check of client transport parameters.
Parameters sent by client are verified and defaults are set for parameters
omitted by client.
2021-02-15 14:05:46 +03:00
Vladimir Homutov
966db12d9f 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.
2021-02-08 20:48:25 +03:00
Vladimir Homutov
d141dfcc05 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.
2021-02-10 14:10:14 +03:00
Roman Arutyunyan
9843c3f980 QUIC: send PING frames on PTO expiration.
Two PING frames are sent per level that generate two UDP datagrams.
2021-02-12 14:51:53 +03:00
Roman Arutyunyan
efe90aef7e 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
2021-02-12 14:40:33 +03:00
Roman Arutyunyan
1f86c95429 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.
2021-02-04 20:39:47 +03:00
Roman Arutyunyan
a61a82ba3b QUIC: fixed logging ACK frames.
Previously, the wrong end pointer was used, which could lead to errors
"quic failed to parse ack frame gap".
2021-02-09 14:31:36 +03:00
Vladimir Homutov
3cc96b7a82 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.
2021-02-08 16:49:33 +03:00
Roman Arutyunyan
9927fc017c QUIC: use server ack_delay_exponent when sending ack.
Previously, client one was used.
2021-02-04 14:35:36 +03:00
Sergey Kandaurov
217682b911 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.
2021-02-03 12:39:41 +03:00
Roman Arutyunyan
7b7c606971 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.
2021-02-02 15:09:48 +03:00
Roman Arutyunyan
e12f2cbfbc HTTP/3: fixed format specifier. 2021-02-01 18:48:18 +03:00
Roman Arutyunyan
b56ed96a16 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.
2021-01-25 16:16:47 +03:00
Roman Arutyunyan
8ac8479b5e 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.
2021-02-01 14:46:36 +03:00
Roman Arutyunyan
b3b99f89e8 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
2021-01-28 12:35:18 +03:00
Vladimir Homutov
d58bbb8942 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.
2021-01-29 15:53:47 +03:00
Roman Arutyunyan
a1810c53c0 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.
2021-01-29 19:42:47 +03:00
Sergey Kandaurov
3018ec12aa README: reflect renaming of several transport parameter directives.
Reported by Kyriakos Zarifis.
2021-01-27 13:09:45 +03:00
Roman Arutyunyan
54694b3165 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.
2020-12-21 17:35:13 +00:00
Roman Arutyunyan
4fe4a1be5e 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.
2021-01-18 13:43:36 +03:00
Roman Arutyunyan
979b89029f HTTP/3: added comment. 2021-01-12 21:08:55 +00:00
Roman Arutyunyan
51d921cf70 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
2021-01-22 15:57:41 +03:00
Roman Arutyunyan
50430c7e1d 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.
2021-01-22 16:34:06 +03:00
Sergey Kandaurov
feb160c9a8 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.
2021-01-11 15:25:48 +03:00
Vladimir Homutov
de172f3c85 QUIC: fixed header protection macro name. 2020-12-30 20:47:35 +03:00
Vladimir Homutov
5a3c80e12a 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.
2020-12-25 15:01:15 +03:00
Vladimir Homutov
ff201f3fe3 Core: added interface to linux bpf() system call.
It contains wrappers for operations with BPF maps and for loading BPF programs.
2020-12-15 15:23:07 +03:00
Vladimir Homutov
ca5b8fcf8e QUIC: ngx_quic_module. 2020-12-25 14:18:51 +03:00
Vladimir Homutov
b16ca606b1 QUIC: moved all quic sources into src/event/quic. 2020-12-25 14:01:28 +03:00
Sergey Kandaurov
e210134561 QUIC: removed unused <openssl/aes.h> inclusion.
The low-level API was used in early QUIC development.
2020-12-22 16:41:56 +03:00
Sergey Kandaurov
fd9e51d00b QUIC: fixed -Wtype-limits with GCC <= 5 (ticket #2104). 2020-12-22 12:04:16 +03:00
Sergey Kandaurov
7378eed63f QUIC: fixed logging PATH_CHALLENGE/RESPONSE and build with GCC < 5. 2020-12-22 12:04:15 +03:00
Sergey Kandaurov
42780e0edc QUIC: fixed building ALPN callback without debug and http2. 2020-12-22 12:04:15 +03:00
Sergey Kandaurov
b4d3563ff8 QUIC: fixed build with OpenSSL < 1.1.1.
The <openssl/kdf.h> 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.
2020-12-22 12:03:43 +03:00
Sergey Kandaurov
33b0a8f597 QUIC: converted to SSL_CIPHER_get_protocol_id().
This API is available in BoringSSL for quite some time:
https://boringssl.googlesource.com/boringssl/+/3743aaf
2020-12-21 15:05:43 +03:00
Sergey Kandaurov
b9f5a4b8d3 HTTP/3: staticize internal parsing functions. 2020-12-16 12:47:41 +00:00
Sergey Kandaurov
ebac406f83 HTTP/3: staticize ngx_http_v3_methods. 2020-12-16 12:47:38 +00:00
Sergey Kandaurov
0767813576 Merged with the default branch. 2020-12-15 16:55:43 +00:00
Roman Arutyunyan
37494c700d 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.
2020-12-09 21:26:21 +00:00
Roman Arutyunyan
9712e7c84d 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.
2020-12-10 14:54:53 +00:00
Roman Arutyunyan
87ed36b6e5 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.
2020-12-09 16:15:24 +00:00
Roman Arutyunyan
ba527cd792 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.
2020-12-08 17:10:22 +00:00
Roman Arutyunyan
cffdf76263 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.
2020-12-08 14:44:41 +00:00
Roman Arutyunyan
819ca27d64 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.
2020-12-07 15:09:08 +00:00
Roman Arutyunyan
73a64aa9eb QUIC: introduced QUIC buffers.
Buffers are used to hold frame data.  They have a fixed size and are reused
after being freed.
2020-12-01 19:11:01 +00:00
Vladimir Homutov
0fdfd7f7fa 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.
2020-12-07 14:06:00 +03:00
Sergey Kandaurov
2a92fecbf9 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.
2020-12-06 14:24:38 +00:00
Vladimir Homutov
03a273fe76 QUIC: fixed missing quic flag on listener in the stream module. 2020-12-04 15:19:03 +03:00
Roman Arutyunyan
dc5ab4196f 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.
2020-11-27 17:46:21 +00:00
Vladimir Homutov
8402a8068a QUIC: fixed send contexts cleanup.
The ngx_quic_get_send_ctx() macro takes 'level' argument, not send context
index.
2020-12-02 10:55:49 +03:00
Vladimir Homutov
5439bfc399 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.
2020-11-27 18:43:36 +03:00
Roman Arutyunyan
dce8a2f3d2 HTTP/3: eliminated r->method_start.
The field was introduced to ease parsing HTTP/3 requests.

The change reduces diff to the default branch.
2020-11-25 17:57:43 +00:00
Roman Arutyunyan
9da2167d27 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.
2020-11-17 20:54:10 +00:00
Roman Arutyunyan
0393266e5f 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.
2020-11-17 21:12:36 +00:00
Sergey Kandaurov
ccbbe4b470 Merged with the default branch. 2020-11-24 17:19:40 +00:00
Sergey Kandaurov
660cbf2a61 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
2020-11-18 20:56:11 +00:00
Sergey Kandaurov
c4bbc9c1d0 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.
2020-11-17 21:33:16 +00:00
Sergey Kandaurov
adc7d2d3f9 QUIC: merged create_long/short_packet() functions.
They no longer differ.
2020-11-17 21:33:12 +00:00
Sergey Kandaurov
4532fb0f3f 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.
2020-11-17 21:32:22 +00:00
Sergey Kandaurov
c61ad80d7e 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.
2020-11-17 21:32:06 +00:00
Sergey Kandaurov
bb47c3dfe4 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.
2020-11-17 21:32:04 +00:00
Sergey Kandaurov
ea0e574048 QUIC: sorted header parsing functions in order of appearance.
No functional changes.
2020-11-17 21:31:51 +00:00
Sergey Kandaurov
7565da40d3 QUIC: removed macros for stream limits unused since c5324bb3a704. 2020-11-17 12:22:24 +00:00
Sergey Kandaurov
f7fe85c087 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.
2020-11-13 15:11:29 +00:00
Sergey Kandaurov
f1094694ff Core: reduced diff to the default branch.
It became feasible to reduce after feec2cc762f6 that
removes ngx_quic_connection_t from ngx_connection_s.
2020-11-13 15:11:27 +00:00
Sergey Kandaurov
be8e1f536e QUIC: microoptimization in varint parsing.
Removed a useless mask from the value being shifted, since it is 1-byte wide.
2020-11-13 13:24:45 +00:00
Roman Arutyunyan
6d48d90763 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.
2020-11-10 20:42:45 +00:00
Roman Arutyunyan
f635285c28 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.
2020-11-11 21:08:48 +00:00
Roman Arutyunyan
67342b0eb2 QUIC: removed comment. 2020-11-11 19:40:41 +00:00
Roman Arutyunyan
0e146ed3d4 QUIC: added quic_stateless_reset_token_key Stream directive.
A similar directive is already available in HTTP.
2020-11-11 19:39:23 +00:00
Roman Arutyunyan
167b65f656 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.
2020-11-11 17:56:02 +00:00
Roman Arutyunyan
e7985ce0ab QUIC: renamed c->qs to c->quic. 2020-11-10 19:40:00 +00:00
Roman Arutyunyan
2722ff0de4 QUIC: got rid of the c->quic field.
Now QUIC connection is accessed via the c->udp field.
2020-11-10 18:38:42 +00:00
Roman Arutyunyan
168b097cbf QUIC: connection multiplexing per port.
Also, connection migration within a single worker is implemented.
2020-11-11 11:57:50 +00:00
Roman Arutyunyan
a601bbdf44 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().
2020-11-09 18:58:29 +00:00
Sergey Kandaurov
cb43caba9d 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.
2020-11-10 00:32:56 +03:00
Sergey Kandaurov
4f6e91f0c9 QUIC: multiple versions support.
Draft-29 and beyond are now treated as compatible versions.
2020-11-10 00:23:04 +03:00
Sergey Kandaurov
b7f2dde342 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.
2020-11-10 00:20:44 +03:00
Vladimir Homutov
211af3d876 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'.
2020-11-06 18:21:31 +03:00
Sergey Kandaurov
4598902e81 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.
2020-11-02 17:38:11 +00:00
Sergey Kandaurov
e9ddd91457 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.
2020-11-02 18:21:34 +03:00
Sergey Kandaurov
3e522efdf7 QUIC: refactored SSL_do_handshake() handling.
No functional changes.
2020-10-29 21:50:49 +00:00
Sergey Kandaurov
af8c42f6e3 QUIC: passing ssl_conn to SSL_get0_alpn_selected() directly.
No functional changes.
2020-10-29 21:50:19 +00:00
Sergey Kandaurov
45cec3fc53 Merged with the default branch. 2020-10-29 14:53:58 +00:00
Roman Arutyunyan
e958f103b2 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.
2020-10-29 14:25:02 +00:00
Vladimir Homutov
716d09c8c0 QUIC: avoided retransmission of stale ack frames.
Acknowledgments are regenerated using the most recent data available.
2020-10-28 14:22:51 +03:00
Roman Arutyunyan
de8e89aacb QUIC: changed STREAM frame debugging. 2020-10-27 18:21:36 +00:00
Roman Arutyunyan
3c7a646b3a 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.
2020-10-28 09:15:04 +00:00
Roman Arutyunyan
ea3987c5ae 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.
2020-10-27 13:24:00 +00:00
Vladimir Homutov
949eec2c5f 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().
2020-10-27 00:14:24 +03:00
Vladimir Homutov
d3480a33f5 QUIC: added push event afer the address was validated.
This allows to continue processing when the anti-amplification limit was hit.
2020-10-27 00:00:56 +03:00
Vladimir Homutov
ed32475c7e 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
2020-10-26 23:58:34 +03:00
Vladimir Homutov
f6692368a8 QUIC: got rid of "pkt" abbreviation in logs. 2020-10-26 23:47:49 +03:00
Vladimir Homutov
2f353e67ee QUIC: added "rx" and "tx" prefixes to packet debug. 2020-10-26 23:47:16 +03:00
Vladimir Homutov
1a6f6bfd26 QUIC: added connection state debug to event handlers. 2020-10-26 23:17:54 +03:00
Vladimir Homutov
1190d9c21c QUIC: added logging of a declined packet without retry token. 2020-10-26 00:34:24 +03:00
Vladimir Homutov
ab43d69d98 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.
2020-10-27 14:12:31 +03:00
Vladimir Homutov
3c4e0cfc22 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.
2020-10-27 14:32:08 +03:00
Vladimir Homutov
aac3894fb6 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.
2020-10-23 17:08:50 +03:00
Vladimir Homutov
cba0f87bfa QUIC: added missing "quic" prefix in debug messages. 2020-10-23 18:22:01 +03:00
Sergey Kandaurov
41510b9c70 QUIC: updated README.
- ACK ranges are implemented
 - up to draft-32 is now supported
 - removed mentions of early alpha quality and further cleanup
2020-10-22 12:55:15 +01:00
Sergey Kandaurov
b041c03f09 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.
2020-10-22 11:05:50 +01:00
Vladimir Homutov
c6693e556f QUIC: fixed dropping output ack ranges on input ack.
While there, additional debug messages were added.
2020-10-21 20:39:25 +03:00
Vladimir Homutov
718bfcae4d QUIC: added macro for unset packet number. 2020-10-21 18:44:25 +03:00
Vladimir Homutov
982d7b7bff 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.
2020-10-20 18:53:25 +03:00
Vladimir Homutov
0a4c1a3fa4 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.
2020-10-20 18:53:00 +03:00
Sergey Kandaurov
4279c3cc87 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.
2020-10-21 12:46:23 +01:00
Sergey Kandaurov
8b3023ea6b QUIC: teach how to compute only the length of created QUIC headers.
It will be used for precise expansion of UDP datagram payload.
2020-10-21 12:03:23 +01:00
Sergey Kandaurov
da44036046 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.
2020-10-21 12:03:22 +01:00
Sergey Kandaurov
a5f7f2f2a1 QUIC: avoided excessive initialization in ngx_quic_send_frames().
A zero-length token was used to initialize a prezeroed packet header.
2020-10-21 12:03:22 +01:00
Sergey Kandaurov
c7bada2ea9 QUIC: sorted ngx_quic_send_frames() declarations. 2020-10-21 12:03:21 +01:00
Vladimir Homutov
dc12426a87 QUIC: account packet header length in amplification limit.
This is the restoration of 02ee77f8d53d accidentally reverted by 93be5658a250.
2020-10-19 12:19:38 +03:00
Vladimir Homutov
25c51c105c QUIC: reverted previous 3 commits.
Changes were intended for the test repository.
2020-10-19 10:32:53 +03:00
Vladimir Homutov
32830dcac9 try: --skiptests 2020-10-19 10:10:21 +03:00
Vladimir Homutov
498f03e9d4 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.
2020-10-14 23:21:36 +03:00
Vladimir Homutov
dee178452f SSL: added the "ssl_keys_file" directive. 2020-09-15 22:44:46 +03:00
Vladimir Homutov
42b39fd65a QUIC: account packet header length in amplification limit.
Header length calculation is adjusted to account real connection id lengths
instead of worst case.
2020-10-15 11:37:01 +03:00
Sergey Kandaurov
545241eb55 QUIC: fixed ngx_http_upstream_init() much like HTTP/2 connections. 2020-10-12 14:00:00 +01:00
Vladimir Homutov
028185db49 QUIC: reset error and error_reason prior to processing packet. 2020-10-09 16:57:19 +03:00
Sergey Kandaurov
d91ea78765 QUIC: fixed dead store assignment.
Found by Clang Static Analyzer.
2020-10-07 14:51:05 +01:00
Vladimir Homutov
31cee96bde QUIC: fixed format specifier in debug message. 2020-10-07 15:29:23 +03:00
Vladimir Homutov
836007bb35 QUIC: added debug message with final packet processing status. 2020-10-02 16:20:41 +03:00
Roman Arutyunyan
56da17436a 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.
2020-10-07 12:24:03 +01:00
Vladimir Homutov
8b94732aa4 QUIC: enabled more key-related debug by default. 2020-10-02 12:40:49 +03:00
Vladimir Homutov
0e47f4bfa5 QUIC: added connection id debug. 2020-10-02 12:56:34 +03:00
Vladimir Homutov
7beae31be5 QUIC: updated c->log->action strings to reflect proper state. 2020-10-07 13:38:17 +03:00
Vladimir Homutov
5de89a7d8c QUIC: fixed memory leak in ngx_quic_send_frames().
The function did not free passed frames in case of error.
2020-10-07 10:14:02 +03:00
Sergey Kandaurov
236b7fb58b QUIC: fixed measuring ACK Delay against 0-RTT packets. 2020-10-06 18:08:55 +01:00
Sergey Kandaurov
1c5823b107 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.
2020-10-05 13:02:53 +01:00
Sergey Kandaurov
6eec0e2364 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.
2020-10-05 13:02:38 +01:00
Vladimir Homutov
d4515820cf 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".
2020-10-05 14:36:17 +03:00
Vladimir Homutov
d88396734b QUIC: inline function instead of macro for hexdump.
This prevents name clashes with local variables.
2020-10-05 10:03:01 +03:00
Vladimir Homutov
6c644adb0e QUIC: fixed handling of incorrect packets.
Instead of ignoring, connection was closed. This was broken in d0d3fc0697a0.
2020-10-01 22:20:51 +03:00
Sergey Kandaurov
52172fc8d9 Merged with the default branch. 2020-10-01 12:21:11 +01:00
Sergey Kandaurov
7499800d7f QUIC: a bandaid for calculating ack_delay with non-monotonic time. 2020-10-01 12:10:37 +01:00
Sergey Kandaurov
85086a5267 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.
2020-10-01 12:10:22 +01:00
Sergey Kandaurov
1fca2f6698 QUIC: fixed clang-ast asserts. 2020-10-01 12:09:47 +01:00
Sergey Kandaurov
e8b61e9b4c QUIC: fixed build with OpenSSL after bed310672f39. 2020-10-01 12:00:12 +01:00
Vladimir Homutov
64b3828e19 QUIC: moved ssl configuration pointer to quic configuration.
The ssl configuration is obtained at config time and saved for future use.
2020-10-01 10:04:35 +03:00
Vladimir Homutov
5bd6c60156 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";
    ...
}
2020-09-30 20:54:46 +03:00
Vladimir Homutov
fc0cbbee53 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.
2020-09-30 21:27:52 +03:00
Vladimir Homutov
19c1c5f206 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
2020-09-30 15:14:09 +03:00
Vladimir Homutov
5b112f3ad6 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.
2020-09-25 21:47:28 +03:00
Roman Arutyunyan
d54717995e QUIC: keep the entire packet size in pkt->len.
Previously pkt->len kept the length of the packet remainder starting from
pkt->raw->pos.
2020-09-25 21:46:55 +03:00
Vladimir Homutov
edd2c9f3e2 QUIC: switched to using fixed-length server connection IDs. 2020-09-18 15:53:37 +03:00
Roman Arutyunyan
a1c43297d9 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.
2020-09-30 20:23:16 +01:00
Roman Arutyunyan
771d716bdb 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.
2020-09-29 22:09:09 +01:00
Sergey Kandaurov
6137585b59 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.
2020-09-23 13:13:04 +01:00
Vladimir Homutov
5b904ab35b 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.
2020-09-21 13:58:17 +03:00
Roman Arutyunyan
86c79ad10e HTTP/3: rearranged length check when parsing header.
The new code looks simpler and is similar to other checks.
2020-09-16 20:21:03 +01:00
Roman Arutyunyan
5dcfdc596c HTTP/3: removed HTTP/3 parser call from discard body filter.
Request body discard is disabled for QUIC streams anyway.
2020-09-16 19:48:33 +01:00
Roman Arutyunyan
a5a7072e05 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.
2020-09-16 12:27:23 +01:00
Roman Arutyunyan
1c1960e839 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.
2020-08-24 09:56:36 +03:00
Roman Arutyunyan
bf3f6ca721 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.
2020-09-16 18:59:25 +01:00
Vladimir Homutov
160bfe5969 QUIC: switched to draft 29 by default. 2020-09-11 10:56:05 +03:00
Roman Arutyunyan
3a60eda160 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.
2020-09-09 16:35:29 +03:00
Roman Arutyunyan
b39e287f48 QUIC: eliminated idle timeout restart for dropped packets. 2020-09-08 15:54:02 +03:00
Sergey Kandaurov
1876298d26 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.
2020-09-08 13:35:50 +03:00
Sergey Kandaurov
23360e59fc 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.
2020-09-08 13:28:56 +03:00
Sergey Kandaurov
a3d0d3aa18 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.
2020-09-08 13:27:39 +03:00
Roman Arutyunyan
90b249a639 QUIC: added logging output stream frame offset. 2020-09-07 20:55:36 +03:00
Vladimir Homutov
a9144afcee 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().
2020-09-04 15:48:53 +03:00
Roman Arutyunyan
a36b8068c0 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.
2020-09-06 14:51:23 +03:00
Vladimir Homutov
a157dea810 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.
2020-09-03 13:11:27 +03:00
Vladimir Homutov
0eded5126d QUIC: style.
Moved processing of RETIRE_CONNECTION_ID right after the NEW_CONNECTION_ID.
2020-08-27 10:15:37 +03:00
Vladimir Homutov
4534a09448 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.
2020-09-02 22:34:15 +03:00
Vladimir Homutov
f2efcf7755 QUIC: discard unrecognized long packes.
While there, updated comment about discarded packets.
2020-09-02 09:54:15 +03:00
Roman Arutyunyan
e5bc0f12a2 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.
2020-08-31 18:42:26 +03:00
Vladimir Homutov
364dcdd9c5 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.
2020-09-01 17:20:42 +03:00
Roman Arutyunyan
84b9ab590a 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.
2020-09-01 15:21:49 +03:00
Roman Arutyunyan
96c1fd6c69 QUIC: handle PATH_CHALLENGE frame.
A PATH_RESPONSE frame with the same data is sent in response.
2020-08-28 12:01:35 +03:00
Roman Arutyunyan
179a9aa334 QUIC: enforce flow control on incoming STREAM and CRYPTO frames. 2020-08-25 17:22:57 +03:00
Roman Arutyunyan
0001eb2ce2 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.
2020-08-25 12:45:21 +03:00
Roman Arutyunyan
037128d3f5 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.
2020-08-25 14:07:26 +03:00
Vladimir Homutov
a6cff7c24d QUIC: updated README.
- version negotiation is implemented
 - quic recovery implementation is greatly improved
2020-08-21 14:55:32 +03:00
Sergey Kandaurov
b9b0e8f5d7 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.
2020-08-21 14:41:42 +03:00
Sergey Kandaurov
bd1d3532bf 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.
2020-08-21 14:41:41 +03:00
Vladimir Homutov
9ec291891f QUIC: dead code removed.
This case was already handled in c70446e3d771.
2020-08-21 10:00:25 +03:00
Vladimir Homutov
1748c01703 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.
2020-08-20 16:45:48 +03:00
Vladimir Homutov
0846ce51bb 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).
2020-08-20 17:11:04 +03:00
Roman Arutyunyan
f3006e0d43 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.
2020-08-20 12:33:00 +03:00
Roman Arutyunyan
301bf01f06 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.
2020-08-18 17:23:16 +03:00
Sergey Kandaurov
2021aa19fb QUIC: fixed format specifiers. 2020-08-19 16:00:12 +03:00
Sergey Kandaurov
3c988540d9 QUIC: changed c->quic->pto_count type to ngx_uint_t.
This field is served as a simple counter for PTO backoff.
2020-08-19 15:58:03 +03:00
Sergey Kandaurov
a62392bdd7 QUIC: do not artificially delay sending queued frames.
This interacts badly with retransmissions of lost packets
and can provoke spurious client retransmits.
2020-08-19 13:24:54 +03:00
Sergey Kandaurov
b64d9073fd QUIC: do not arm loss detection timer on packet threshold. 2020-08-19 13:24:53 +03:00
Sergey Kandaurov
f53fe3e070 QUIC: do not arm loss detection timer for succeeding packets. 2020-08-19 13:24:47 +03:00
Sergey Kandaurov
7b29edad30 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.
2020-08-19 13:24:30 +03:00
Sergey Kandaurov
8402cb2f3e 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.
2020-08-19 13:24:23 +03:00
Sergey Kandaurov
89121eccf7 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.
2020-08-18 23:33:40 +03:00
Sergey Kandaurov
5ec053cab6 HTTP/3: fixed context storage in request body parser. 2020-08-18 17:11:32 +03:00
Roman Arutyunyan
82df85de1a Merged with the default branch. 2020-08-18 16:22:00 +03:00
Roman Arutyunyan
64c17e86d5 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()
2020-08-18 12:28:33 +03:00
Sergey Kandaurov
706b5c3d6c QUIC: packet based bytes_in_flight accounting.
A packet size is kept in one of the frames belonging to the packet.
2020-08-14 16:54:13 +03:00
Sergey Kandaurov
c62e3d6ece QUIC: fixed leak of bytes_in_flight on keys discard.
This applies to discarding Initial and Handshake keys.
2020-08-14 16:54:06 +03:00
Sergey Kandaurov
d0e545b601 QUIC: fixed leak of bytes_in_flight attributed to lost packets. 2020-08-14 16:53:56 +03:00
Roman Arutyunyan
9bcefb54f1 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.
2020-08-03 13:31:48 +03:00
Roman Arutyunyan
a814e5049a 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.
2020-08-11 19:10:57 +03:00
Roman Arutyunyan
04d6190f86 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.
2020-08-11 10:41:39 +03:00
Sergey Kandaurov
25f8b56153 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.
2020-08-07 12:34:15 +03:00
Sergey Kandaurov
a7b7ca0e0d 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.
2020-08-07 12:34:11 +03:00
Sergey Kandaurov
54936090ee QUIC: fixed format specifiers and removed casts. 2020-07-28 18:54:20 +03:00
Sergey Kandaurov
593b3f32d2 QUIC: consistent Stream ID logging format. 2020-07-28 17:11:25 +03:00
Roman Arutyunyan
9fb0feab7d QUIC: added HTTP/3 directives list to README.
Also removed server push from TODO list.
2020-07-28 15:53:42 +03:00
Roman Arutyunyan
1cb69c22fa HTTP/3: server pushes.
New directives are added:
- http3_max_concurrent_pushes
- http3_push
- http3_push_preload
2020-07-23 13:41:24 +03:00
Roman Arutyunyan
eff039022b 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
2020-07-27 19:15:17 +03:00
Roman Arutyunyan
d6157edc80 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.
2020-07-27 18:51:42 +03:00
Roman Arutyunyan
da55aaa1f3 HTTP/3: support $server_protocol variable.
Now it holds "HTTP/3.0".  Previously it was empty.
2020-07-14 16:52:44 +03:00
Roman Arutyunyan
6ffd56a4c4 Style: moved function declarations to match usual code style.
Plus a few other minor style changes.
2020-07-23 11:40:10 +03:00
Roman Arutyunyan
c7f1f9beb8 HTTP/3: renamed server configuration variables from v3cf to h3scf.
Now they are similar to HTTP/2 where they are called h2scf.
2020-07-23 13:12:01 +03:00
Roman Arutyunyan
9d47ea2ffc HTTP/3: renamed ngx_http_v3.c to ngx_http_v3_encode.c.
The file contains only encoding functions.
2020-07-13 12:38:08 +03:00
Roman Arutyunyan
4de5e5ffbf 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.
2020-07-13 12:33:00 +03:00
Roman Arutyunyan
0676bed5c6 HTTP/3: generate Location response header for absolute redirects. 2020-07-23 12:31:40 +03:00
Roman Arutyunyan
459fd8e589 HTTP/3: header encoding functions. 2020-07-13 16:00:00 +03:00
Roman Arutyunyan
3697fe9f72 QUIC: updated README to mention "quic" listen parameter. 2020-07-22 13:45:34 +03:00
Sergey Kandaurov
9bbd65b36a 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.
2020-07-22 14:48:49 +03:00
Roman Arutyunyan
e4a574cad4 SSL: fixed compilation without QUIC after 0d2b2664b41c. 2020-07-22 13:34:48 +03:00
Roman Arutyunyan
b8698bf9d9 HTTP/3: do not call shutdown() for QUIC streams.
Previously, this triggered an alert "shutdown() failed" in error log.
2020-07-22 11:03:42 +03:00
Roman Arutyunyan
03f7cbabc1 QUIC: eliminated connection handler argument in ngx_quic_run().
Now c->listening->handler() is called instead.
2020-07-21 23:08:23 +03:00
Roman Arutyunyan
049af62328 QUIC: added "quic" listen parameter in Stream.
Also, introduced ngx_stream_quic_module.
2020-07-21 23:08:39 +03:00
Roman Arutyunyan
0c9a1fd9cc 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
2020-07-21 23:09:22 +03:00
Roman Arutyunyan
c1e4763682 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.
2020-07-18 00:08:04 +03:00
Roman Arutyunyan
fa3392dc10 QUIC: fixed stream read event log.
Previously, the main connection log was there.  Now it's the stream connection
log.
2020-07-18 00:08:29 +03:00
Sergey Kandaurov
2e5c317431 Fixed format specifiers. 2020-07-20 15:19:03 +03:00
Vladimir Homutov
288a3eef97 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.
2020-07-16 16:36:02 +03:00
Vladimir Homutov
497a37971f 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.
2020-07-16 15:02:38 +03:00
Vladimir Homutov
6771c2464b QUIC: implemented probe timeout (PTO) calculation. 2020-07-16 16:05:44 +03:00
Vladimir Homutov
eefc09faa7 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.
2020-07-13 17:31:29 +03:00
Vladimir Homutov
108baa4d33 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.
2020-07-15 15:10:17 +03:00
Vladimir Homutov
37d1ec06ef 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.
2020-07-13 10:07:15 +03:00
Vladimir Homutov
fa72cd0ce7 QUIC: caching c->quic in the ngx_quic_handle_ack_frame() function.
To minimize difference with the following changes.
2020-07-13 10:07:20 +03:00
Vladimir Homutov
36d8584477 QUIC: delay field of an ACK frame is now calculated. 2020-07-10 15:33:51 +03:00
Vladimir Homutov
2141a5e0a2 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.
2020-07-16 15:44:06 +03:00
Sergey Kandaurov
2107ce98ba Merged with the default branch. 2020-07-13 15:34:22 +03:00
Roman Arutyunyan
1258c98695 HTTP/3: simplified handling return codes from parse functions. 2020-07-02 20:07:24 +03:00
Roman Arutyunyan
4ba5f50fb2 HTTP/3: put ngx_http_v3_parse_varlen_int() return code in variable.
This makes calling this function similar to other parse functions.
2020-07-03 12:07:43 +03:00
Roman Arutyunyan
9db31cffb4 HTTP/3: simplifed handling ngx_http_v3_parse_literal() return code. 2020-07-03 12:05:05 +03:00
Roman Arutyunyan
fd81c91474 HTTP/3: limited prefixed integer size by 62 bits. 2020-07-03 09:26:12 +03:00
Roman Arutyunyan
fa9142e87f 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.
2020-07-03 16:41:31 +03:00
Sergey Kandaurov
7fa11fe28b HTTP/3: fixed prefix in decoding Section Acknowledgement. 2020-07-02 17:35:57 +03:00
Roman Arutyunyan
1e59bcec9b 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.
2020-06-30 15:32:09 +03:00
Roman Arutyunyan
df0a95b586 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.
2020-07-02 16:47:51 +03:00
Roman Arutyunyan
a3fd09e793 HTTP/3: error code definitions for HTTP/3 and QPACK. 2020-06-30 12:30:57 +03:00
Roman Arutyunyan
601c9c8886 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.
2020-07-02 16:33:59 +03:00
Roman Arutyunyan
11e75ce4bc HTTP/3: downgraded literal size error level to NGX_LOG_INFO.
Now it's similar to HTTP/2.
2020-07-02 16:33:36 +03:00
Roman Arutyunyan
8a99b8cabd 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
2020-07-02 15:34:05 +03:00
Roman Arutyunyan
902358052c HTTP/3: fixed prefixed integer encoding and decoding.
Previously bytes were ordered from MSB to LSB, but the right order is the
reverse.
2020-07-02 15:15:55 +03:00
Roman Arutyunyan
9e72031709 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.
2020-06-29 15:56:14 +03:00
Roman Arutyunyan
9eae53813f 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.
2020-06-26 11:58:00 +03:00
Roman Arutyunyan
865e4f4e16 HTTP/3: fixed dropping first non-pseudo header. 2020-06-26 10:05:28 +03:00
Sergey Kandaurov
45440e5519 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.
2020-06-25 20:31:13 +03:00
Vladimir Homutov
21ffa1fea0 Style. 2020-06-19 11:29:30 +03:00
Sergey Kandaurov
98a1c01bb6 README: documented draft-28, draft-29 support. 2020-06-23 11:57:00 +03:00
Sergey Kandaurov
a07fa1cbf4 Update Initial salt and Retry secret from quic-tls-29.
See sections 5.2 and 5.8 for the current values.
2020-06-23 11:57:00 +03:00
Sergey Kandaurov
7731665186 Get rid of hardcoded numbers used for quic handshake errors. 2020-06-23 11:57:00 +03:00
Sergey Kandaurov
8634e4d110 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.
2020-06-23 11:57:00 +03:00
Sergey Kandaurov
4bec083118 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.
2020-06-23 11:57:00 +03:00
Sergey Kandaurov
79cbb7167c Define KEY_UPDATE_ERROR from quic-tls-24. 2020-06-23 11:57:00 +03:00
Sergey Kandaurov
3c79cc18f3 Reject new QUIC connection with CONNECTION_REFUSED on shutdown. 2020-06-23 11:57:00 +03:00
Sergey Kandaurov
14b468b6ae Close QUIC connection with NO_ERROR on c->close.
That way it makes more sense.  Previously it was closed with INTERNAL_ERROR.
2020-06-23 11:57:00 +03:00
Sergey Kandaurov
75c37350c2 Do not close QUIC sockets in ngx_close_listening_sockets().
This breaks graceful shutdown of QUIC connections in terms of quic-transport.
2020-06-23 11:57:00 +03:00
Sergey Kandaurov
5e12fc0fb0 QUIC error SERVER_BUSY renamed to CONNECTION_REFUSED in draft-29. 2020-06-23 11:57:00 +03:00
Vladimir Homutov
b1628fe8de 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().
2020-06-18 14:29:24 +03:00
Vladimir Homutov
d934c2b980 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.
2020-06-18 13:58:46 +03:00
Vladimir Homutov
2a006072aa 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.
2020-06-18 11:16:35 +03:00
Vladimir Homutov
085547cfc0 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.
2020-06-16 11:54:05 +03:00
Vladimir Homutov
bd10b24229 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).
2020-06-15 17:06:40 +03:00
Vladimir Homutov
b3d75381c7 QUIC: Fixed connection cleanup.
A posted event need to be deleted during the connection close.
2020-06-15 16:59:53 +03:00
Vladimir Homutov
3cb92e42a8 Style. 2020-06-10 21:37:48 +03:00
Vladimir Homutov
e16023a735 Limited max udp payload size for outgoing packets.
This allows to avoid problems with packet fragmentation in real networks.
This is a temporary workaround.
2020-06-10 21:37:08 +03:00
Vladimir Homutov
3239735a54 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.
2020-06-10 21:33:20 +03:00
Vladimir Homutov
b5436b1bcc Fixed usage of own/client transport parameters. 2020-06-10 21:23:10 +03:00
Sergey Kandaurov
6f6e3e6e67 Stream ID handling in MAX_STREAM_DATA and STREAM_DATA_BLOCKED. 2020-06-05 20:59:27 +03:00
Sergey Kandaurov
9c1b503837 Stream ID handling in RESET_STREAM and STOP_SENDING frames. 2020-06-05 20:59:27 +03:00
Sergey Kandaurov
9922e9a4db Reject invalid STREAM ID with STREAM_STATE_ERROR connection error. 2020-06-05 20:59:26 +03:00
Sergey Kandaurov
ae303e8ef9 Introduced connection error APPLICATION_ERROR from draft-28. 2020-06-05 13:20:03 +03:00
Sergey Kandaurov
250ff7c03c Receipt of CONNECTION_CLOSE in 0-RTT is permitted in draft-28. 2020-06-05 13:20:02 +03:00
Sergey Kandaurov
894a1af249 Treat receipt of NEW_TOKEN as connection error PROTOCOL_VIOLATION. 2020-06-05 13:20:02 +03:00
Roman Arutyunyan
dfb9f8fa7b 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.
2020-06-02 15:59:14 +03:00
Sergey Kandaurov
3ff3f33fe7 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!/
2020-06-01 19:53:13 +03:00
Sergey Kandaurov
a8f6ffe53d Fixed transport parameters on a new connection with a valid token.
Previously, the retry transport parameter was sent regardless.
2020-06-01 19:16:44 +03:00
Roman Arutyunyan
fcb3cd9ae0 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.
2020-05-29 12:42:23 +03:00
Vladimir Homutov
3a1ddd803b 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.
2020-05-29 13:29:24 +03:00
Sergey Kandaurov
59a7a800e1 Made NGX_QUIC_DRAFT_VERSION tunable from configure parameters.
Now it can be switched using --with-cc-opt='-DNGX_QUIC_DRAFT_VERSION=28'.
2020-05-29 15:07:46 +03:00
Sergey Kandaurov
eefeb8ed3e 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.
2020-05-29 15:06:33 +03:00
Sergey Kandaurov
e076bf84bb Introduced macros for building length-value transport parameters. 2020-05-29 13:05:57 +03:00
Sergey Kandaurov
892adedeb8 Renamed max_packet_size to max_udp_payload_size, from draft-28.
No functional changes.
2020-05-29 12:56:08 +03:00
Sergey Kandaurov
e2570743bb Rejected forbidden transport parameters with TRANSPORT_PARAMETER_ERROR. 2020-05-29 12:55:39 +03:00
Sergey Kandaurov
a10e9100a7 Fixed return codes in ngx_quic_add_handshake_data() callback. 2020-05-29 12:50:20 +03:00
Sergey Kandaurov
8ead0f088d README: update after merging 1.19.0. 2020-05-26 20:41:43 +03:00
Sergey Kandaurov
ec7d07eac8 Merged with the default branch. 2020-05-26 20:26:44 +03:00
Vladimir Homutov
abb49cc7cc Updated README with "Contributing" section and draft details. 2020-05-25 18:37:43 +03:00
Roman Arutyunyan
bf8e1fc66f HTTP/3: reallocate strings inserted into the dynamic table.
They should always be allocated from the main QUIC connection pool.
2020-05-14 16:02:32 +03:00
Roman Arutyunyan
278d5912aa 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.
2020-05-19 16:20:33 +03:00
Roman Arutyunyan
34ac45d0a8 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.
2020-05-19 15:47:37 +03:00
Roman Arutyunyan
e6c8d371a7 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.
2020-05-19 15:34:00 +03:00
Roman Arutyunyan
28bcacbe7a 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.
2020-05-19 15:29:10 +03:00
Roman Arutyunyan
35586c3acb 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.
2020-05-14 14:49:53 +03:00
Roman Arutyunyan
4ad1869197 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.
2020-05-19 15:41:41 +03:00
Sergey Kandaurov
85bf88ffa8 README: documented Retry, 0-RTT, TLSv1.3 configuration. 2020-05-23 14:41:08 +03:00
Vladimir Homutov
c39e666583 Style.
Rephrased error message and removed trailing space.  Long comments were
shortened/rephrased.
2020-05-21 15:48:39 +03:00
Vladimir Homutov
b46205cae6 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.
2020-05-22 18:14:35 +03:00
Vladimir Homutov
e19c3c0399 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.
2020-05-22 18:08:02 +03:00
Vladimir Homutov
bf08ad6564 Fixed retransmission of frames after closing connection.
Frames in sent queues are discarded, as no acknowledgment is expected
if the connection is closing.
2020-05-21 15:41:01 +03:00
Vladimir Homutov
742ea8420e 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.
2020-05-21 15:38:52 +03:00
Sergey Kandaurov
620dfec5cb README: pointed out Alt-Svc "ma" parameter useful with curl. 2020-05-22 18:22:00 +03:00
Vladimir Homutov
8cf95255ac Fixed a typo. 2020-05-22 18:16:34 +03:00
Sergey Kandaurov
930c135a02 Assorted fixes.
Found by Clang Static Analyzer.
2020-05-20 15:36:24 +03:00
Vladimir Homutov
0982c2ee43 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.
2020-05-18 13:54:53 +03:00
Vladimir Homutov
d39920689b 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.
2020-05-18 13:54:35 +03:00
Vladimir Homutov
7d4864b89a Removed outdated debug. 2020-05-14 18:10:53 +03:00
Vladimir Homutov
c18864a097 Fixed a typo. 2020-05-14 17:22:29 +03:00
Sergey Kandaurov
7005f46678 README: Retry support, protocol error messages implemented. 2020-05-14 16:33:46 +03:00
Vladimir Homutov
2b8786afe8 Fixed time comparison. 2020-05-12 18:45:44 +03:00
Vladimir Homutov
685b42cef6 Added tests for connection id lengths in initial packet. 2020-05-14 14:49:28 +03:00
Vladimir Homutov
aa4f97dd73 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.
2020-05-14 01:06:45 +03:00
Vladimir Homutov
b507229c73 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.
2020-05-14 15:54:45 +03:00
Sergey Kandaurov
4d3b28b39c Address validation using NEW_TOKEN frame. 2020-05-14 15:47:24 +03:00
Sergey Kandaurov
fbff14f583 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.
2020-05-14 15:47:18 +03:00
Sergey Kandaurov
92324d157c Server CID change refactored. 2020-05-13 18:34:34 +03:00
Sergey Kandaurov
b6e8c1b542 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.
2020-05-12 18:18:58 +03:00
Sergey Kandaurov
95ff5f6be4 Removed redundant long packet type checks. 2020-05-09 17:41:07 +03:00
Sergey Kandaurov
a05371cf9e Removed redundant SSL_do_handshake call before any handshake data. 2020-05-09 17:39:47 +03:00
Vladimir Homutov
72b6655e9c 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.
2020-05-08 13:08:04 +03:00
Vladimir Homutov
8e16e4eff5 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.
2020-05-07 12:34:04 +03:00
Sergey Kandaurov
04ba271d3e Restored ngx_quic_encrypt return type.
It was inadvertently changed while working on removing memory allocations.
2020-05-06 14:34:44 +03:00
Vladimir Homutov
2c62f443a9 Store clearflags in pkt->flags after decryption.
It doesn't make sense to store protected flags.
2020-04-30 12:22:35 +03:00
Sergey Kandaurov
76ef3c1768 Configure: fixed static compilation with OpenSSL 1.1.1 / BoringSSL.
See 7246:04ebf29eaf5b for details.
2020-05-01 13:02:30 +03:00
Sergey Kandaurov
5fc7d63f80 Mention quic branch in README. 2020-04-30 15:59:14 +03:00
Sergey Kandaurov
7a34d6e74a Configure: unbreak with old OpenSSL, --with-http_v3_module added. 2020-04-30 15:47:43 +03:00
Vladimir Homutov
8abc8b130f Removed outdated/incorrect comments and fixed style.
- we need transport parameters early to get packet size limits at least.
2020-04-29 14:45:55 +03:00
Vladimir Homutov
675ec33c5b 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.
2020-04-30 12:38:38 +03:00
Sergey Kandaurov
390ffc92f2 Renamed retransmit event object in preparation for retry support. 2020-04-29 14:59:21 +03:00
Sergey Kandaurov
58dcabc2be Server CID change. 2020-04-28 18:24:01 +03:00
Sergey Kandaurov
90bd619f81 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.
2020-04-28 18:23:56 +03:00
Vladimir Homutov
dfc9c2dd14 Added README. 2020-04-28 18:16:13 +03:00
Roman Arutyunyan
b93e22b5fd Respect MAX_DATA and MAX_STREAM_DATA from QUIC client. 2020-04-28 16:37:32 +03:00
Roman Arutyunyan
fa1795919c QUIC basic congestion control. 2020-04-28 16:42:43 +03:00
Roman Arutyunyan
70e34b17c8 Fixed packet retransmission.
Previously frames in ctx->sent queue could be lost.
2020-04-24 17:20:37 +03:00
Roman Arutyunyan
e15adc3eb8 Assign connection number to every QUIC stream log. 2020-04-23 18:05:05 +03:00
Vladimir Homutov
ff7635070e 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
2020-04-24 14:38:49 +03:00
Vladimir Homutov
530342f5fe 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
2020-04-24 11:33:00 +03:00
Vladimir Homutov
f6306e8faf 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
2020-04-24 10:11:47 +03:00
Vladimir Homutov
3df104d74a 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.
2020-04-23 12:25:00 +03:00
Vladimir Homutov
3a9bdecdcd Retired the ngx_quic_parse_int_multi() function.
It used variable-length arguments what is not really necessary.
2020-04-23 12:10:56 +03:00
Vladimir Homutov
7727d103ff Removed support of drafts older than currently latest 27. 2020-04-23 11:50:20 +03:00
Vladimir Homutov
9268eb82e6 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.
2020-04-23 13:41:08 +03:00
Vladimir Homutov
51a4a7cace 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.
2020-04-23 11:15:44 +03:00
Sergey Kandaurov
143642175b HTTP/3: directives with limited values converted to post handler.
The purpose is to show a precise line number with an invalid value.
2020-04-22 15:59:19 +03:00
Sergey Kandaurov
b609fbb299 HTTP/3: bytes holding directives changed to ngx_conf_set_size_slot.
This allows to specify directive values with measurement units.
2020-04-22 15:48:39 +03:00
Sergey Kandaurov
8da6bbe021 Improved ngx_quic_build_int() code and readability.
The function now generates somewhat shorter assembler after inlining.
2020-04-22 14:52:16 +03:00
Roman Arutyunyan
29f6610c6a Fixed QUIC buffer consumption in send_chain(). 2020-04-21 17:52:32 +03:00
Roman Arutyunyan
f5497fb4b2 HTTP/3: fixed encoding variable-length integers. 2020-04-21 17:11:49 +03:00
Vladimir Homutov
fddff472ae Fixed memory leak with reordered stream frames. 2020-04-20 18:32:46 +03:00
Roman Arutyunyan
9ad3701249 Fixed includes in quic headers. 2020-04-21 12:06:24 +03:00
Vladimir Homutov
52ee48aee1 Added MAX_STREAM_DATA stub handler.
Currently sending code is ignoring this.
2020-04-20 17:18:04 +03:00
Vladimir Homutov
034b7aa141 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).
2020-04-20 22:25:22 +03:00
Vladimir Homutov
6b721fa123 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.
2020-04-20 12:12:17 +03:00
Sergey Kandaurov
5bd2c23508 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.
2020-04-17 12:01:45 +03:00
Vladimir Homutov
5900b8eca0 Fixed error descriptions.
The check for array bound is done inside function that returns error
description.  Missing initialization element is added.
2020-04-16 16:54:22 +03:00
Vladimir Homutov
236228d223 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.
2020-04-16 13:28:43 +03:00
Vladimir Homutov
2febedf38c Added handling of incorrect values in TP configuration.
Some parameters have minimal/maximum values defined by standard.
2020-04-16 12:17:41 +03:00
Sergey Kandaurov
97c2ac2892 Parsing of truncated packet numbers.
For sample decoding algorithm, see quic-transport-27#appendix-A.
2020-04-16 12:46:48 +03:00
Vladimir Homutov
4a267b8304 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)
2020-04-15 18:54:03 +03:00
Vladimir Homutov
fb07bd3fc1 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.
2020-04-15 14:29:00 +03:00
Vladimir Homutov
a99a268a5d 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)
2020-04-15 13:09:39 +03:00
Vladimir Homutov
a72ce93da9 Sorted functions and functions declarations. 2020-04-14 16:30:41 +03:00
Vladimir Homutov
0a59aa67e4 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.
2020-04-15 11:11:54 +03:00
Vladimir Homutov
30f51174ec 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.
2020-04-14 12:16:25 +03:00
Vladimir Homutov
6d8f571730 Cleaned up magic numbers in ngx_quic_output_frames(). 2020-04-13 14:57:58 +03:00
Vladimir Homutov
7a7ef3cbfb 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()
2020-04-14 12:06:32 +03:00
Sergey Kandaurov
8acaa933af Merged with the default branch. 2020-04-14 19:35:20 +03:00
Roman Arutyunyan
3dc5ecbe69 HTTP/3: fixed reading request body. 2020-04-13 17:54:23 +03:00
Vladimir Homutov
093f4f21b5 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.
2020-04-07 15:50:38 +03:00
Sergey Kandaurov
df34c84733 Fixed build with OpenSSL using old callbacks API. 2020-04-07 12:54:34 +03:00
Vladimir Homutov
88b9aed247 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
2020-04-06 16:19:26 +03:00
Vladimir Homutov
c0c3a400ef 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.
2020-04-06 11:16:45 +03:00
Vladimir Homutov
c025e2cf80 Added check for SSL_get_current_cipher() results.
The function may return NULL and result need to be checked before use.
2020-04-04 22:25:41 +03:00
Vladimir Homutov
97ebd69704 Added a bit more debugging in STREAM frame parser. 2020-04-06 11:17:14 +03:00
Vladimir Homutov
e9f4adf0b3 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.
2020-04-04 22:27:29 +03:00
Sergey Kandaurov
d42d04baf6 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.
2020-04-06 14:54:10 +03:00
Sergey Kandaurov
9c12453342 Rejecting new connections with non-zero Initial packet. 2020-04-06 14:54:10 +03:00
Sergey Kandaurov
4a03675be3 TLS Key Update in QUIC.
Old keys retention is yet to be implemented.
2020-04-06 14:54:08 +03:00
Sergey Kandaurov
bf825ce6cc Removed excessive debugging in QUIC packet creation.
While here, eliminated further difference in between.
2020-04-04 17:34:39 +03:00
Sergey Kandaurov
755dd33d97 Logging of packet numbers in QUIC packet creation. 2020-04-04 17:34:04 +03:00
Vladimir Homutov
dc3a60c8d9 Removed unneccesary milliseconds conversion. 2020-04-03 16:33:59 +03:00
Vladimir Homutov
7e1e892a8a 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
2020-04-03 14:02:16 +03:00
Sergey Kandaurov
3d9b7f1c8b 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.
2020-04-03 13:49:44 +03:00
Sergey Kandaurov
48608142a3 Fixed computing nonce again, by properly shifting packet number. 2020-04-03 13:49:40 +03:00
Vladimir Homutov
4b40730b18 Fixed missing propagation of need_ack flag from frames to packet. 2020-04-03 09:53:51 +03:00
Vladimir Homutov
58b439447e 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.
2020-04-02 14:53:01 +03:00
Sergey Kandaurov
9f9700d6e3 Fixed computing nonce by xoring all packet number bytes.
Previously, the stub worked only with pnl=0.
2020-04-02 11:40:25 +03:00
Vladimir Homutov
dc0b7674f1 Output buffering.
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...)
2020-04-01 17:09:11 +03:00
Vladimir Homutov
41fca95d9a 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.
2020-04-01 17:06:26 +03:00
Vladimir Homutov
7eac371881 Introduced packet namespace in QUIC connection.
The structure contains all data that is related to the namespace:
packet number and output queue (next patch).
2020-04-01 14:31:08 +03:00
Vladimir Homutov
82558fa46a 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.
2020-04-01 14:25:25 +03:00
Vladimir Homutov
38b5f39e8e Added missing debug description. 2020-04-01 17:21:52 +03:00
Sergey Kandaurov
56456e36fc TLS Early Data support. 2020-04-01 13:27:42 +03:00
Sergey Kandaurov
f68c876ca3 TLS Early Data key derivation support. 2020-04-01 13:27:42 +03:00
Sergey Kandaurov
e0e880bfab 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.
2020-04-01 13:27:42 +03:00
Sergey Kandaurov
21f4be001a QUIC packet padding to fulfil header protection sample demands. 2020-04-01 13:27:42 +03:00
Sergey Kandaurov
7d5fe69bb2 Improved SSL_do_handshake() error handling in QUIC.
It can either return a recoverable SSL_ERROR_WANT_READ or fatal errors.
2020-04-01 13:27:42 +03:00
Sergey Kandaurov
108bc03458 Style. 2020-04-01 13:27:41 +03:00
Vladimir Homutov
ed21279b6f Removed unused field from ngx_quic_header_t. 2020-03-31 13:13:12 +03:00
Sergey Kandaurov
6749f64f7f HTTP/3: http3 variable. 2020-03-28 18:41:31 +03:00
Sergey Kandaurov
536810e48b HTTP/3: static table cleanup. 2020-03-28 18:02:20 +03:00
Roman Arutyunyan
48a1eeb5c2 Parsing HTTP/3 request body. 2020-03-27 19:41:06 +03:00
Roman Arutyunyan
732e383dd1 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.
2020-03-27 10:02:45 +03:00
Roman Arutyunyan
6bc0ecd946 Push QUIC stream frames in send() and cleanup handler. 2020-03-27 19:08:24 +03:00
Roman Arutyunyan
50e32ed41d Chunked response body in HTTP/3. 2020-03-27 19:46:54 +03:00
Roman Arutyunyan
1903ad35b9 Fixed buffer overflow. 2020-03-27 15:50:42 +03:00
Sergey Kandaurov
20659b28cc Unbreak sending CONNECTION_CLOSE from the send_alert callback. 2020-03-27 12:52:08 +03:00
Vladimir Homutov
dd88e287a5 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.
2020-03-26 18:29:38 +03:00
Vladimir Homutov
559e9b7f59 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.
2020-03-26 16:54:46 +03:00
Vladimir Homutov
6bad711183 Logging cleanup.
pool->log is replaced with pkt->log or explicit argument passing where
possible.
2020-03-26 13:54:49 +03:00
Roman Arutyunyan
5f6d337e47 QUIC frames reuse. 2020-03-25 23:40:50 +03:00
Vladimir Homutov
73fc0300aa 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()
2020-03-26 12:11:50 +03:00
Roman Arutyunyan
8decfa3847 Fixed QUIC stream insert and find. 2020-03-25 14:05:40 +03:00
Roman Arutyunyan
5162a3da50 Simplifed handling HTTP/3 streams. 2020-03-25 12:14:24 +03:00
Roman Arutyunyan
897df08a00 Safe QUIC stream creation. 2020-03-25 12:56:21 +03:00
Roman Arutyunyan
372d6283c2 When closing a QUIC connection, wait for all streams to finish.
Additionally, streams are now removed from the tree in cleanup handler.
2020-03-24 18:05:45 +03:00
Roman Arutyunyan
22a1957f92 Removed ngx_quic_stream_node_t.
Now ngx_quic_stream_t is directly inserted into the tree.
2020-03-24 16:38:03 +03:00
Roman Arutyunyan
f4b6701ab4 Implemented eof in QUIC streams. 2020-03-24 13:49:42 +03:00
Vladimir Homutov
34e20825bb Fixed log initialization.
Should be done after memzero.
2020-03-25 19:42:00 +03:00
Sergey Kandaurov
9dda9e51f3 Advertise our max_idle_timeout in transport parameters.
So we can easily tune how soon client would decide to close a connection.
2020-03-24 22:12:52 +03:00
Sergey Kandaurov
c87e5a3a13 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.
2020-03-24 19:17:57 +03:00
Vladimir Homutov
6b8343d4cf 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.
2020-03-24 17:03:39 +03:00
Vladimir Homutov
55680af808 Added QUIC version check for sending HANDSHAKE_DONE frame. 2020-03-24 12:15:39 +03:00
Vladimir Homutov
f38c75578c Implemented sending HANDSHAKE_DONE frame after handshake.
This makes it possible to switch to draft 27 by default.
2020-03-24 11:59:14 +03:00
Sergey Kandaurov
ac4d386e29 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.
2020-03-23 20:48:34 +03:00
Roman Arutyunyan
b20601811e Respect QUIC max_idle_timeout. 2020-03-23 21:20:20 +03:00
Roman Arutyunyan
ca0bc7f0d7 Allow ngx_queue_frame() to insert frame in the front.
Previously a frame could only be inserted after the first element of the list.
2020-03-23 19:42:09 +03:00
Roman Arutyunyan
0d50d1718c Support for HTTP/3 ALPN.
This is required by Chrome.
2020-03-23 19:26:24 +03:00
Roman Arutyunyan
ce532aa3e2 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".
2020-03-23 15:32:24 +03:00
Roman Arutyunyan
25447805a2 Avoid using QUIC connection after CONNECTION_CLOSE. 2020-03-23 19:19:44 +03:00
Roman Arutyunyan
aca8dcc624 Better flow control and buffering for QUIC streams. 2020-03-23 15:49:31 +03:00
Roman Arutyunyan
d60818a0d3 Limit output QUIC packets with client max_packet_size.
Additionally, receive larger packets than 512 bytes.
2020-03-23 18:47:17 +03:00
Sergey Kandaurov
1afb9cd2be Fixed received ACK fields order in debug logging. 2020-03-23 18:20:42 +03:00
Vladimir Homutov
b934f9289b 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();
2020-03-23 14:53:04 +03:00
Vladimir Homutov
a707587883 Skip unknown transport parameters. 2020-03-23 12:57:24 +03:00
Vladimir Homutov
f26700cc7f Add unsupported version into log.
This makes it easier to understand what client wants.
2020-03-23 10:57:28 +03:00
Vladimir Homutov
b0972707a3 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.
2020-03-21 20:51:59 +03:00
Sergey Kandaurov
02a2cbf438 Fixed CRYPTO offset generation. 2020-03-22 12:15:54 +03:00
Sergey Kandaurov
457e579896 Closing connection on NGX_QUIC_FT_CONNECTION_CLOSE. 2020-03-22 11:35:15 +03:00
Vladimir Homutov
76db776d70 Implemented parsing of remaining frame types. 2020-03-21 20:49:55 +03:00
Sergey Kandaurov
9e02252c76 Fixed parsing NGX_QUIC_FT_CONNECTION_CLOSE. 2020-03-21 19:45:24 +03:00
Sergey Kandaurov
d588f9da33 Fixed buffer overrun in create_transport_params() with -24.
It writes 16-bit prefix as designed, but length calculation assumed varint.
2020-03-21 19:22:39 +03:00
Sergey Kandaurov
5a823e8656 Fixed build with macOS's long long abomination. 2020-03-21 18:44:10 +03:00
Roman Arutyunyan
587adbda18 Removed unused variable. 2020-03-20 23:49:42 +03:00
Vladimir Homutov
8c69d52595 Removed unused variable. 2020-03-20 20:39:41 +03:00
Vladimir Homutov
9fe82b7379 Added checks for permitted frame types.
+ cleanup in macros for packet types
 + some style fixes in quic_transport.h (case, indentation)
2020-03-20 20:03:44 +03:00
Vladimir Homutov
b5c3e7cc6d Fixed parsing of CONNECTION CLOSE2 frames.
The "frame_type" field is not passed in case of 0x1d frame.
2020-03-20 15:14:00 +03:00
Vladimir Homutov
f9e8909725 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.
2020-03-20 14:50:05 +03:00
Vladimir Homutov
4b97a37ef9 Adedd the http "quic" variable.
The value is literal "quic" for requests passed over HTTP/3, and empty string
otherwise.
2020-03-20 12:44:45 +03:00
Vladimir Homutov
239eab2f11 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)
2020-03-20 13:47:44 +03:00
Roman Arutyunyan
e1c5be01a8 Reset QUIC timeout on every datagram. 2020-03-19 21:46:28 +03:00
Roman Arutyunyan
cc82918be2 Double MAX_STREAMS on STREAMS_BLOCKED. 2020-03-20 10:14:58 +03:00
Roman Arutyunyan
faffab6185 Fixed ACKs to packet numbers greater than 63. 2020-03-20 09:23:31 +03:00
Sergey Kandaurov
ee9d09252b Fixed specifiers in "quic packet length" logging. 2020-03-19 17:33:36 +03:00
Sergey Kandaurov
961a0bd505 Fixed build. 2020-03-19 17:22:43 +03:00
Vladimir Homutov
f950ce410a The ngx_quic_frame_len() function is not really needed. 2020-03-19 14:59:55 +03:00
Vladimir Homutov
5040cd1100 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.
2020-03-19 17:07:12 +03:00
Roman Arutyunyan
2b325d5232 Send a FIN frame when QUIC stream is closed. 2020-03-19 15:34:35 +03:00
Roman Arutyunyan
2fbf07e5f9 Fixed header creation for header_only responses in HTTP/3. 2020-03-19 15:03:09 +03:00
Sergey Kandaurov
7280e53dc0 MAX_DATA frame parser/handler. 2020-03-18 23:26:26 +03:00
Vladimir Homutov
50dae403db Added parsing of STREAMS BLOCKED frames.
While there, added hex prefix for debug to avoid frame type confusion.
2020-03-19 11:15:43 +03:00
Sergey Kandaurov
e86812b373 Implemented send_alert callback, CONNECTION_CLOSE writer.
The callback produces a CONNECTION_CLOSE frame, as per quic-tls-24#section-4.9.
2020-03-18 23:07:40 +03:00
Roman Arutyunyan
e6013b7ceb Added copying addr_text to QUIC stream connections.
Now $remote_addr holds client address.
2020-03-18 20:28:28 +03:00
Roman Arutyunyan
bcda520b67 HTTP/3 $request_line variable. 2020-03-18 20:22:16 +03:00
Roman Arutyunyan
622a45cedb 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.
2020-03-18 16:37:16 +03:00
Vladimir Homutov
aaaa18b4bb Added parsing of RESET_STREAM and STOP_SENDING frames 2020-03-18 16:35:11 +03:00
Roman Arutyunyan
1fd47a9563 Fixed pointer increment while parsing HTTP/3 header. 2020-03-18 15:28:20 +03:00
Vladimir Homutov
30c58cf40d 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.
2020-03-18 13:49:39 +03:00
Roman Arutyunyan
1121c906b1 Fixed HTTP/3 server stream creation. 2020-03-18 14:10:44 +03:00
Roman Arutyunyan
9bd0187263 Removed comment. 2020-03-18 14:09:50 +03:00
Roman Arutyunyan
08a6458386 Refactored HTTP/3 parser. 2020-03-18 13:46:35 +03:00
Vladimir Homutov
50c8ba32ea 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
2020-03-18 13:02:19 +03:00
Vladimir Homutov
a201153f69 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.
2020-03-18 12:58:27 +03:00
Vladimir Homutov
c628e1ef8b 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";
        }
    }
}
2020-03-17 14:10:37 +03:00
Vladimir Homutov
715e0cc149 Fixed a typo with OpenSSL. 2020-03-16 19:42:57 +03:00
Vladimir Homutov
aaa0d454bf 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).
2020-03-16 19:00:47 +03:00
Vladimir Homutov
4c90b01897 Added processing of CONNECTION CLOSE frames.
Contents is parsed and debug is output. No actions are taken.
2020-03-16 13:06:43 +03:00
Roman Arutyunyan
469230a940 Temporary fix for header null-termination in HTTP/3. 2020-03-14 13:18:55 +03:00
Sergey Kandaurov
3326916378 Fixed header protection application with pn length > 1. 2020-03-14 03:15:09 +03:00
Roman Arutyunyan
dfc75f89f6 Fixed sanitizer errors. 2020-03-13 20:44:32 +03:00
Vladimir Homutov
218417c5cf Added check for initialized c->ssl before calling SSL shutdown. 2020-03-13 18:55:58 +03:00
Roman Arutyunyan
0159e05a1e HTTP/3. 2020-03-13 19:36:33 +03:00
Roman Arutyunyan
5d91366f54 Fixed infinite loop in ngx_quic_stream_send_chain(). 2020-03-13 18:30:37 +03:00
Roman Arutyunyan
ed7b99249c Implemented tracking offset in STREAM frames. 2020-03-13 18:29:50 +03:00
Roman Arutyunyan
67d3e0727f Implemented ngx_quic_stream_send_chain() method.
- just call send in a loop
2020-03-13 15:56:10 +03:00
Vladimir Homutov
1b1717d472 Stream "connection" read/write methods. 2020-03-13 14:39:23 +03:00
Sergey Kandaurov
65a6ae1afc Fix build. 2020-03-12 18:08:26 +03:00
Vladimir Homutov
32a82b3af2 Removed hardcoded CRYPTO and ACK frame sizes. 2020-03-12 14:23:27 +03:00
Vladimir Homutov
d311deed47 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
2020-03-12 16:54:43 +03:00
Vladimir Homutov
15d6485f1d Initial parsing of STREAM frames. 2020-03-11 15:41:35 +03:00
Vladimir Homutov
6fa68213c7 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.
2020-03-12 14:43:24 +03:00
Vladimir Homutov
2425de1cee Added more transport parameters.
Needed for client to start sending streams.
2020-03-11 15:43:23 +03:00
Sergey Kandaurov
577f654b11 Compatibility with BoringSSL revised QUIC encryption secret APIs.
See for details: https://boringssl.googlesource.com/boringssl/+/1e85905%5E!/
2020-03-11 21:53:02 +03:00
Sergey Kandaurov
068f620567 Chacha20 header protection support with BoringSSL.
BoringSSL lacks EVP for Chacha20.  Here we use CRYPTO_chacha_20() instead.
2020-03-10 19:15:12 +03:00
Sergey Kandaurov
9cfb197dd6 ChaCha20 / Poly1305 initial support. 2020-03-10 19:13:09 +03:00
Sergey Kandaurov
c321510f45 Using SSL cipher suite id to obtain cipher/digest, part 2.
Ciphers negotiation handling refactored into ngx_quic_ciphers().
2020-03-10 19:12:22 +03:00
Sergey Kandaurov
8d857000d9 Fixed nonce in short packet protection. 2020-03-10 18:40:18 +03:00
Vladimir Homutov
9bb1eba59e 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).
2020-03-10 18:24:39 +03:00
Sergey Kandaurov
f1d376e384 Fixed header protection with negotiated cipher suite. 2020-03-05 20:05:40 +03:00
Sergey Kandaurov
fe5ad8267a Initial packets are protected with AEAD_AES_128_GCM. 2020-03-05 19:49:49 +03:00
Sergey Kandaurov
3a354b0ab3 Fixed write secret logging in set_encryption_secrets callback. 2020-03-05 18:01:18 +03:00
Vladimir Homutov
6f67c00f50 Fixed format specifiers. 2020-03-05 17:51:22 +03:00
Vladimir Homutov
0039105abd Style. 2020-03-05 17:24:04 +03:00
Vladimir Homutov
a46e0377c1 Added functions to decrypt long packets. 2020-03-05 17:18:33 +03:00
Sergey Kandaurov
74b67ff2b2 Fixed ngx_quic_varint_len misuse in the previous change. 2020-03-05 15:26:15 +03:00
Vladimir Homutov
f4a487ceaa Macro for calculating size of varint. 2020-03-04 23:24:51 +03:00
Sergey Kandaurov
9b4c52cab0 Fixed packet "input" debug log message. 2020-03-05 13:10:01 +03:00
Sergey Kandaurov
73fc9eba33 Using SSL cipher suite id to obtain cipher/digest, part 1.
While here, log the negotiated cipher just once, - after handshake.
2020-03-05 13:00:59 +03:00
Sergey Kandaurov
36412471e2 Using cached ssl_conn in ngx_quic_handshake_input(), NFC. 2020-03-05 12:51:49 +03:00
Sergey Kandaurov
1ffd2a5fe4 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.
2020-03-04 16:05:39 +03:00
Vladimir Homutov
65772b9bd1 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.
2020-03-04 15:52:12 +03:00
Sergey Kandaurov
131b2bb9bf 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.
2020-03-03 17:25:02 +03:00
Vladimir Homutov
d7c0d2df7a Split frame and packet generation into separate steps.
While there, a number of QUIC constants from spec defined and magic numbers
were replaced.
2020-03-03 13:30:30 +03:00
Vladimir Homutov
11cd3e4683 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);
2020-03-02 21:38:03 +03:00
Vladimir Homutov
49049bdd1b 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.
2020-02-28 16:23:25 +03:00
Sergey Kandaurov
fd70d48420 Introduced quic_version macro, uint16/uint32 routines ported. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
c3aa2abe4e Cleanup. 2020-02-28 13:09:52 +03:00
Vladimir Homutov
9ae0d4089c Generic function for HKDF expansion. 2020-02-26 16:56:47 +03:00
Sergey Kandaurov
eb36fdff02 QUIC header protection routines, introduced ngx_quic_tls_hp(). 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
74c165c94e AEAD routines, introduced ngx_quic_tls_open()/ngx_quic_tls_seal(). 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
81f1ccd4ee Transport parameters stub, to complete handshake. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
930c146505 Introduced ngx_quic_secret_t. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
f1f2cac9e8 QUIC handshake handler, draft 24 bump. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
4ea79f7d02 Fixed indentation. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
6d32c6184e PN-aware AEAD nonce, feeding proper CRYPTO length. 2020-02-28 13:09:52 +03:00
Sergey Kandaurov
5ec316b631 OpenSSL compatibility. 2020-02-28 13:09:51 +03:00
Sergey Kandaurov
dafa5d611a QUIC add_handshake_data callback, varint routines. 2020-02-28 13:09:51 +03:00
Sergey Kandaurov
fdfc8d7bd1 QUIC set_encryption_secrets callback. 2020-02-28 13:09:51 +03:00
Sergey Kandaurov
67f8c85e40 Server Initial Keys. 2020-02-28 13:09:51 +03:00
Sergey Kandaurov
f45ea7a822 Initial QUIC support in http. 2020-02-28 13:09:51 +03:00
Sergey Kandaurov
7860cca902 HTTP UDP layer, QUIC support autotest. 2020-02-28 13:09:51 +03:00
Vladimir Homutov
a03810451f Created the "quic" branch. 2020-03-03 12:14:44 +03:00
84 changed files with 25522 additions and 53 deletions

389
README Normal file
View file

@ -0,0 +1,389 @@
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 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 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.
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.
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;
To enable GSO (Generic Segmentation Offloading):
quic_gso on;
To limit maximum UDP payload size on receive path:
quic_mtu <size>;
To set host key for various tokens:
quic_host_key <filename>;
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_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,
"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 {
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;
ssl_protocols TLSv1.3;
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 | 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 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

View file

@ -5,12 +5,17 @@
if [ $OPENSSL != NONE ]; then
case "$CC" in
cl | bcc32)
have=NGX_OPENSSL . auto/have
have=NGX_SSL . auto/have
if [ $USE_OPENSSL_QUIC = YES ]; then
have=NGX_QUIC . auto/have
have=NGX_QUIC_OPENSSL_COMPAT . auto/have
fi
case "$CC" in
cl | bcc32)
CFLAGS="$CFLAGS -DNO_SYS_TYPES_H"
CORE_INCS="$CORE_INCS $OPENSSL/openssl/include"
@ -33,9 +38,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"
@ -123,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=<path> option.
END
exit 1
fi
fi
fi
fi

View file

@ -6,9 +6,10 @@
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/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

View file

@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then
fi
if [ $HTTP_V2 = YES ]; then
if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then
HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS"
fi
@ -124,6 +124,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
@ -156,6 +157,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 \
@ -217,6 +219,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=
@ -426,6 +439,33 @@ if [ $HTTP = YES ]; then
. auto/module
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
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_encode.h \
src/http/v3/ngx_http_v3_parse.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_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=
ngx_module_link=$HTTP_V3
. auto/module
fi
if :; then
ngx_module_name=ngx_http_static_module
ngx_module_incs=
@ -1035,6 +1075,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
@ -1272,6 +1326,63 @@ if [ $USE_OPENSSL = YES ]; then
fi
if [ $USE_OPENSSL_QUIC = YES ]; 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 \
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_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 \
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 \
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_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_socket.c \
src/event/quic/ngx_event_quic_openssl_compat.c"
ngx_module_libs=
ngx_module_link=YES
ngx_module_order=
. auto/module
if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; 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
if [ $USE_PCRE = YES ]; then
ngx_module_type=CORE
ngx_module_name=ngx_regex_module

View file

@ -45,6 +45,8 @@ USE_THREADS=NO
NGX_FILE_AIO=NO
QUIC_BPF=NO
HTTP=YES
NGX_HTTP_LOG_PATH=
@ -59,6 +61,7 @@ HTTP_CHARSET=YES
HTTP_GZIP=YES
HTTP_SSL=NO
HTTP_V2=NO
HTTP_V3=NO
HTTP_SSI=YES
HTTP_REALIP=NO
HTTP_XSLT=NO
@ -116,6 +119,7 @@ MAIL_SMTP=YES
STREAM=NO
STREAM_SSL=NO
STREAM_QUIC=NO
STREAM_REALIP=NO
STREAM_LIMIT_CONN=YES
STREAM_ACCESS=YES
@ -149,6 +153,7 @@ PCRE_JIT=NO
PCRE2=YES
USE_OPENSSL=NO
USE_OPENSSL_QUIC=NO
OPENSSL=NONE
USE_ZLIB=NO
@ -166,6 +171,8 @@ USE_GEOIP=NO
NGX_GOOGLE_PERFTOOLS=NO
NGX_CPP_TEST=NO
SO_COOKIE_FOUND=NO
NGX_LIBATOMIC=NO
NGX_CPU_CACHE_LINE=
@ -211,6 +218,8 @@ do
--with-file-aio) NGX_FILE_AIO=YES ;;
--without-quic_bpf_module) QUIC_BPF=NONE ;;
--with-ipv6)
NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG
$0: warning: the \"--with-ipv6\" option is deprecated"
@ -228,6 +237,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 ;;
@ -314,6 +324,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)
@ -443,8 +454,11 @@ 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_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
@ -533,6 +547,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

View file

@ -232,6 +232,50 @@ ngx_feature_test="struct crypt_data cd;
ngx_include="sys/vfs.h"; . auto/include
# BPF sockhash
ngx_feature="BPF sockhash"
ngx_feature_name="NGX_HAVE_BPF"
ngx_feature_run=no
ngx_feature_incs="#include <linux/bpf.h>
#include <sys/syscall.h>"
ngx_feature_path=
ngx_feature_libs=
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
CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c"
CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h"
if [ $QUIC_BPF != NONE ]; then
QUIC_BPF=YES
fi
fi
ngx_feature="SO_COOKIE"
ngx_feature_name="NGX_HAVE_SO_COOKIE"
ngx_feature_run=no
ngx_feature_incs="#include <sys/socket.h>
$NGX_INCLUDE_INTTYPES_H"
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
fi
# UDP segmentation offloading
ngx_feature="UDP_SEGMENT"

View file

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

View file

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

143
src/core/ngx_bpf.c Normal file
View file

@ -0,0 +1,143 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#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 = (uintptr_t) program->license;
attr.prog_type = program->type;
attr.insns = (uintptr_t) program->ins;
attr.insn_cnt = program->nins;
#if (NGX_DEBUG)
/* for verifier errors */
attr.log_buf = (uintptr_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 = (uintptr_t) key;
attr.value = (uintptr_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 = (uintptr_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 = (uintptr_t) key;
attr.value = (uintptr_t) value;
return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}

43
src/core/ngx_bpf.h Normal file
View file

@ -0,0 +1,43 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_BPF_H_INCLUDED_
#define _NGX_BPF_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <linux/bpf.h>
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_ */

View file

@ -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;
@ -1037,6 +1033,12 @@ 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;
if (c) {

View file

@ -73,6 +73,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;
@ -147,6 +148,10 @@ struct ngx_connection_s {
ngx_proxy_protocol_t *proxy_protocol;
#if (NGX_QUIC || NGX_COMPAT)
ngx_quic_stream_t *quic;
#endif
#if (NGX_SSL || NGX_COMPAT)
ngx_ssl_connection_t *ssl;
#endif

View file

@ -27,6 +27,7 @@ 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;
@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_resolver.h>
#if (NGX_OPENSSL)
#include <ngx_event_openssl.h>
#if (NGX_QUIC)
#include <ngx_event_quic.h>
#endif
#endif
#include <ngx_process_cycle.h>
#include <ngx_conf_file.h>
@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c);
#include <ngx_connection.h>
#include <ngx_syslog.h>
#include <ngx_proxy_protocol.h>
#if (NGX_HAVE_BPF)
#include <ngx_bpf.h>
#endif
#define LF (u_char) '\n'

View file

@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle)
ngx_int_t
ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags)
{
#if (NGX_QUIC)
ngx_connection_t *c;
c = rev->data;
if (c->quic) {
return NGX_OK;
}
#endif
if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
/* kqueue, epoll */
@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat)
{
ngx_connection_t *c;
if (lowat) {
c = wev->data;
#if (NGX_QUIC)
if (c->quic) {
return NGX_OK;
}
#endif
if (lowat) {
if (ngx_send_lowat(c, lowat) == NGX_ERROR) {
return NGX_ERROR;
}
@ -873,8 +891,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)

View file

@ -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;
@ -3202,6 +3199,13 @@ ngx_ssl_shutdown(ngx_connection_t *c)
ngx_err_t err;
ngx_uint_t tries;
#if (NGX_QUIC)
if (c->quic) {
/* QUIC streams inherit SSL object */
return NGX_OK;
}
#endif
rc = NGX_OK;
ngx_ssl_ocsp_cleanup(c);

View file

@ -24,6 +24,14 @@
#include <openssl/engine.h>
#endif
#include <openssl/evp.h>
#if (NGX_QUIC)
#ifdef OPENSSL_IS_BORINGSSL
#include <openssl/hkdf.h>
#include <openssl/chacha.h>
#else
#include <openssl/kdf.h>
#endif
#endif
#include <openssl/hmac.h>
#ifndef OPENSSL_NO_OCSP
#include <openssl/ocsp.h>
@ -302,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);

View file

@ -12,13 +12,6 @@
#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);

View file

@ -23,6 +23,13 @@
#endif
struct ngx_udp_connection_s {
ngx_rbtree_node_t node;
ngx_connection_t *connection;
ngx_buf_t *buffer;
};
#if (NGX_HAVE_ADDRINFO_CMSG)
typedef union {

View file

@ -0,0 +1,113 @@
#!/bin/bash
export LANG=C
set -e
if [ $# -lt 1 ]; then
echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>"
exit 1
fi
self=$0
filename=$1
funcname=$PROGNAME
generate_head()
{
cat << END
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#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

View file

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

View file

@ -0,0 +1,140 @@
#include <errno.h>
#include <linux/string.h>
#include <linux/udp.h>
#include <linux/bpf.h>
/*
* 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 <bpf/bpf_helpers.h>
#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) | \
((__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,
* 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)); /* data at UDP header */
advance_data(1); /* data at QUIC flags */
if (data[0] & NGX_QUIC_PKT_LONG) {
advance_data(4); /* data at QUIC version */
advance_data(1); /* data at DCID len */
len = data[0]; /* read DCID length */
if (len < 8) {
/* it's useless to search for key in such short DCID */
return SK_PASS;
}
} 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%llx", key);
return SK_PASS;
/* kernel returns positive error numbers, errno.h defines positive */
case -ENOENT:
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%llx",
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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,131 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_H_INCLUDED_
#define _NGX_EVENT_QUIC_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527
#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3
#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25
#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_SR_TOKEN_LEN 16
#define NGX_QUIC_MIN_INITIAL_SIZE 1200
#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01
#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);
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 {
uint64_t size;
uint64_t offset;
uint64_t last_offset;
ngx_chain_t *chain;
ngx_chain_t *last_chain;
} ngx_quic_buffer_t;
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;
ngx_quic_init_pt init;
ngx_quic_shutdown_pt shutdown;
u_char av_token_key[NGX_QUIC_AV_KEY_LEN];
u_char sr_token_key[NGX_QUIC_SR_KEY_LEN];
} ngx_quic_conf_t;
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 sent;
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_final_size;
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;
unsigned cancelable:1;
unsigned fin_acked:1;
};
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,
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);
ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how);
void ngx_quic_cancelable_stream(ngx_connection_t *c);
ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len,
ngx_str_t *dcid);
ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label,
ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len);
#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
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_ */

View file

@ -0,0 +1,657 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#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;
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);
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:%d 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:%d", 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:%d new:%d",
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:%ui",
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:%d address:%*s",
grp->map_fd, p - v, v);
v = p + 1;
break;
default:
break;
}
}
return NGX_OK;
}

View file

@ -0,0 +1,88 @@
/* AUTO-GENERATED, DO NOT EDIT. */
#include <stddef.h>
#include <stdint.h>
#include "ngx_bpf.h"
static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = {
{ "ngx_quic_sockmap", 55 },
};
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) 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) 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 },
{ 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) 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) 39, 0x0 },
{ 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe },
{ 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) 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) 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) 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 },
{ 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, 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 },
{ 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,
};

View file

@ -0,0 +1,283 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
/* #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;
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;
#if (NGX_QUIC_OPENSSL_COMPAT)
#include <ngx_event_quic_openssl_compat.h>
#endif
#include <ngx_event_quic_transport.h>
#include <ngx_event_quic_protection.h>
#include <ngx_event_quic_frames.h>
#include <ngx_event_quic_migration.h>
#include <ngx_event_quic_connid.h>
#include <ngx_event_quic_streams.h>
#include <ngx_event_quic_ssl.h>
#include <ngx_event_quic_tokens.h>
#include <ngx_event_quic_ack.h>
#include <ngx_event_quic_output.h>
#include <ngx_event_quic_socket.h>
/* 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
#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:
*
* 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_get_connection(c) \
(((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL)
#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp))
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_uint_t used; /* unsigned used:1; */
};
struct ngx_quic_server_id_s {
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
};
struct ngx_quic_path_s {
ngx_queue_t queue;
struct sockaddr *sockaddr;
ngx_sockaddr_t sa;
socklen_t socklen;
ngx_quic_client_id_t *cid;
ngx_msec_t expires;
ngx_uint_t tries;
ngx_uint_t tag;
off_t sent;
off_t received;
u_char challenge1[8];
u_char challenge2[8];
uint64_t seqnum;
ngx_str_t addr_text;
u_char text[NGX_SOCKADDR_STRLEN];
unsigned validated:1;
unsigned validating:1;
unsigned limited:1;
};
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_sockaddr_t sockaddr;
socklen_t socklen;
ngx_uint_t used; /* unsigned used:1; */
};
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;
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_uint_t initialized;
/* unsigned initialized:1; */
} 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;
/*
* 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 that
* are also Initial packets.
*/
struct ngx_quic_send_ctx_s {
enum ssl_encryption_level_t level;
ngx_quic_buffer_t crypto;
uint64_t crypto_sent;
uint64_t pnum; /* to be sent */
uint64_t largest_ack; /* received from peer */
uint64_t largest_pn; /* received from peer */
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;
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;
};
struct ngx_quic_connection_s {
uint32_t version;
ngx_quic_path_t *path;
ngx_queue_t sockets;
ngx_queue_t paths;
ngx_queue_t client_ids;
ngx_queue_t free_sockets;
ngx_queue_t free_paths;
ngx_queue_t free_client_ids;
ngx_uint_t nsockets;
ngx_uint_t nclient_ids;
uint64_t max_retired_seqnum;
uint64_t client_seqnum;
uint64_t server_seqnum;
uint64_t path_seqnum;
ngx_quic_tp_t tp;
ngx_quic_tp_t ctp;
ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST];
ngx_quic_keys_t *keys;
ngx_quic_conf_t *conf;
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 first_rtt;
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_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
#if (NGX_QUIC_OPENSSL_COMPAT)
ngx_quic_compat_t *compat;
#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;
unsigned client_tp_done:1;
};
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);
#if (NGX_DEBUG)
void ngx_quic_connstate_dbg(ngx_connection_t *c);
#else
#define ngx_quic_connstate_dbg(c)
#endif
#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */

View file

@ -0,0 +1,502 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#define NGX_QUIC_MAX_SERVER_IDS 8
#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_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_send_server_id(ngx_connection_t *c,
ngx_quic_server_id_t *sid);
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
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_new_conn_id_frame_t *f)
{
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;
qc = ngx_quic_get_connection(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
* done so for that sequence number.
*/
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;
}
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)
{
/*
* ..if 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 {
id.data = f->cid;
id.len = f->len;
if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) {
return NGX_ERROR;
}
}
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;
}
if (ngx_quic_retire_client_id(c, cid) != NGX_OK) {
return NGX_ERROR;
}
}
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
* 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_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
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);
if (!cid->used) {
return ngx_quic_free_client_id(c, cid);
}
/* we are going to retire client id which is in use */
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->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;
}
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 seq:%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_client_id_t *cid;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
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->used) {
return 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_socket_t *qsock;
ngx_quic_connection_t *qc;
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;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%uL is retired", qsock->sid.seqnum);
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;
}
ngx_int_t
ngx_quic_create_sockets(ngx_connection_t *c)
{
ngx_uint_t n;
ngx_quic_socket_t *qsock;
ngx_quic_connection_t *qc;
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 create sockets has:%ui max:%ui", qc->nsockets, n);
while (qc->nsockets < n) {
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
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_frame_t *frame;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
dcid.len = sid->len;
dcid.data = sid->id;
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, sid->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;
}
ngx_int_t
ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid)
{
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 = ssl_encryption_application;
frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID;
frame->u.retire_cid.sequence_number = cid->seqnum;
ngx_quic_queue_frame(qc, frame);
/* 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;
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c,
ngx_quic_retire_cid_frame_t *f);
ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c,
ngx_quic_new_conn_id_frame_t *f);
ngx_int_t ngx_quic_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);
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_ */

View file

@ -0,0 +1,891 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#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_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl,
off_t offset);
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;
}
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)
{
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 if (qc->nframes < 10000) {
frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t));
if (frame == NULL) {
return NULL;
}
++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));
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_chain(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
}
void
ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in)
{
ngx_chain_t *cl;
while (in) {
cl = in;
in = in->next;
ngx_quic_free_buf(c, cl->buf);
ngx_free_chain(c->pool, 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_buffer_t qb;
ngx_quic_ordered_frame_t *of, *onf;
switch (f->type) {
case NGX_QUIC_FT_CRYPTO:
case NGX_QUIC_FT_STREAM:
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;
}
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;
}
*nf = *f;
onf = &nf->u.ord;
onf->offset += of->length;
onf->length = shrink;
nf->len = ngx_quic_create_frame(NULL, nf);
nf->data = qb.chain;
if (f->type == NGX_QUIC_FT_STREAM) {
f->u.stream.fin = 0;
}
ngx_queue_insert_after(&f->queue, &nf->queue);
return NGX_OK;
}
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)
{
uint64_t n;
ngx_buf_t *b;
ngx_chain_t *out, **ll;
out = qb->chain;
for (ll = &out; *ll; ll = &(*ll)->next) {
b = (*ll)->buf;
if (b->sync) {
/* hole */
break;
}
if (limit == 0) {
break;
}
n = b->last - b->pos;
if (n > limit) {
if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
n = limit;
}
limit -= n;
qb->offset += n;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
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;
}
if (qb->offset >= qb->last_offset) {
qb->last_chain = NULL;
}
}
ngx_chain_t *
ngx_quic_alloc_chain(ngx_connection_t *c)
{
ngx_chain_t *cl;
cl = ngx_alloc_chain_link(c->pool);
if (cl == NULL) {
return NULL;
}
cl->buf = ngx_quic_alloc_buf(c);
if (cl->buf == NULL) {
return NULL;
}
return cl;
}
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)
{
u_char *p;
uint64_t n, base;
ngx_buf_t *b;
ngx_chain_t *cl, **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) {
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) {
cl = ngx_quic_alloc_chain(c);
if (cl == NULL) {
return NGX_CHAIN_ERROR;
}
cl->buf->last = cl->buf->end;
cl->buf->sync = 1; /* hole */
cl->next = NULL;
*chain = cl;
}
b = cl->buf;
n = b->last - b->pos;
if (base + n <= offset) {
base += n;
chain = &cl->next;
continue;
}
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 - base);
while (in) {
if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) {
in = in->next;
continue;
}
if (p == b->last || limit == 0) {
break;
}
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);
qb->size += n;
}
p += n;
in->buf->pos += n;
offset += n;
limit -= n;
}
if (b->sync && p == b->last) {
b->sync = 0;
continue;
}
if (b->sync && p != b->pos) {
if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) {
return NGX_CHAIN_ERROR;
}
b->sync = 0;
}
}
qb->last_offset = base;
qb->last_chain = *chain;
return 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
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);
#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:
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_STREAM:
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");
#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:
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

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
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_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,
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);
#else
#define ngx_quic_log_frame(log, f, tx)
#endif
#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */

View file

@ -0,0 +1,671 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
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_path_t *path);
static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c,
ngx_quic_path_t *path);
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_header_t *pkt, ngx_quic_path_challenge_frame_t *f)
{
ngx_quic_frame_t frame, *fp;
ngx_quic_connection_t *qc;
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;
/*
* RFC 9000, 8.2.2. Path Validation Responses
*
* A PATH_RESPONSE frame MUST be sent on the network path where the
* PATH_CHALLENGE frame was received.
*/
/*
* 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, pkt->path) != NGX_OK) {
return NGX_ERROR;
}
if (pkt->path == qc->path) {
/*
* 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.
*/
fp = ngx_quic_alloc_frame(c);
if (fp == NULL) {
return NGX_ERROR;
}
fp->level = ssl_encryption_application;
fp->type = NGX_QUIC_FT_PING;
ngx_quic_queue_frame(qc, fp);
}
return NGX_OK;
}
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;
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.
*/
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;
}
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_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stale PATH_RESPONSE ignored");
return NGX_OK;
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.
*/
rst = 1;
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)
== 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,
ngx_max(2 * qc->tp.max_udp_payload_size,
14720));
qc->congestion.ssthresh = (size_t) -1;
qc->congestion.recovery_start = ngx_current_msec;
}
/*
* 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.
*/
if (ngx_quic_send_new_token(c, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"quic path seq:%uL addr:%V successfully validated",
path->seqnum, &path->addr_text);
ngx_quic_path_dbg(c, "is validated", path);
path->validated = 1;
path->validating = 0;
path->limited = 0;
return NGX_OK;
}
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;
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);
ngx_memzero(path, sizeof(ngx_quic_path_t));
} else {
path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t));
if (path == NULL) {
return NULL;
}
}
ngx_queue_insert_tail(&qc->paths, &path->queue);
path->cid = cid;
cid->used = 1;
path->limited = 1;
path->seqnum = qc->path_seqnum++;
path->sockaddr = &path->sa.sockaddr;
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_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%uL created addr:%V",
path->seqnum, &path->addr_text);
return path;
}
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;
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 (path->tag == tag) {
return path;
}
}
return NULL;
}
ngx_int_t
ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
off_t len;
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;
qc = ngx_quic_get_connection(c);
qsock = ngx_quic_get_socket(c);
len = pkt->raw->last - pkt->raw->start;
if (c->udp->buffer == NULL) {
/* first ever packet in connection, path already exists */
path = qc->path;
goto update;
}
probe = NULL;
for (q = ngx_queue_head(&qc->paths);
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(&qsock->sockaddr.sockaddr, qsock->socklen,
path->sockaddr, path->socklen, 1)
== NGX_OK)
{
goto update;
}
if (path->tag == NGX_QUIC_PATH_PROBE) {
probe = path;
}
}
/* 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;
}
/* new path requires new client id */
cid = ngx_quic_next_client_id(c);
if (cid == NULL) {
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;
}
path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid);
if (path == NULL) {
return NGX_ERROR;
}
path->tag = NGX_QUIC_PATH_PROBE;
/*
* client arrived using new path and previously seen DCID,
* this indicates NAT rebinding (or bad client)
*/
if (qsock->used) {
pkt->rebound = 1;
}
update:
qsock->used = 1;
pkt->path = path;
/* 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_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"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);
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 seq:%uL addr:%V retired",
path->seqnum, &path->addr_text);
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 seq:%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, *bkp;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
/* got non-probing packet via non-active path */
qc = ngx_quic_get_connection(c);
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.
*/
if (pkt->pn != ctx->largest_pn) {
return NGX_OK;
}
next = pkt->path;
/*
* RFC 9000, 9.3.3:
*
* 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) {
if (next->tag != NGX_QUIC_PATH_BACKUP) {
/* can delete backup path, if any */
bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP);
if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) {
return NGX_ERROR;
}
}
qc->path->tag = NGX_QUIC_PATH_BACKUP;
ngx_quic_path_dbg(c, "is now backup", qc->path);
} else {
if (ngx_quic_free_path(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/* 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 migrated to path seq:%uL addr:%V",
qc->path->seqnum, &qc->path->addr_text);
ngx_quic_path_dbg(c, "is now active", qc->path);
return NGX_OK;
}
static ngx_int_t
ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path)
{
ngx_msec_t pto;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
qc = ngx_quic_get_connection(c);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic initiated validation of path seq:%uL", path->seqnum);
path->validating = 1;
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)
{
ngx_quic_frame_t frame;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic path seq:%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;
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
* sending a datagram of this size.
*/
/* same applies to PATH_RESPONSE frames */
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
ngx_memcpy(frame.u.path_challenge.data, path->challenge2, 8);
if (ngx_quic_frame_sendto(c, &frame, 1200, path) != NGX_OK) {
return NGX_ERROR;
}
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, *bkp;
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;
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->validating) {
continue;
}
left = path->expires - now;
if (left > 0) {
if (next == -1 || left < next) {
next = left;
}
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 seq:%uL validation failed", path->seqnum);
/* found expired path */
path->validated = 0;
path->validating = 0;
path->limited = 1;
/* 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->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 seq:%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_free_path(c, path) != NGX_OK) {
ngx_quic_close_connection(c, NGX_ERROR);
return;
}
}
if (next != -1) {
ngx_add_timer(&qc->path_validation, next);
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#define NGX_QUIC_PATH_RETRIES 3
#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 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": "");
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_path_challenge_frame_t *f);
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_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);
void ngx_quic_path_validation_handler(ngx_event_t *ev);
#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */

View file

@ -0,0 +1,646 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#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 */

View file

@ -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 <ngx_config.h>
#include <ngx_core.h>
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_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
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);
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_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);
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_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event_quic_transport.h>
#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
#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];
} 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;
};
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,
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_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_ */

View file

@ -0,0 +1,237 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
ngx_int_t
ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc,
ngx_quic_header_t *pkt)
{
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
* 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 (id auto-generated) */
qsock = ngx_quic_create_socket(c, qc);
if (qsock == NULL) {
return NGX_ERROR;
}
/* socket is listening at new server id */
if (ngx_quic_listen(c, qc, qsock) != NGX_OK) {
return NGX_ERROR;
}
qsock->used = 1;
qc->tp.initial_scid.len = qsock->sid.len;
qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len);
if (qc->tp.initial_scid.data == NULL) {
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;
}
/* 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) {
qc->path->validated = 1;
qc->path->limited = 0;
}
ngx_quic_path_dbg(c, "set active", qc->path);
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;
}
return NGX_OK;
failed:
ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node);
c->udp = NULL;
return NGX_ERROR;
}
ngx_quic_socket_t *
ngx_quic_create_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;
}
}
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;
}
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--;
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L closed nsock:%ui",
(int64_t) qsock->sid.seqnum, qc->nsockets);
}
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;
id.data = sid->id;
id.len = sid->len;
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);
qc->nsockets++;
qsock->quic = qc;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic socket seq:%L listening at sid:%xV nsock:%ui",
(int64_t) sid->seqnum, &id, qc->nsockets);
return NGX_OK;
}
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);
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;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
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_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);
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);
#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */

View file

@ -0,0 +1,600 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
#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
*
* Implementations MUST support buffering at least 4096 bytes of data
*/
#define NGX_QUIC_MAX_BUFFERED 65535
#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);
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_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data);
#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 *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
if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level,
cipher, rsecret, 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 *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
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, 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 *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->log, 0, qc->keys, level,
cipher, rsecret, secret_len)
!= NGX_OK)
{
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
if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level,
cipher, wsecret, secret_len)
!= NGX_OK)
{
return 0;
}
return 1;
}
#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;
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_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);
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)
SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len);
if (alpn_len == 0) {
qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL);
qc->error_reason = "unsupported protocol in ALPN extension";
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) {
/* RFC 9001, 8.2. QUIC Transport Parameters 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,
"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;
}
ctx = ngx_quic_get_send_ctx(qc, level);
out = ngx_quic_copy_buffer(c, (u_char *) data, len);
if (out == NGX_CHAIN_ERROR) {
return 0;
}
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;
frame->u.crypto.length = len;
ctx->crypto_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() 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);
qc->error_reason = "handshake failed";
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_chain_t *cl;
ngx_quic_send_ctx_t *ctx;
ngx_quic_connection_t *qc;
ngx_quic_crypto_frame_t *f;
qc = ngx_quic_get_connection(c);
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 > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) {
qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED;
return NGX_ERROR;
}
if (last <= ctx->crypto.offset) {
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;
}
if (f->offset == ctx->crypto.offset) {
if (ngx_quic_crypto_input(c, frame->data) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_skip_buffer(c, &ctx->crypto, last);
} else {
if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length,
f->offset)
== NGX_CHAIN_ERROR)
{
return NGX_ERROR;
}
}
cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1);
if (cl) {
if (ngx_quic_crypto_input(c, cl) != NGX_OK) {
return NGX_ERROR;
}
ngx_quic_free_chain(c, cl);
}
return NGX_OK;
}
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);
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 = 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) {
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;
}
}
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;
}
#if (NGX_DEBUG)
ngx_ssl_handshake_log(c);
#endif
c->ssl->handshaked = 1;
frame = ngx_quic_alloc_frame(c);
if (frame == NULL) {
return NGX_ERROR;
}
frame->level = ssl_encryption_application;
frame->type = NGX_QUIC_FT_HANDSHAKE_DONE;
ngx_quic_queue_frame(qc, frame);
if (qc->conf->retry) {
if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) {
return NGX_ERROR;
}
}
/*
* RFC 9001, 9.5. Header Protection Timing Side Channels
*
* Generating next keys before a key update is received.
*/
if (ngx_quic_keys_update(c, qc->keys) != NGX_OK) {
return NGX_ERROR;
}
/*
* RFC 9001, 4.9.2. Discarding Handshake Keys
*
* An endpoint MUST discard its Handshake keys
* when the TLS handshake is confirmed.
*/
ngx_quic_discard_ctx(c, ssl_encryption_handshake);
/* start accepting clients on negotiated number of server ids */
if (ngx_quic_create_sockets(c) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_quic_init_streams(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_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);
if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) {
return NGX_ERROR;
}
c->ssl->no_wait_shutdown = 1;
ssl_conn = c->ssl->connection;
if (!quic_method.send_alert) {
#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
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");
return NGX_ERROR;
}
#ifdef OPENSSL_INFO_QUIC
if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) {
SSL_set_quic_early_data_enabled(ssl_conn, 1);
}
#endif
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)
{
return NGX_ERROR;
}
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;
}
#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");
return NGX_ERROR;
}
#endif
return NGX_OK;
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
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);
#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c,
ngx_quic_header_t *pkt, ngx_quic_frame_t *frame);
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_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,
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);
ngx_int_t ngx_quic_init_streams(ngx_connection_t *c);
void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree,
uint64_t id);
ngx_int_t ngx_quic_close_streams(ngx_connection_t *c,
ngx_quic_connection_t *qc);
#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */

View file

@ -0,0 +1,289 @@
/*
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_sha1.h>
#include <ngx_event_quic_connection.h>
static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen,
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;
}
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic stateless reset token %*xs",
(size_t) NGX_QUIC_SR_TOKEN_LEN, token);
return NGX_OK;
}
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 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(sockaddr, socklen, !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 = NGX_QUIC_AES_256_CBC_IV_LEN;
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;
}
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, log, 0,
"quic new token len:%uz %xV", token->len, token);
#endif
return NGX_OK;
}
static void
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;
ngx_sha1_t sha1;
struct sockaddr_in *sin;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
#endif
len = (size_t) socklen;
data = (u_char *) sockaddr;
if (no_port) {
switch (sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sockaddr;
len = sizeof(struct in6_addr);
data = sin6->sin6_addr.s6_addr;
break;
#endif
case AF_INET:
sin = (struct sockaddr_in *) 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];
#if NGX_SUPPRESS_WARN
ngx_str_null(&odcid);
#endif
/* Retry token or NEW_TOKEN in a previous connection */
cipher = EVP_aes_256_cbc();
iv = pkt->token.data;
iv_len = NGX_QUIC_AES_256_CBC_IV_LEN;
/* sanity checks */
if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_CBC_BLOCK_SIZE) {
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->sockaddr, c->socklen, !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;
}
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 = pkt->odcid_buf;
ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len);
} 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;
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#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_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,
u_char *key, ngx_quic_header_t *pkt);
#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,397 @@
/*
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
/*
* 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 */
#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"
#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
#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
#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
#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE
/* 22.5. QUIC Transport Error Codes Registry */
#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_AEAD_LIMIT_REACHED 0x0F
#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10
#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100
#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e))
/* 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
#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;
} 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;
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_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;
unsigned need_ack:1;
unsigned pkt_need_ack:1;
unsigned flush: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_path_t *path;
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 */
u_char odcid_buf[NGX_QUIC_MAX_CID_LEN];
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;
unsigned validated:1;
unsigned retried:1;
unsigned first:1;
unsigned rebound:1;
} 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);
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,
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_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,
ngx_quic_tp_t *tp, size_t *clen);
void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key);
#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */

View file

@ -0,0 +1,473 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_event_quic_connection.h>
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;
}

View file

@ -9,6 +9,10 @@
#include <ngx_core.h>
#include <ngx_http.h>
#if (NGX_QUIC_OPENSSL_COMPAT)
#include <ngx_event_quic_openssl_compat.h>
#endif
typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
ngx_pool_t *pool, ngx_str_t *s);
@ -52,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[] = {
@ -424,10 +432,13 @@ 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)
#if (NGX_HTTP_V3)
ngx_http_v3_srv_conf_t *h3scf;
#endif
#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG)
ngx_connection_t *c;
c = ngx_ssl_get_connection(ssl_conn);
@ -441,13 +452,40 @@ 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_PROTO NGX_HTTP_ALPN_PROTOS;
srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1;
} else
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->quic) {
h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module);
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 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
#endif
{
srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS;
@ -1241,6 +1279,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;
@ -1290,22 +1329,44 @@ 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) {
if (!addr[a].opt.ssl && !addr[a].opt.quic) {
continue;
}
if (addr[a].opt.quic) {
name = "quic";
#if (NGX_QUIC_OPENSSL_COMPAT)
if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != 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.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;
}
@ -1326,8 +1387,8 @@ ngx_http_ssl_init(ngx_conf_t *cf)
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;
}
}
@ -1335,3 +1396,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

View file

@ -1200,7 +1200,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;
}
@ -1217,6 +1220,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;
@ -1237,6 +1241,10 @@ 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_V3)
ngx_uint_t http3;
ngx_uint_t quic;
#endif
/*
* we cannot compare whole sockaddr struct's as kernel
@ -1278,6 +1286,10 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf,
protocols |= lsopt->http2 << 2;
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
if (lsopt->set) {
@ -1365,6 +1377,10 @@ 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_V3)
addr[i].opt.http3 = http3;
addr[i].opt.quic = quic;
#endif
return NGX_OK;
}
@ -1831,6 +1847,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;
@ -1864,6 +1881,19 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
#if (NGX_HAVE_REUSEPORT)
ls->reuseport = addr->opt.reuseport;
#endif
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;
@ -1897,6 +1927,10 @@ 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_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;
@ -1962,6 +1996,10 @@ 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_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

@ -20,6 +20,8 @@ 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 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);
@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r,
#if (NGX_HTTP_V2)
#include <ngx_http_v2.h>
#endif
#if (NGX_HTTP_V3)
#include <ngx_http_v3.h>
#endif
#if (NGX_HTTP_CACHE)
#include <ngx_http_cache.h>
#endif
@ -124,6 +129,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);
@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *);
#endif
#if (NGX_HTTP_V2)
#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);
size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst,

View file

@ -3005,6 +3005,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)
@ -3986,6 +3987,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)
@ -4184,6 +4186,36 @@ 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;
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) {
@ -4285,6 +4317,28 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
return NGX_CONF_ERROR;
}
#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++) {
for (i = 0; i < n; i++) {

View file

@ -75,6 +75,8 @@ typedef struct {
unsigned wildcard:1;
unsigned ssl:1;
unsigned http2:1;
unsigned http3:1;
unsigned quic:1;
#if (NGX_HAVE_INET6)
unsigned ipv6only:1;
#endif
@ -86,6 +88,7 @@ typedef struct {
int backlog;
int rcvbuf;
int sndbuf;
int type;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
@ -237,6 +240,8 @@ struct ngx_http_addr_conf_s {
unsigned ssl:1;
unsigned http2:1;
unsigned http3:1;
unsigned quic:1;
unsigned proxy_protocol:1;
};
@ -266,6 +271,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;

View file

@ -29,10 +29,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);
@ -50,7 +46,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(ngx_log_t *log, u_char *buf, size_t len);
@ -329,6 +324,13 @@ ngx_http_init_connection(ngx_connection_t *c)
}
#endif
#if (NGX_HTTP_V3)
if (hc->addr_conf->quic) {
ngx_http_v3_init_stream(c);
return;
}
#endif
#if (NGX_HTTP_SSL)
{
ngx_http_ssl_srv_conf_t *sscf;
@ -949,6 +951,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_V3)
if (c->listening->quic) {
SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
}
#endif
#endif
}
@ -2095,7 +2105,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;
@ -2187,7 +2197,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;
@ -2710,6 +2720,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->connection->quic) {
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) {
@ -2925,6 +2942,20 @@ ngx_http_test_reading(ngx_http_request_t *r)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
if (rev->error) {
c->error = 1;
err = 0;
goto closed;
}
return;
}
#endif
#if (NGX_HAVE_KQUEUE)
if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
@ -3590,7 +3621,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;
@ -3677,7 +3708,12 @@ 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_V3)
&& r->connection->quic == NULL
#endif
)
{
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (clcf->reset_timedout_connection) {
@ -3750,6 +3786,12 @@ ngx_http_close_connection(ngx_connection_t *c)
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_v3_reset_stream(c);
}
#endif
#if (NGX_STAT_STUB)
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
#endif

View file

@ -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 0x00000001
#define NGX_HTTP_GET 0x00000002
@ -323,6 +324,10 @@ typedef struct {
#endif
#endif
#if (NGX_HTTP_V3 || NGX_COMPAT)
ngx_http_v3_session_t *v3_session;
#endif
ngx_chain_t *busy;
ngx_int_t nbusy;
@ -451,6 +456,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;
@ -543,6 +549,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;

View file

@ -92,6 +92,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) {
@ -238,6 +245,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;
@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (r->http_version == NGX_HTTP_VERSION_30) {
return NGX_OK;
}
#endif
if (ngx_http_test_expect(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
@ -920,6 +945,9 @@ ngx_http_test_expect(ngx_http_request_t *r)
|| r->http_version < NGX_HTTP_VERSION_11
#if (NGX_HTTP_V2)
|| r->stream != NULL
#endif
#if (NGX_HTTP_V3)
|| r->connection->quic != NULL
#endif
)
{

View file

@ -521,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_t *r)
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
ngx_http_upstream_init_request(r);
return;
}
#endif
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
@ -1354,6 +1361,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
}
#endif
#if (NGX_HTTP_V3)
if (c->quic) {
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) {

View file

@ -240,6 +240,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;
}
@ -346,6 +350,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;
}

116
src/http/v3/ngx_http_v3.c Normal file
View file

@ -0,0 +1,116 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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_pool_cleanup_t *cln;
ngx_http_connection_t *hc;
ngx_http_v3_session_t *h3c;
hc = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session");
h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t));
if (h3c == NULL) {
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;
h3c->keepalive.handler = ngx_http_v3_keepalive_handler;
h3c->table.send_insert_count.log = c->log;
h3c->table.send_insert_count.data = c;
h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler;
cln = ngx_pool_cleanup_add(c->pool, 0);
if (cln == NULL) {
goto failed;
}
cln->handler = ngx_http_v3_cleanup_session;
cln->data = h3c;
hc->v3_session = h3c;
return NGX_OK;
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;
}
static void
ngx_http_v3_keepalive_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
c = ev->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler");
ngx_http_v3_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;
ngx_http_v3_cleanup_table(h3c);
if (h3c->keepalive.timer_set) {
ngx_del_timer(&h3c->keepalive);
}
if (h3c->table.send_insert_count.posted) {
ngx_delete_posted_event(&h3c->table.send_insert_count);
}
}
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;
}

170
src/http/v3/ngx_http_v3.h Normal file
View file

@ -0,0 +1,170 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_H_INCLUDED_
#define _NGX_HTTP_V3_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_http_v3_parse.h>
#include <ngx_http_v3_encode.h>
#include <ngx_http_v3_uni.h>
#include <ngx_http_v3_table.h>
#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
#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_FIELD_SECTION_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
#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
#define NGX_HTTP_V3_MAX_UNI_STREAMS 3
/* 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_quic_get_connection(c) \
((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
#define ngx_http_v3_get_module_loc_conf(c, 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_quic_get_connection(c)->conf_ctx, \
module)
#define ngx_http_v3_finalize_connection(c, code, reason) \
ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
#define ngx_http_v3_shutdown_connection(c, code, reason) \
ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \
code, reason)
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;
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;
ngx_http_v3_parse_data_t body;
ngx_array_t *cookies;
};
struct ngx_http_v3_session_s {
ngx_http_v3_dynamic_table_t table;
ngx_event_t keepalive;
ngx_uint_t nrequests;
ngx_queue_t blocked;
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;
off_t payload_bytes;
unsigned goaway:1;
unsigned hq:1;
ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM];
};
void ngx_http_v3_init_stream(ngx_connection_t *c);
void ngx_http_v3_reset_stream(ngx_connection_t *c);
ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c);
ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c);
ngx_int_t ngx_http_v3_init(ngx_connection_t *c);
void ngx_http_v3_shutdown(ngx_connection_t *c);
ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r);
ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r);
extern ngx_module_t ngx_http_v3_module;
#endif /* _NGX_HTTP_V3_H_INCLUDED_ */

View file

@ -0,0 +1,304 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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_field_section_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_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index)
{
/* Indexed Field Line */
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_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) {
return ngx_http_v3_encode_prefix_int(NULL, index, 4)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*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) {
p2 = p;
hlen = ngx_http_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;
}
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) {
return ngx_http_v3_encode_prefix_int(NULL, name->len, 3)
+ name->len
+ ngx_http_v3_encode_prefix_int(NULL, value->len, 7)
+ value->len;
}
p1 = p;
*p = 0x20;
p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3);
p2 = p;
hlen = ngx_http_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);
p2 = p;
hlen = ngx_http_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;
}
uintptr_t
ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index)
{
/* Indexed Field Line 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_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) {
return ngx_http_v3_encode_prefix_int(NULL, index, 3)
+ ngx_http_v3_encode_prefix_int(NULL, len, 7)
+ len;
}
*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) {
p2 = p;
hlen = ngx_http_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;
}

View file

@ -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 <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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_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_field_ri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index);
uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic,
ngx_uint_t index, u_char *data, size_t len);
uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name,
ngx_str_t *value);
uintptr_t ngx_http_v3_encode_field_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);
#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,554 @@
/*
* Copyright (C) Nginx, Inc.
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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);
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);
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_mtu_post =
{ ngx_http_quic_mtu };
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,
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,
NGX_HTTP_SRV_CONF_OFFSET,
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,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size),
NULL },
{ 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_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,
NGX_HTTP_SRV_CONF_OFFSET,
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
};
static ngx_http_module_t ngx_http_v3_module_ctx = {
ngx_http_v3_add_variables, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
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 */
};
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
};
static ngx_http_variable_t ngx_http_v3_vars[] = {
{ ngx_string("http3"), NULL, ngx_http_v3_variable, 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(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_v3_session_t *h3c;
if (r->connection->quic) {
h3c = ngx_http_v3_get_session(r->connection);
if (h3c->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;
}
v->len = sizeof("h3") - 1;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
v->data = (u_char *) "h3";
return NGX_OK;
}
*v = ngx_http_variable_null_value;
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)
{
ngx_http_v3_srv_conf_t *h3scf;
h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t));
if (h3scf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* 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->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.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;
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;
}
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_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);
ngx_conf_merge_uint_value(conf->max_concurrent_streams,
prev->max_concurrent_streams, 128);
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);
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);
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;
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_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)
{
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;
}
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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,146 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_
#define _NGX_HTTP_V3_PARSE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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 shift;
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_field_section_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_field_t;
typedef struct {
ngx_uint_t state;
ngx_http_v3_parse_field_t field;
} ngx_http_v3_parse_field_rep_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_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_field_t field;
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;
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;
ngx_uint_t length;
ngx_http_v3_parse_varlen_int_t vlint;
} 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_headers(ngx_connection_t *c,
ngx_http_v3_parse_headers_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c,
ngx_http_v3_parse_data_t *st, ngx_buf_t *b);
ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c,
ngx_http_v3_parse_uni_t *st, ngx_buf_t *b);
#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,715 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#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 target);
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);
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);
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;
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) {
return NGX_ERROR;
}
return NGX_OK;
}
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)
{
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;
}
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;
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 target)
{
size_t size;
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;
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;
}
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)
{
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_FIELD_SECTION_SIZE:
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http3 param SETTINGS_MAX_FIELD_SECTION_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;
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_
#define _NGX_HTTP_V3_TABLE_H_INCLUDED_
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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;
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);
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);
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);
#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */

View file

@ -0,0 +1,781 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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_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);
void
ngx_http_v3_init_uni_stream(ngx_connection_t *c)
{
uint64_t n;
ngx_http_v3_session_t *h3c;
ngx_http_v3_uni_stream_t *us;
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;
}
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;
}
ngx_quic_cancelable_stream(c);
us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t));
if (us == NULL) {
ngx_http_v3_finalize_connection(c,
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_uni_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");
if (c->close) {
ngx_http_v3_close_uni_stream(c);
return;
}
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_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 (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);
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;
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);
}
}
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;
}
ngx_quic_cancelable_stream(sc);
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_dummy_read_handler;
sc->write->handler = ngx_http_v3_uni_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;
}
ngx_post_event(sc->read, &ngx_posted_events);
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;
}

View file

@ -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 <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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_ */

View file

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

View file

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

View file

@ -518,6 +518,24 @@ 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);
}
#endif
stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t));
if (stport == NULL) {
return NGX_CONF_ERROR;
@ -575,6 +593,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;
@ -610,6 +631,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;

View file

@ -16,6 +16,10 @@
#include <ngx_stream_ssl_module.h>
#endif
#if (NGX_STREAM_QUIC)
#include <ngx_stream_quic_module.h>
#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
@ -76,6 +81,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;

View file

@ -760,6 +760,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) {
@ -871,6 +894,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\"";
}

View file

@ -129,6 +129,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;
}
@ -173,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;

View file

@ -1772,6 +1772,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");

View file

@ -0,0 +1,377 @@
/*
* Copyright (C) Nginx, Inc.
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
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_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[] = {
{ 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_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,
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->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;
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->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);
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_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)
{
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;
}

View file

@ -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 <ngx_config.h>
#include <ngx_core.h>
#include <ngx_stream.h>
extern ngx_module_t ngx_stream_quic_module;
#endif /* _NGX_STREAM_QUIC_H_INCLUDED_ */

View file

@ -9,6 +9,10 @@
#include <ngx_core.h>
#include <ngx_stream.h>
#if (NGX_QUIC_OPENSSL_COMPAT)
#include <ngx_event_quic_openssl_compat.h>
#endif
typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c,
ngx_pool_t *pool, ngx_str_t *s);
@ -1195,7 +1199,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);
@ -1207,5 +1214,29 @@ 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;
}