diff options
Diffstat (limited to '')
-rw-r--r-- | doc/source/programmers-guide.rst | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/doc/source/programmers-guide.rst b/doc/source/programmers-guide.rst new file mode 100644 index 0000000..5138361 --- /dev/null +++ b/doc/source/programmers-guide.rst @@ -0,0 +1,446 @@ +The ngtcp2 programmers' guide for early adopters +================================================ + +This document is written for early adopters of ngtcp2 library. It +describes a brief introduction of programming ngtcp2. + +Prerequisites +------------- + +Reading :rfc:`9000` and :rfc:`9001` helps you a lot to write QUIC +application. They describes how TLS is integrated into QUIC and why +the existing TLS stack cannot be used with QUIC. + +QUIC requires the special interface from TLS stack, which is probably +not available from most of the existing TLS stacks. As far as I know, +the TLS stacks maintained by the active participants of QUIC working +group only get this interface at the time of this writing. In order +to build QUIC application you have to choose one of them. Here is the +list of TLS stacks which are supposed to provide such interface and +for which we provide crypto helper libraries: + +* `OpenSSL with QUIC support + <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1q+quic>`_ +* GnuTLS >= 3.7.2 +* BoringSSL +* Picotls +* wolfSSL + +Creating ngtcp2_conn object +--------------------------- + +:type:`ngtcp2_conn` is the primary object to present a single QUIC +connection. Use `ngtcp2_conn_client_new()` for client application, +and `ngtcp2_conn_server_new()` for server. + +They require :type:`ngtcp2_callbacks`, :type:`ngtcp2_settings`, and +:type:`ngtcp2_transport_params` objects. + +The :type:`ngtcp2_callbacks` contains the callback functions which +:type:`ngtcp2_conn` calls when a specific event happens, say, +receiving stream data or stream is closed, etc. Some of the callback +functions are optional. For client application, the following +callback functions must be set: + +* :member:`client_initial <ngtcp2_callbacks.client_initial>`: + `ngtcp2_crypto_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`recv_retry <ngtcp2_callbacks.recv_retry>`: + `ngtcp2_crypto_recv_retry_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +For server application, the following callback functions must be set: + +* :member:`recv_client_initial + <ngtcp2_callbacks.recv_client_initial>`: + `ngtcp2_crypto_recv_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +``ngtcp2_crypto_*`` functions are a part of :doc:`ngtcp2 crypto API +<crypto_apiref>` which provides easy integration with the supported +TLS backend. It vastly simplifies TLS integration and is strongly +recommended. + +:type:`ngtcp2_settings` contains the settings for QUIC connection. +All fields must be set. Application should call +`ngtcp2_settings_default()` to set the default values. It would be +very useful to enable debug logging by setting logging function to +:member:`ngtcp2_settings.log_printf` field. ngtcp2 library relies on +the timestamp fed from application. The initial timestamp must be +passed to :member:`ngtcp2_settings.initial_ts` field in nanosecond +resolution. ngtcp2 cares about the difference from that initial +value. It could be any timestamp which increases monotonically, and +actual value does not matter. + +:type:`ngtcp2_transport_params` contains QUIC transport parameters +which is sent to a remote endpoint during handshake. All fields must +be set. Application should call `ngtcp2_transport_params_default()` +to set the default values. + +Client application has to supply Connection IDs to +`ngtcp2_conn_client_new()`. The *dcid* parameter is the destination +connection ID (DCID), and which should be random byte string and at +least 8 bytes long. The *scid* is the source connection ID (SCID) +which identifies the client itself. The *version* parameter is the +QUIC version to use. It should be :macro:`NGTCP2_PROTO_VER_V1`. + +Similarly, server application has to supply these parameters to +`ngtcp2_conn_server_new()`. But the *dcid* must be the same value +which is received from client (which is client SCID). The *scid* is +chosen by server. Don't use DCID in client packet as server SCID. +The *version* parameter is the QUIC version to use. It should be +:macro:`NGTCP2_PROTO_VER_V1`. + +A path is very important to QUIC connection. It is the pair of +endpoints, local and remote. The path passed to +`ngtcp2_conn_client_new()` and `ngtcp2_conn_server_new()` is a network +path that handshake is performed. The path must not change during +handshake. After handshake is confirmed, client can migrate to new +path. An application must provide actual path to the API function to +tell the library where a packet comes from. The "write" API function +takes path parameter and fills it to which the packet should be sent. + +TLS integration +--------------- + +Use of :doc:`ngtcp2 crypto API <crypto_apiref>` is strongly +recommended because it vastly simplifies the TLS integration. + +The most of the TLS work is done by the callback functions passed to +:type:`ngtcp2_callbacks` object. There are some operations left to +application in order to make TLS integration work. We have a set of +helper functions to make it easier for applications to configure TLS +stack object to work with QUIC and ngtcp2. They are specific to each +supported TLS stack: + +- OpenSSL + + * `ngtcp2_crypto_openssl_configure_client_context` + * `ngtcp2_crypto_openssl_configure_server_context` + +- BoringSSL + + * `ngtcp2_crypto_boringssl_configure_client_context` + * `ngtcp2_crypto_boringssl_configure_server_context` + +- GnuTLS + + * `ngtcp2_crypto_gnutls_configure_client_session` + * `ngtcp2_crypto_gnutls_configure_server_session` + +- Picotls + + * `ngtcp2_crypto_picotls_configure_client_context` + * `ngtcp2_crypto_picotls_configure_server_context` + * `ngtcp2_crypto_picotls_configure_client_session` + * `ngtcp2_crypto_picotls_configure_server_session` + +- wolfSSL + + * `ngtcp2_crypto_wolfssl_configure_client_context` + * `ngtcp2_crypto_wolfssl_configure_server_context` + +They make the minimal QUIC specific changes to TLS stack object. See +the ngtcp2 crypto API header files for each supported TLS stack. In +order to make these functions work, we require that a pointer to +:type:`ngtcp2_crypto_conn_ref` must be set as a user data in TLS stack +object, and its :member:`ngtcp2_crypto_conn_ref.get_conn` must point +to a function which returns :type:`ngtcp2_conn` of the underlying QUIC +connection. + +If you do not use the above helper functions, you need to generate and +install keys to :type:`ngtcp2_conn`, and pass handshake messages to +:type:`ngtcp2_conn` as well. When TLS stack generates new secrets, +they have to be installed to :type:`ngtcp2_conn` by calling +`ngtcp2_crypto_derive_and_install_rx_key()` and +`ngtcp2_crypto_derive_and_install_tx_key()`. When TLS stack generates +new crypto data to send, they must be passed to :type:`ngtcp2_conn` by +calling `ngtcp2_conn_submit_crypto_data()`. + +When QUIC handshake is completed, +:member:`ngtcp2_callbacks.handshake_completed` callback function is +called. The local and remote endpoint independently declare handshake +completion. The endpoint has to confirm that the other endpoint also +finished handshake. When the handshake is confirmed, client side +:type:`ngtcp2_conn` will call +:member:`ngtcp2_callbacks.handshake_confirmed` callback function. +Server confirms handshake when it declares handshake completion, +therefore, separate handshake confirmation callback is not called. + +Read and write packets +---------------------- + +`ngtcp2_conn_read_pkt()` processes the incoming QUIC packets. In +order to write QUIC packets, call `ngtcp2_conn_writev_stream()` or +`ngtcp2_conn_write_pkt()`. The *destlen* parameter must be at least +the value returned from `ngtcp2_conn_get_max_tx_udp_payload_size()`. + +In order to send stream data, the application has to first open a +stream. Use `ngtcp2_conn_open_bidi_stream()` to open bidirectional +stream. For unidirectional stream, call +`ngtcp2_conn_open_uni_stream()`. Call `ngtcp2_conn_writev_stream()` +to send stream data. + +An application should pace sending packets. +`ngtcp2_conn_get_send_quantum()` returns the number of bytes that can +be sent without packet spacing. After one or more calls of +`ngtcp2_conn_writev_stream()` (it can be called multiple times to fill +the buffer sized up to `ngtcp2_conn_get_send_quantum()` bytes), call +`ngtcp2_conn_update_pkt_tx_time()` to set the timer when the next +packet should be sent. The timer is integrated into +`ngtcp2_conn_get_expiry()`. + +Packet handling on server side +------------------------------ + +Any incoming UDP datagram should be first processed by +`ngtcp2_pkt_decode_version_cid()`. It can handle Connection ID more +than 20 bytes which is the maximum length defined in QUIC v1. If the +function returns :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`, server +should send Version Negotiation packet. Use +`ngtcp2_pkt_write_version_negotiation()` for this purpose. If +`ngtcp2_pkt_decode_version_cid()` succeeds, then check whether the UDP +datagram belongs to any existing connection by looking up connection +tables by Destination Connection ID. If it belongs to an existing +connection, pass the UDP datagram to `ngtcp2_conn_read_pkt()`. If it +does not belong to any existing connection, it should be passed to +`ngtcp2_accept()`. If it returns :macro:`NGTCP2_ERR_RETRY`, the +server should send Retry packet (use `ngtcp2_crypto_write_retry()` to +create Retry packet). If it returns an other negative error code, +just drop the packet to the floor and take no action, or send +Stateless Reset packet (use `ngtcp2_pkt_write_stateless_reset()` to +create Stateless Reset packet). Otherwise, the UDP datagram is +acceptable as a new connection. Create :type:`ngtcp2_conn` object and +pass the UDP datagram to `ngtcp2_conn_read_pkt()`. + +Dealing with early data +----------------------- + +Client application has to load resumed TLS session. It also has to +set the remembered transport parameters using +`ngtcp2_conn_set_early_remote_transport_params()` function. + +Other than that, there is no difference between early data and 1RTT +data in terms of API usage. + +If early data is rejected by a server, client must call +`ngtcp2_conn_early_data_rejected`. All connection states altered +during early data transmission are undone. The library does not +retransmit early data to server as 1RTT data. If an application +wishes to resend data, it has to reopen streams and writes data again. +See `ngtcp2_conn_early_data_rejected`. + +Stream data ownership +-------------------------------- + +Stream data passed to :type:`ngtcp2_conn` must be held by application +until :member:`ngtcp2_callbacks.acked_stream_data_offset` callbacks is +invoked, telling that the those data are acknowledged by the remote +endpoint and no longer used by the library. + +Timers +------ + +The library does not ask an operating system for any timestamp. +Instead, an application has to supply timestamp to the library. The +type of timestamp in ngtcp2 library is :type:`ngtcp2_tstamp` which is +nanosecond resolution. The library only cares the difference of +timestamp, so it does not have to be a system clock. A monotonic +clock should work better. It should be same clock passed to +:member:`ngtcp2_settings.initial_ts`. The duration in ngtcp2 library +is :type:`ngtcp2_duration` which is also nanosecond resolution. + +`ngtcp2_conn_get_expiry()` tells an application when timer fires. +When it fires, call `ngtcp2_conn_handle_expiry()`. If it returns +:macro:`NGTCP2_ERR_IDLE_CLOSE`, it means that an idle timer has fired +for this particular connection. In this case, drop the connection +without calling `ngtcp2_conn_write_connection_close()`. Otherwise, +call `ngtcp2_conn_writev_stream()`. After calling +`ngtcp2_conn_handle_expiry()` and `ngtcp2_conn_writev_stream()`, new +expiry is set. The application should call `ngtcp2_conn_get_expiry()` +to get a new deadline. + +Please note that :type:`ngtcp2_tstamp` of value ``UINT64_MAX`` is +treated as an invalid timestamp. Do not pass ``UINT64_MAX`` to any +ngtcp2 functions which take :type:`ngtcp2_tstamp` unless it is +explicitly allowed. + +Connection migration +-------------------- + +In QUIC, client application can migrate to a new local address. +`ngtcp2_conn_initiate_immediate_migration()` migrates to a new local +address without checking reachability. On the other hand, +`ngtcp2_conn_initiate_migration()` migrates to a new local address +after a new path is validated (thus reachability is established). + +Closing connection abruptly +--------------------------- + +In order to close QUIC connection abruptly, call +`ngtcp2_conn_write_connection_close()` and get a terminal packet. +After the call, the connection enters the closing state. + +The closing and draining state +------------------------------ + +After the successful call of `ngtcp2_conn_write_connection_close()`, +the connection enters the closing state. When +`ngtcp2_conn_read_pkt()` returns :macro:`NGTCP2_ERR_DRAINING`, the +connection has entered the draining state. In these states, +`ngtcp2_conn_writev_stream()` and `ngtcp2_conn_read_pkt()` return an +error (either :macro:`NGTCP2_ERR_CLOSING` or +:macro:`NGTCP2_ERR_DRAINING` depending on the state). +`ngtcp2_conn_write_connection_close()` returns 0 in these states. If +an application needs to send a packet containing CONNECTION_CLOSE +frame in the closing state, resend the packet produced by the first +call of `ngtcp2_conn_write_connection_close()`. Therefore, after a +connection has entered one of these states, the application can +discard :type:`ngtcp2_conn` object. The closing and draining state +should persist at least 3 times the current PTO. + +Error handling in general +------------------------- + +In general, when error is returned from the ngtcp2 library function, +call `ngtcp2_conn_write_connection_close()` to get terminal packet. +If the successful call of the function creates non-empty packet, the +QUIC connection enters the closing state. + +If :macro:`NGTCP2_ERR_DROP_CONN` is returned from +`ngtcp2_conn_read_pkt`, a connection should be dropped without calling +`ngtcp2_conn_write_connection_close()`. Similarly, if +:macro:`NGTCP2_ERR_IDLE_CLOSE` is returned from +`ngtcp2_conn_handle_expiry`, a connection should be dropped without +calling `ngtcp2_conn_write_connection_close()`. If +:macro:`NGTCP2_ERR_DRAINING` is returned from `ngtcp2_conn_read_pkt`, +a connection has entered the draining state, and no further packet +transmission is allowed. + +The following error codes must be considered as transitional, and +application should keep connection alive: + +* :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` +* :macro:`NGTCP2_ERR_STREAM_SHUT_WR` +* :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` +* :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + +Version negotiation +------------------- + +Version negotiation is configured with the following +:type:`ngtcp2_settings` fields: + +* :member:`ngtcp2_settings.preferred_versions` and + :member:`ngtcp2_settings.preferred_versionslen` +* :member:`ngtcp2_settings.other_versions` and + :member:`ngtcp2_settings.other_versionslen` +* :member:`ngtcp2_settings.original_version` + +*client_chosen_version* passed to `ngtcp2_conn_client_new` also +influence the version negotiation process. + +By default, client sends *client_chosen_version* passed to +`ngtcp2_conn_client_new` in other_versions field of +version_information QUIC transport parameter. That means there is no +chance for server to select the other compatible version. Meanwhile, +ngtcp2 supports QUIC v2 draft version +(:macro:`NGTCP2_PROTO_VER_V2_DRAFT`). Including both +:macro:`NGTCP2_PROTO_VER_V1` and :macro:`NGTCP2_PROTO_VER_V2_DRAFT` in +:member:`ngtcp2_settings.other_versions` field allows server to choose +:macro:`NGTCP2_PROTO_VER_V2_DRAFT` which is compatible to +:macro:`NGTCP2_PROTO_VER_V1`. + +By default, server sends :macro:`NGTCP2_PROTO_VER_V1` in +other_versions field of version_information QUIC transport parameter. +Because there is no particular preferred versions specified, server +will accept any supported version. In order to set the version +preference, specify :member:`ngtcp2_settings.preferred_versions` +field. If it is specified, server sends them in other_versions field +of version_information QUIC transport parameter unless +:member:`ngtcp2_settings.other_versionslen` is not zero. Specifying +:member:`ngtcp2_settings.other_versions` overrides the above mentioned +default behavior. Even if there is no overlap between +:member:`ngtcp2_settings.preferred_versions` and other_versions field +plus *client_chosen_version* from client, as long as +*client_chosen_version* is supported by server, server accepts +*client_chosen_version*. + +If client receives Version Negotiation packet from server, +`ngtcp2_conn_read_pkt` returns +:macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`. +:member:`ngtcp2_callbacks.recv_version_negotiation` is also invoked if +set. It will provide the versions contained in the packet. Client +then either gives up the connection attempt, or selects the version +from Version Negotiation packet, and starts new connection attempt +with that version. In the latter case, the initial version that used +in the first connection attempt must be set to +:member:`ngtcp2_settings.original_version`. The client version +preference that is used when selecting a version from Version +Negotiation packet must be set to +:member:`ngtcp2_settings.preferred_versions`. +:member:`ngtcp2_settings.other_versions` must include the selected +version. The selected version becomes *client_chosen_version* in the +second connection attempt, and must be passed to +`ngtcp2_conn_client_new`. + +Server never know whether client reacted upon Version Negotiation +packet or not, and there is no particular setup for server to make +this incompatible version negotiation work. + +Thread safety +------------- + +ngtcp2 library is thread-safe as long as a single :type:`ngtcp2_conn` +object is accessed by a single thread at a time. For multi-threaded +applications, it is recommended to create :type:`ngtcp2_conn` objects +per thread to avoid locks. |