From 6527a5f3d9caf1efafd89dd7e664889e45b1929e Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Mon, 25 Mar 2024 12:13:01 +1300 Subject: [PATCH] connection: add code sample for connection error assertion --- content/docs/quic/connection.md | 52 ++++++++++++++++++++++++++++----- content/docs/quic/transport.md | 4 +-- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/content/docs/quic/connection.md b/content/docs/quic/connection.md index 1bf372e..4e226d1 100644 --- a/content/docs/quic/connection.md +++ b/content/docs/quic/connection.md @@ -4,7 +4,7 @@ toc: true weight: 4 --- -## Idle Timeouts +## Idle Timeouts {#idle-timeout} A QUIC connections can be closed automatically (i.e. without sending of any packets), if it is not used for a certain period of time, the so-called idle timeout. This is especially useful on mobile devices, where waking up the radio just to close a connection would be wasteful. @@ -32,15 +32,53 @@ quic.Config{ This will cause a PING frame to be sent _at least_ every `KeepAlivePeriod`. If the idle timeout negotiated between the two endpoints is shorter than the `KeepAlivePeriod`, PING frames will be sent more frequently. -## When the Remote Peer closes the Connection -In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Additionally, it is set as cancellation cause of the connection context. Users can use errors assertions to find out what exactly went wrong: +## Inspecting the Error -* `quic.VersionNegotiationError`: Happens during the handshake, if there is no overlap between our and the remote's supported QUIC versions. +In case the peer closes the QUIC connection, all calls to open streams, accept streams, as well as all methods on streams immediately return an error. Additionally, it is set as cancellation cause of the connection context. In most cases, applications won't need to closely inspect the error returned. + +The most common way to handle an error is by interface-asserting it to `net.Error`, and (for example) retry the last operation if it's a temporary error. + +The following example shows how to inspect an error in detail: + +```go +var ( + statelessResetErr *quic.StatelessResetError + handshakeTimeoutErr *quic.HandshakeTimeoutError + idleTimeoutErr *quic.IdleTimeoutError + appErr *quic.ApplicationError + transportErr *quic.TransportError + vnErr *quic.VersionNegotiationError +) +switch { +case errors.As(err, &statelessResetErr): + // stateless reset +case errors.As(err, &handshakeTimeoutErr): + // connection timed out before completion of the handshake +case errors.As(err, &idleTimeoutErr): + // idle timeout +case errors.As(err, &appErr): + // application error + remote := appErr.Remote // was the error triggered by the peer? + errorCode := appErr.ErrorCode // application-defined error code + errorMessage := appErr.ErrorMessage // application-defined error message +case errors.As(err, &transportErr): + // transport error + remote := transportErr.Remote // was the error triggered by the peer? + errorCode := transportErr.ErrorCode // error code (RFC 9000, section 20.1) + errorMessage := transportErr.ErrorMessage // error message +case errors.As(err, &vnErr): + // version negotation error + ourVersions := vnErr.Ours // locally supported QUIC versions + theirVersions := vnErr.Theirs // QUIC versions support by the remote +} +``` + +* `quic.VersionNegotiationError`: Happens during the handshake, if [Version Negotiation]({{< relref "transport.md#version-negotiation" >}}) fails, i.e. when there is no overlap between the client's and the server's supported QUIC versions. * `quic.HandshakeTimeoutError`: Happens if the QUIC handshake doesn't complete within the time specified in `quic.Config.HandshakeTimeout`. -* `quic.IdleTimeoutError`: Happens after completion of the handshake if the connection is idle for longer than the minimum of both peers idle timeouts (as configured by `quic.Config.IdleTimeout`). The connection is considered idle when no stream data (and datagrams, if applicable) are exchanged for that period. The QUIC connection can be instructed to regularly send a packet to prevent a connection from going idle by setting `quic.Config.KeepAlive`. However, this is no guarantee that the peer doesn't suddenly go away (e.g. by abruptly shutting down the node or by crashing), or by a NAT binding expiring, in which case this error might still occur. -* `quic.StatelessResetError`: Happens when the remote peer lost the state required to decrypt the packet. This requires the `quic.Transport.StatelessResetToken` to be configured by the peer. -* `quic.TransportError`: Happens if when the QUIC protocol is violated. Unless the error code is `APPLICATION_ERROR`, this will not happen unless one of the QUIC stacks involved is misbehaving. Please open an issue if you encounter this error. +* `quic.IdleTimeoutError`: Happens after completion of the handshake if the connection is [idle](#idle-timeout) for longer than the minimum of both peers idle timeouts. +* `quic.StatelessResetError`: Happens when a [Stateless Reset]({{< relref "transport.md#stateless-reset" >}}) is received. +* `quic.TransportError`: Happens if the QUIC protocol is violated. Unless the error code is `APPLICATION_ERROR`, this will not happen unless one of the QUIC stacks involved is misbehaving. Please open an issue if you encounter this error. * `quic.ApplicationError`: Happens when the remote decides to close the connection, see below. ## When we close the Connection diff --git a/content/docs/quic/transport.md b/content/docs/quic/transport.md index d47766a..b305681 100644 --- a/content/docs/quic/transport.md +++ b/content/docs/quic/transport.md @@ -50,7 +50,7 @@ tr.ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error) Using the `ReadNonQUICPacket` method is preferable over implementation this inspection logic outside of quic-go, and passing a wrapped `net.PacketConn` to the `Transport`, as it allows quic-go to use a number of kernel-based optimization (e.g. GSO) that massively speed up QUIC transfers (see [Optimizations]({{< relref path="optimizations.md#gso" >}})). -## Stateless Resets +## Stateless Resets {#stateless-reset} QUIC is designed to prevent off-path attackers from disrupting connections, unlike TCP where such attackers can close connections using RST packets. @@ -70,7 +70,7 @@ quic.Transport{ Applications need to make sure that this key stays constant across reboots of the endpoint. One way to achieve this is to load it from a configuration file on disk. Alternatively, an application could also derive it from the TLS private key. Keeping this key confidential is essential to prevent off-path attackers from disrupting QUIC connections managed by the endpoint. -## Version Negotiation +## Version Negotiation {#version-negotiation} QUIC is designed to accommodate the definition of new versions in the future. [RFC 8999](https://datatracker.ietf.org/doc/html/rfc8999) describes the (minimal set of) properties of QUIC that must be fulfilled by all QUIC versions.