Skip to content

Commit

Permalink
connection: add code sample for connection error assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
marten-seemann committed Mar 24, 2024
1 parent d9b3ff4 commit 6527a5f
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
52 changes: 45 additions & 7 deletions content/docs/quic/connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions content/docs/quic/transport.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.

Expand Down

0 comments on commit 6527a5f

Please sign in to comment.