quic

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2024 License: MIT Imports: 42 Imported by: 1

README

A QUIC implementation in pure Go

PkgGoDev Code Coverage Fuzzing Status

quic-go is an implementation of the QUIC protocol (RFC 9000, RFC 9001, RFC 9002) in Go. It has support for HTTP/3 (RFC 9114), including QPACK (RFC 9204).

In addition to these base RFCs, it also implements the following RFCs:

Support for WebTransport over HTTP/3 (draft-ietf-webtrans-http3) is implemented in webtransport-go.

Using QUIC

Running a Server

The central entry point is the quic.Transport. A transport manages QUIC connections running on a single UDP socket. Since QUIC uses Connection IDs, it can demultiplex a listener (accepting incoming connections) and an arbitrary number of outgoing QUIC connections on the same UDP socket.

udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 1234})
// ... error handling
tr := quic.Transport{
  Conn: udpConn,
}
ln, err := tr.Listen(tlsConf, quicConf)
// ... error handling
go func() {
  for {
    conn, err := ln.Accept()
    // ... error handling
    // handle the connection, usually in a new Go routine
  }
}()

The listener ln can now be used to accept incoming QUIC connections by (repeatedly) calling the Accept method (see below for more information on the quic.Connection).

As a shortcut, quic.Listen and quic.ListenAddr can be used without explicitly initializing a quic.Transport:

ln, err := quic.Listen(udpConn, tlsConf, quicConf)

When using the shortcut, it's not possible to reuse the same UDP socket for outgoing connections.

Running a Client

As mentioned above, multiple outgoing connections can share a single UDP socket, since QUIC uses Connection IDs to demultiplex connections.

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
defer cancel()
conn, err := tr.Dial(ctx, <server address>, <tls.Config>, <quic.Config>)
// ... error handling

As a shortcut, quic.Dial and quic.DialAddr can be used without explictly initializing a quic.Transport:

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // 3s handshake timeout
defer cancel()
conn, err := quic.Dial(ctx, conn, <server address>, <tls.Config>, <quic.Config>)

Just as we saw before when used a similar shortcut to run a server, it's also not possible to reuse the same UDP socket for other outgoing connections, or to listen for incoming connections.

Using a QUIC Connection
Accepting Streams

QUIC is a stream-multiplexed transport. A quic.Connection fundamentally differs from the net.Conn and the net.PacketConn interface defined in the standard library. Data is sent and received on (unidirectional and bidirectional) streams (and, if supported, in datagrams), not on the connection itself. The stream state machine is described in detail in Section 3 of RFC 9000.

Note: A unidirectional stream is a stream that the initiator can only write to (quic.SendStream), and the receiver can only read from (quic.ReceiveStream). A bidirectional stream (quic.Stream) allows reading from and writing to for both sides.

On the receiver side, streams are accepted using the AcceptStream (for bidirectional) and AcceptUniStream functions. For most user cases, it makes sense to call these functions in a loop:

for {
  str, err := conn.AcceptStream(context.Background()) // for bidirectional streams
  // ... error handling
  // handle the stream, usually in a new Go routine
}

These functions return an error when the underlying QUIC connection is closed.

Opening Streams

There are two slightly different ways to open streams, one synchronous and one (potentially) asynchronous. This API is necessary since the receiver grants us a certain number of streams that we're allowed to open. It may grant us additional streams later on (typically when existing streams are closed), but it means that at the time we want to open a new stream, we might not be able to do so.

Using the synchronous method OpenStreamSync for bidirectional streams, and OpenUniStreamSync for unidirectional streams, an application can block until the peer allows opening additional streams. In case that we're allowed to open a new stream, these methods return right away:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
str, err := conn.OpenStreamSync(ctx) // wait up to 5s to open a new bidirectional stream

The asynchronous version never blocks. If it's currently not possible to open a new stream, it returns a net.Error timeout error:

str, err := conn.OpenStream()
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
  // It's currently not possible to open another stream,
  // but it might be possible later, once the peer allowed us to do so.
}

These functions return an error when the underlying QUIC connection is closed.

Using Streams

Using QUIC streams is pretty straightforward. The quic.ReceiveStream implements the io.Reader interface, and the quic.SendStream implements the io.Writer interface. A bidirectional stream (quic.Stream) implements both these interfaces. Conceptually, a bidirectional stream can be thought of as the composition of two unidirectional streams in opposite directions.

Calling Close on a quic.SendStream or a quic.Stream closes the send side of the stream. On the receiver side, this will be surfaced as an io.EOF returned from the io.Reader once all data has been consumed. Note that for bidirectional streams, Close only closes the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.

In case the application wishes to abort sending on a quic.SendStream or a quic.Stream , it can reset the send side by calling CancelWrite with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a quic.StreamError containing that error code on the io.Reader. Note that for bidirectional streams, CancelWrite only resets the send side of the stream. It is still possible to read from the stream until the peer closes or resets the stream.

Conversely, in case the application wishes to abort receiving from a quic.ReceiveStream or a quic.Stream, it can ask the sender to abort data transmission by calling CancelRead with an application-defined error code (an unsigned 62-bit number). On the receiver side, this surfaced as a quic.StreamError containing that error code on the io.Writer. Note that for bidirectional streams, CancelWrite only resets the receive side of the stream. It is still possible to write to the stream.

A bidirectional stream is only closed once both the read and the write side of the stream have been either closed or reset. Only then the peer is granted a new stream according to the maximum number of concurrent streams configured via quic.Config.MaxIncomingStreams.

Configuring QUIC

The quic.Config struct passed to both the listen and dial calls (see above) contains a wide range of configuration options for QUIC connections, incl. the ability to fine-tune flow control limits, the number of streams that the peer is allowed to open concurrently, keep-alives, idle timeouts, and many more. Please refer to the documentation for the quic.Config for details.

The quic.Transport contains a few configuration options that don't apply to any single QUIC connection, but to all connections handled by that transport. It is highly recommend to set the StatelessResetToken, which allows endpoints to quickly recover from crashes / reboots of our node (see Section 10.3 of RFC 9000).

Closing a Connection
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:

  • quic.VersionNegotiationError: Happens during the handshake, if there is no overlap between our and the remote'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.ApplicationError: Happens when the remote decides to close the connection, see below.
Initiated by the Application

A quic.Connection can be closed using CloseWithError:

conn.CloseWithError(0x42, "error 0x42 occurred")

Applications can transmit both an error code (an unsigned 62-bit number) as well as a UTF-8 encoded human-readable reason. The error code allows the receiver to learn why the connection was closed, and the reason can be useful for debugging purposes.

On the receiver side, this is surfaced as a quic.ApplicationError.

QUIC Datagrams

Unreliable datagrams are a QUIC extension (RFC 9221) that is negotiated during the handshake. Support can be enabled by setting the quic.Config.EnableDatagram flag. Note that this doesn't guarantee that the peer also supports datagrams. Whether or not the feature negotiation succeeded can be learned from the quic.ConnectionState.SupportsDatagrams obtained from quic.Connection.ConnectionState().

QUIC DATAGRAMs are a new QUIC frame type sent in QUIC 1-RTT packets (i.e. after completion of the handshake). Therefore, they're end-to-end encrypted and congestion-controlled. However, if a DATAGRAM frame is deemed lost by QUIC's loss detection mechanism, they are not retransmitted.

Datagrams are sent using the SendDatagram method on the quic.Connection:

conn.SendDatagram([]byte("foobar"))

And received using ReceiveDatagram:

msg, err := conn.ReceiveDatagram()

Note that this code path is currently not optimized. It works for datagrams that are sent occasionally, but it doesn't achieve the same throughput as writing data on a stream. Please get in touch on issue #3766 if your use case relies on high datagram throughput, or if you'd like to help fix this issue. There are also some restrictions regarding the maximum message size (see #3599).

QUIC Event Logging using qlog

quic-go logs a wide range of events defined in draft-ietf-quic-qlog-quic-events, providing comprehensive insights in the internals of a QUIC connection.

qlog files can be processed by a number of 3rd-party tools. qviz has proven very useful for debugging all kinds of QUIC connection failures.

qlog can be activated by setting the Tracer callback on the Config. It is called as soon as quic-go decides to start the QUIC handshake on a new connection. qlog.DefaultTracer provides a tracer implementation which writes qlog files to a directory specified by the QLOGDIR environment variable, if set. The default qlog tracer can be used like this:

quic.Config{
  Tracer: qlog.DefaultTracer,
}

This example creates a new qlog file under <QLOGDIR>/<Original Destination Connection ID>_<Vantage Point>.qlog, e.g. qlogs/2e0407da_client.qlog.

For custom qlog behavior, qlog.NewConnectionTracer can be used.

Using HTTP/3

As a server

See the example server. Starting a QUIC server is very similar to the standard library http package in Go:

http.Handle("/", http.FileServer(http.Dir(wwwDir)))
http3.ListenAndServeQUIC("localhost:4242", "/path/to/cert/chain.pem", "/path/to/privkey.pem", nil)
As a client

See the example client. Use a http3.RoundTripper as a Transport in a http.Client.

http.Client{
  Transport: &http3.RoundTripper{},
}

Projects using quic-go

Project Description Stars
AdGuardHome Free and open source, powerful network-wide ads & trackers blocking DNS server. GitHub Repo stars
algernon Small self-contained pure-Go web server with Lua, Markdown, HTTP/2, QUIC, Redis and PostgreSQL support GitHub Repo stars
caddy Fast, multi-platform web server with automatic HTTPS GitHub Repo stars
cloudflared A tunneling daemon that proxies traffic from the Cloudflare network to your origins GitHub Repo stars
go-libp2p libp2p implementation in Go, powering Kubo (IPFS) and Lotus (Filecoin), among others GitHub Repo stars
gost A simple security tunnel written in Go GitHub Repo stars
Hysteria A powerful, lightning fast and censorship resistant proxy GitHub Repo stars
Mercure An open, easy, fast, reliable and battery-efficient solution for real-time communications GitHub Repo stars
OONI Probe Next generation OONI Probe. Library and CLI tool. GitHub Repo stars
RoadRunner High-performance PHP application server, process manager written in Go and powered with plugins GitHub Repo stars
syncthing Open Source Continuous File Synchronization GitHub Repo stars
traefik The Cloud Native Application Proxy GitHub Repo stars
v2ray-core A platform for building proxies to bypass network restrictions GitHub Repo stars
YoMo Streaming Serverless Framework for Geo-distributed System GitHub Repo stars

If you'd like to see your project added to this list, please send us a PR.

Release Policy

quic-go always aims to support the latest two Go releases.

Contributing

We are always happy to welcome new contributors! We have a number of self-contained issues that are suitable for first-time contributors, they are tagged with help wanted. If you have any questions, please feel free to reach out by opening an issue or leaving a comment.

Documentation

Index

Constants

View Source
const (
	NoError                   = qerr.NoError
	InternalError             = qerr.InternalError
	ConnectionRefused         = qerr.ConnectionRefused
	FlowControlError          = qerr.FlowControlError
	StreamLimitError          = qerr.StreamLimitError
	StreamStateError          = qerr.StreamStateError
	FinalSizeError            = qerr.FinalSizeError
	FrameEncodingError        = qerr.FrameEncodingError
	TransportParameterError   = qerr.TransportParameterError
	ConnectionIDLimitError    = qerr.ConnectionIDLimitError
	ProtocolViolation         = qerr.ProtocolViolation
	InvalidToken              = qerr.InvalidToken
	ApplicationErrorErrorCode = qerr.ApplicationErrorErrorCode
	CryptoBufferExceeded      = qerr.CryptoBufferExceeded
	KeyUpdateError            = qerr.KeyUpdateError
	AEADLimitReached          = qerr.AEADLimitReached
	NoViablePathError         = qerr.NoViablePathError
)
View Source
const (
	// Version1 is RFC 9000
	Version1 = protocol.Version1
	// Version2 is RFC 9369
	Version2 = protocol.Version2
)

Variables

View Source
var ConnectionTracingKey = connTracingCtxKey{}

ConnectionTracingKey can be used to associate a ConnectionTracer with a Connection. It is set on the Connection.Context() context, as well as on the context passed to logging.Tracer.NewConnectionTracer.

View Source
var Err0RTTRejected = errors.New("0-RTT rejected")

Err0RTTRejected is the returned from: * Open{Uni}Stream{Sync} * Accept{Uni}Stream * Stream.Read and Stream.Write when the server rejects a 0-RTT connection attempt.

View Source
var ErrServerClosed = errors.New("quic: server closed")

ErrServerClosed is returned by the Listener or EarlyListener's Accept method after a call to Close.

View Source
var QUICVersionContextKey = handshake.QUICVersionContextKey

QUICVersionContextKey can be used to find out the QUIC version of a TLS handshake from the context returned by tls.Config.ClientHelloInfo.Context.

Functions

This section is empty.

Types

type ApplicationError

type ApplicationError = qerr.ApplicationError

type ApplicationErrorCode

type ApplicationErrorCode = qerr.ApplicationErrorCode

type ClientHelloInfo

type ClientHelloInfo struct {
	RemoteAddr net.Addr
}

type ClientToken

type ClientToken struct {
	// contains filtered or unexported fields
}

A ClientToken is a token received by the client. It can be used to skip address validation on future connection attempts.

type Config

type Config struct {
	// GetConfigForClient is called for incoming connections.
	// If the error is not nil, the connection attempt is refused.
	GetConfigForClient func(info *ClientHelloInfo) (*Config, error)
	// The QUIC versions that can be negotiated.
	// If not set, it uses all versions available.
	Versions []VersionNumber
	// HandshakeIdleTimeout is the idle timeout before completion of the handshake.
	// If we don't receive any packet from the peer within this time, the connection attempt is aborted.
	// Additionally, if the handshake doesn't complete in twice this time, the connection attempt is also aborted.
	// If this value is zero, the timeout is set to 5 seconds.
	HandshakeIdleTimeout time.Duration
	// MaxIdleTimeout is the maximum duration that may pass without any incoming network activity.
	// The actual value for the idle timeout is the minimum of this value and the peer's.
	// This value only applies after the handshake has completed.
	// If the timeout is exceeded, the connection is closed.
	// If this value is zero, the timeout is set to 30 seconds.
	MaxIdleTimeout time.Duration
	// RequireAddressValidation determines if a QUIC Retry packet is sent.
	// This allows the server to verify the client's address, at the cost of increasing the handshake latency by 1 RTT.
	// See https://datatracker.ietf.org/doc/html/rfc9000#section-8 for details.
	// If not set, every client is forced to prove its remote address.
	RequireAddressValidation func(net.Addr) bool
	// The TokenStore stores tokens received from the server.
	// Tokens are used to skip address validation on future connection attempts.
	// The key used to store tokens is the ServerName from the tls.Config, if set
	// otherwise the token is associated with the server's IP address.
	TokenStore TokenStore
	// InitialStreamReceiveWindow is the initial size of the stream-level flow control window for receiving data.
	// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
	// will increase the window up to MaxStreamReceiveWindow.
	// If this value is zero, it will default to 512 KB.
	// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
	InitialStreamReceiveWindow uint64
	// MaxStreamReceiveWindow is the maximum stream-level flow control window for receiving data.
	// If this value is zero, it will default to 6 MB.
	// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
	MaxStreamReceiveWindow uint64
	// InitialConnectionReceiveWindow is the initial size of the stream-level flow control window for receiving data.
	// If the application is consuming data quickly enough, the flow control auto-tuning algorithm
	// will increase the window up to MaxConnectionReceiveWindow.
	// If this value is zero, it will default to 512 KB.
	// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
	InitialConnectionReceiveWindow uint64
	// MaxConnectionReceiveWindow is the connection-level flow control window for receiving data.
	// If this value is zero, it will default to 15 MB.
	// Values larger than the maximum varint (quicvarint.Max) will be clipped to that value.
	MaxConnectionReceiveWindow uint64
	// AllowConnectionWindowIncrease is called every time the connection flow controller attempts
	// to increase the connection flow control window.
	// If set, the caller can prevent an increase of the window. Typically, it would do so to
	// limit the memory usage.
	// To avoid deadlocks, it is not valid to call other functions on the connection or on streams
	// in this callback.
	AllowConnectionWindowIncrease func(conn Connection, delta uint64) bool
	// MaxIncomingStreams is the maximum number of concurrent bidirectional streams that a peer is allowed to open.
	// If not set, it will default to 100.
	// If set to a negative value, it doesn't allow any bidirectional streams.
	// Values larger than 2^60 will be clipped to that value.
	MaxIncomingStreams int64
	// MaxIncomingUniStreams is the maximum number of concurrent unidirectional streams that a peer is allowed to open.
	// If not set, it will default to 100.
	// If set to a negative value, it doesn't allow any unidirectional streams.
	// Values larger than 2^60 will be clipped to that value.
	MaxIncomingUniStreams int64
	// KeepAlivePeriod defines whether this peer will periodically send a packet to keep the connection alive.
	// If set to 0, then no keep alive is sent. Otherwise, the keep alive is sent on that period (or at most
	// every half of MaxIdleTimeout, whichever is smaller).
	KeepAlivePeriod time.Duration
	// DisablePathMTUDiscovery disables Path MTU Discovery (RFC 8899).
	// This allows the sending of QUIC packets that fully utilize the available MTU of the path.
	// Path MTU discovery is only available on systems that allow setting of the Don't Fragment (DF) bit.
	// If unavailable or disabled, packets will be at most 1252 (IPv4) / 1232 (IPv6) bytes in size.
	DisablePathMTUDiscovery bool
	// Allow0RTT allows the application to decide if a 0-RTT connection attempt should be accepted.
	// Only valid for the server.
	Allow0RTT bool
	// Enable QUIC datagram support (RFC 9221).
	EnableDatagrams bool
	Tracer          func(context.Context, logging.Perspective, ConnectionID) *logging.ConnectionTracer

	// [Psiphon]
	// ClientHelloSeed is used for TLS Client Hello randomization and replay.
	ClientHelloSeed *prng.Seed

	// [Psiphon]
	// GetClientHelloRandom is used by the QUIC client to supply a specific
	// value in the TLS Client Hello random field. This is used to send an
	// anti-probing message, indistinguishable from random, that proves
	// knowlegde of a shared secret key.
	GetClientHelloRandom func() ([]byte, error)

	// [Psiphon]
	// VerifyClientHelloRandom is used by the QUIC server to verify that the
	// TLS Client Hello random field, supplied in the Initial packet for a
	// new connection, was created using the shared secret key and is not
	// replayed.
	VerifyClientHelloRandom func(net.Addr, []byte) bool

	// [Psiphon]
	// ClientMaxPacketSizeAdjustment indicates that the max packet size should
	// be reduced by the specified amount. This is used to reserve space for
	// packet obfuscation overhead while remaining at or under the 1280
	// initial target packet size as well as protocol.MaxPacketBufferSize,
	// the maximum packet size under MTU discovery.
	ClientMaxPacketSizeAdjustment int

	// [Psiphon]
	// ServerMaxPacketSizeAdjustment indicates that, for the flow associated
	// with the given client address, the max packet size should be reduced
	// by the specified amount. This is used to reserve space for packet
	// obfuscation overhead while remaining at or under the 1280 target
	// packet size. Must be set only for QUIC server configs.
	ServerMaxPacketSizeAdjustment func(net.Addr) int
}

Config contains all configuration data needed for a QUIC server or client.

func (*Config) Clone

func (c *Config) Clone() *Config

Clone clones a Config

type Connection

type Connection interface {
	// AcceptStream returns the next stream opened by the peer, blocking until one is available.
	// If the connection was closed due to a timeout, the error satisfies
	// the net.Error interface, and Timeout() will be true.
	AcceptStream(context.Context) (Stream, error)
	// AcceptUniStream returns the next unidirectional stream opened by the peer, blocking until one is available.
	// If the connection was closed due to a timeout, the error satisfies
	// the net.Error interface, and Timeout() will be true.
	AcceptUniStream(context.Context) (ReceiveStream, error)
	// OpenStream opens a new bidirectional QUIC stream.
	// There is no signaling to the peer about new streams:
	// The peer can only accept the stream after data has been sent on the stream.
	// If the error is non-nil, it satisfies the net.Error interface.
	// When reaching the peer's stream limit, err.Temporary() will be true.
	// If the connection was closed due to a timeout, Timeout() will be true.
	OpenStream() (Stream, error)
	// OpenStreamSync opens a new bidirectional QUIC stream.
	// It blocks until a new stream can be opened.
	// If the error is non-nil, it satisfies the net.Error interface.
	// If the connection was closed due to a timeout, Timeout() will be true.
	OpenStreamSync(context.Context) (Stream, error)
	// OpenUniStream opens a new outgoing unidirectional QUIC stream.
	// If the error is non-nil, it satisfies the net.Error interface.
	// When reaching the peer's stream limit, Temporary() will be true.
	// If the connection was closed due to a timeout, Timeout() will be true.
	OpenUniStream() (SendStream, error)
	// OpenUniStreamSync opens a new outgoing unidirectional QUIC stream.
	// It blocks until a new stream can be opened.
	// If the error is non-nil, it satisfies the net.Error interface.
	// If the connection was closed due to a timeout, Timeout() will be true.
	OpenUniStreamSync(context.Context) (SendStream, error)
	// LocalAddr returns the local address.
	LocalAddr() net.Addr
	// RemoteAddr returns the address of the peer.
	RemoteAddr() net.Addr
	// CloseWithError closes the connection with an error.
	// The error string will be sent to the peer.
	CloseWithError(ApplicationErrorCode, string) error
	// Context returns a context that is cancelled when the connection is closed.
	// The cancellation cause is set to the error that caused the connection to
	// close, or `context.Canceled` in case the listener is closed first.
	Context() context.Context
	// ConnectionState returns basic details about the QUIC connection.
	// Warning: This API should not be considered stable and might change soon.
	ConnectionState() ConnectionState

	// SendDatagram sends a message using a QUIC datagram, as specified in RFC 9221.
	// There is no delivery guarantee for DATAGRAM frames, they are not retransmitted if lost.
	// The payload of the datagram needs to fit into a single QUIC packet.
	// In addition, a datagram may be dropped before being sent out if the available packet size suddenly decreases.
	// If the payload is too large to be sent at the current time, a DatagramTooLargeError is returned.
	SendDatagram(payload []byte) error
	// ReceiveDatagram gets a message received in a datagram, as specified in RFC 9221.
	ReceiveDatagram(context.Context) ([]byte, error)
}

A Connection is a QUIC connection between two peers. Calls to the connection (and to streams) can return the following types of errors: * ApplicationError: for errors triggered by the application running on top of QUIC * TransportError: for errors triggered by the QUIC transport (in many cases a misbehaving peer) * IdleTimeoutError: when the peer goes away unexpectedly (this is a net.Error timeout error) * HandshakeTimeoutError: when the cryptographic handshake takes too long (this is a net.Error timeout error) * StatelessResetError: when we receive a stateless reset (this is a net.Error temporary error) * VersionNegotiationError: returned by the client, when there's no version overlap between the peers

func Dial

func Dial(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error)

Dial establishes a new QUIC connection to a server using a net.PacketConn. If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does), ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write packets. The tls.Config must define an application protocol (using NextProtos).

This is a convenience function. More advanced use cases should instantiate a Transport, which offers configuration options for a more fine-grained control of the connection establishment, including reusing the underlying UDP socket for multiple QUIC connections.

func DialAddr

func DialAddr(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (Connection, error)

DialAddr establishes a new QUIC connection to a server. It resolves the address, and then creates a new UDP connection to dial the QUIC server. When the QUIC connection is closed, this UDP connection is closed. See Dial for more details.

type ConnectionID

type ConnectionID = protocol.ConnectionID

A ConnectionID is a QUIC Connection ID, as defined in RFC 9000. It is not able to handle QUIC Connection IDs longer than 20 bytes, as they are allowed by RFC 8999.

func ConnectionIDFromBytes

func ConnectionIDFromBytes(b []byte) ConnectionID

ConnectionIDFromBytes interprets b as a Connection ID. It panics if b is longer than 20 bytes.

type ConnectionIDGenerator

type ConnectionIDGenerator interface {
	// GenerateConnectionID generates a new ConnectionID.
	// Generated ConnectionIDs should be unique and observers should not be able to correlate two ConnectionIDs.
	GenerateConnectionID() (ConnectionID, error)

	// ConnectionIDLen tells what is the length of the ConnectionIDs generated by the implementation of
	// this interface.
	// Effectively, this means that implementations of ConnectionIDGenerator must always return constant-size
	// connection IDs. Valid lengths are between 0 and 20 and calls to GenerateConnectionID.
	// 0-length ConnectionsIDs can be used when an endpoint (server or client) does not require multiplexing connections
	// in the presence of a connection migration environment.
	ConnectionIDLen() int
}

A ConnectionIDGenerator is an interface that allows clients to implement their own format for the Connection IDs that servers/clients use as SrcConnectionID in QUIC packets.

Connection IDs generated by an implementation should always produce IDs of constant size.

type ConnectionState

type ConnectionState struct {
	// TLS contains information about the TLS connection state, incl. the tls.ConnectionState.
	TLS tls.ConnectionState
	// SupportsDatagrams says if support for QUIC datagrams (RFC 9221) was negotiated.
	// This requires both nodes to support and enable the datagram extensions (via Config.EnableDatagrams).
	// If datagram support was negotiated, datagrams can be sent and received using the
	// SendDatagram and ReceiveDatagram methods on the Connection.
	SupportsDatagrams bool
	// Used0RTT says if 0-RTT resumption was used.
	Used0RTT bool
	// Version is the QUIC version of the QUIC connection.
	Version VersionNumber
	// GSO says if generic segmentation offload is used
	GSO bool
}

ConnectionState records basic details about a QUIC connection

type DatagramTooLargeError

type DatagramTooLargeError struct {
	PeerMaxDatagramFrameSize int64
}

DatagramTooLargeError is returned from Connection.SendDatagram if the payload is too large to be sent.

func (*DatagramTooLargeError) Error

func (e *DatagramTooLargeError) Error() string

func (*DatagramTooLargeError) Is

func (e *DatagramTooLargeError) Is(target error) bool

type EarlyConnection

type EarlyConnection interface {
	Connection

	// HandshakeComplete blocks until the handshake completes (or fails).
	// For the client, data sent before completion of the handshake is encrypted with 0-RTT keys.
	// For the server, data sent before completion of the handshake is encrypted with 1-RTT keys,
	// however the client's identity is only verified once the handshake completes.
	HandshakeComplete() <-chan struct{}

	NextConnection() Connection
}

An EarlyConnection is a connection that is handshaking. Data sent during the handshake is encrypted using the forward secure keys. When using client certificates, the client's identity is only verified after completion of the handshake.

func DialAddrEarly

func DialAddrEarly(ctx context.Context, addr string, tlsConf *tls.Config, conf *Config) (EarlyConnection, error)

DialAddrEarly establishes a new 0-RTT QUIC connection to a server. See DialAddr for more details.

func DialEarly

func DialEarly(ctx context.Context, c net.PacketConn, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error)

DialEarly establishes a new 0-RTT QUIC connection to a server using a net.PacketConn. See Dial for more details.

type EarlyListener

type EarlyListener struct {
	// contains filtered or unexported fields
}

An EarlyListener listens for incoming QUIC connections, and returns them before the handshake completes. For connections that don't use 0-RTT, this allows the server to send 0.5-RTT data. This data is encrypted with forward-secure keys, however, the client's identity has not yet been verified. For connection using 0-RTT, this allows the server to accept and respond to streams that the client opened in the 0-RTT data it sent. Note that at this point during the handshake, the live-ness of the client has not yet been confirmed, and the 0-RTT data could have been replayed by an attacker.

func ListenAddrEarly

func ListenAddrEarly(addr string, tlsConf *tls.Config, config *Config) (*EarlyListener, error)

ListenAddrEarly works like ListenAddr, but it returns connections before the handshake completes.

func ListenEarly

func ListenEarly(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*EarlyListener, error)

ListenEarly works like Listen, but it returns connections before the handshake completes.

func (*EarlyListener) Accept

Accept returns a new connections. It should be called in a loop.

func (*EarlyListener) Addr

func (l *EarlyListener) Addr() net.Addr

Addr returns the local network addr that the server is listening on.

func (*EarlyListener) Close

func (l *EarlyListener) Close() error

Close the server. All active connections will be closed.

type HandshakeTimeoutError

type HandshakeTimeoutError = qerr.HandshakeTimeoutError

type IdleTimeoutError

type IdleTimeoutError = qerr.IdleTimeoutError

type Listener

type Listener struct {
	// contains filtered or unexported fields
}

A Listener listens for incoming QUIC connections. It returns connections once the handshake has completed.

func Listen

func Listen(conn net.PacketConn, tlsConf *tls.Config, config *Config) (*Listener, error)

Listen listens for QUIC connections on a given net.PacketConn. If the PacketConn satisfies the OOBCapablePacketConn interface (as a net.UDPConn does), ECN and packet info support will be enabled. In this case, ReadMsgUDP and WriteMsgUDP will be used instead of ReadFrom and WriteTo to read/write packets. A single net.PacketConn can only be used for a single call to Listen.

The tls.Config must not be nil and must contain a certificate configuration. Furthermore, it must define an application control (using NextProtos). The quic.Config may be nil, in that case the default values will be used.

This is a convenience function. More advanced use cases should instantiate a Transport, which offers configuration options for a more fine-grained control of the connection establishment, including reusing the underlying UDP socket for outgoing QUIC connections. When closing a listener created with Listen, all established QUIC connections will be closed immediately.

func ListenAddr

func ListenAddr(addr string, tlsConf *tls.Config, config *Config) (*Listener, error)

ListenAddr creates a QUIC server listening on a given address. See Listen for more details.

func (*Listener) Accept

func (l *Listener) Accept(ctx context.Context) (Connection, error)

Accept returns new connections. It should be called in a loop.

func (*Listener) Addr

func (l *Listener) Addr() net.Addr

Addr returns the local network address that the server is listening on.

func (*Listener) Close

func (l *Listener) Close() error

Close closes the listener. Accept will return ErrServerClosed as soon as all connections in the accept queue have been accepted. QUIC handshakes that are still in flight will be rejected with a CONNECTION_REFUSED error. The effect of closing the listener depends on how it was created: * if it was created using Transport.Listen, already established connections will be unaffected * if it was created using the Listen convenience method, all established connection will be closed immediately

type OOBCapablePacketConn

type OOBCapablePacketConn interface {
	net.PacketConn
	SyscallConn() (syscall.RawConn, error)
	SetReadBuffer(int) error
	ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
	WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}

OOBCapablePacketConn is a connection that allows the reading of ECN bits from the IP header. If the PacketConn passed to Dial or Listen satisfies this interface, quic-go will use it. In this case, ReadMsgUDP() will be used instead of ReadFrom() to read packets.

type ReceiveStream

type ReceiveStream interface {
	// StreamID returns the stream ID.
	StreamID() StreamID
	// Read reads data from the stream.
	// Read can be made to time out and return a net.Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetReadDeadline.
	// If the stream was canceled by the peer, the error implements the StreamError
	// interface, and Canceled() == true.
	// If the connection was closed due to a timeout, the error satisfies
	// the net.Error interface, and Timeout() will be true.
	io.Reader
	// CancelRead aborts receiving on this stream.
	// It will ask the peer to stop transmitting stream data.
	// Read will unblock immediately, and future Read calls will fail.
	// When called multiple times or after reading the io.EOF it is a no-op.
	CancelRead(StreamErrorCode)

	SetReadDeadline(t time.Time) error
}

A ReceiveStream is a unidirectional Receive Stream.

type SendStream

type SendStream interface {
	// StreamID returns the stream ID.
	StreamID() StreamID
	// Write writes data to the stream.
	// Write can be made to time out and return a net.Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetWriteDeadline.
	// If the stream was canceled by the peer, the error implements the StreamError
	// interface, and Canceled() == true.
	// If the connection was closed due to a timeout, the error satisfies
	// the net.Error interface, and Timeout() will be true.
	io.Writer
	// Close closes the write-direction of the stream.
	// Future calls to Write are not permitted after calling Close.
	// It must not be called concurrently with Write.
	// It must not be called after calling CancelWrite.
	io.Closer
	// CancelWrite aborts sending on this stream.
	// Data already written, but not yet delivered to the peer is not guaranteed to be delivered reliably.
	// Write will unblock immediately, and future calls to Write will fail.
	// When called multiple times or after closing the stream it is a no-op.
	CancelWrite(StreamErrorCode)
	// The Context is canceled as soon as the write-side of the stream is closed.
	// This happens when Close() or CancelWrite() is called, or when the peer
	// cancels the read-side of their stream.
	// The cancellation cause is set to the error that caused the stream to
	// close, or `context.Canceled` in case the stream is closed without error.
	Context() context.Context
	// SetWriteDeadline sets the deadline for future Write calls
	// and any currently-blocked Write call.
	// Even if write times out, it may return n > 0, indicating that
	// some data was successfully written.
	// A zero value for t means Write will not time out.
	SetWriteDeadline(t time.Time) error
}

A SendStream is a unidirectional Send Stream.

type StatelessResetError

type StatelessResetError = qerr.StatelessResetError

type StatelessResetKey

type StatelessResetKey [32]byte

StatelessResetKey is a key used to derive stateless reset tokens.

type Stream

type Stream interface {
	ReceiveStream
	SendStream
	// SetDeadline sets the read and write deadlines associated
	// with the connection. It is equivalent to calling both
	// SetReadDeadline and SetWriteDeadline.
	SetDeadline(t time.Time) error
}

Stream is the interface implemented by QUIC streams In addition to the errors listed on the Connection, calls to stream functions can return a StreamError if the stream is canceled.

type StreamError

type StreamError struct {
	StreamID  StreamID
	ErrorCode StreamErrorCode
	Remote    bool
}

A StreamError is used for Stream.CancelRead and Stream.CancelWrite. It is also returned from Stream.Read and Stream.Write if the peer canceled reading or writing.

func (*StreamError) Error

func (e *StreamError) Error() string

func (*StreamError) Is

func (e *StreamError) Is(target error) bool

type StreamErrorCode

type StreamErrorCode = qerr.StreamErrorCode

type StreamID

type StreamID = protocol.StreamID

The StreamID is the ID of a QUIC stream.

type TokenGeneratorKey

type TokenGeneratorKey = handshake.TokenProtectorKey

TokenGeneratorKey is a key used to encrypt session resumption tokens.

type TokenStore

type TokenStore interface {
	// Pop searches for a ClientToken associated with the given key.
	// Since tokens are not supposed to be reused, it must remove the token from the cache.
	// It returns nil when no token is found.
	Pop(key string) (token *ClientToken)

	// Put adds a token to the cache with the given key. It might get called
	// multiple times in a connection.
	Put(key string, token *ClientToken)
}

func NewLRUTokenStore

func NewLRUTokenStore(maxOrigins, tokensPerOrigin int) TokenStore

NewLRUTokenStore creates a new LRU cache for tokens received by the client. maxOrigins specifies how many origins this cache is saving tokens for. tokensPerOrigin specifies the maximum number of tokens per origin.

type Transport

type Transport struct {
	// A single net.PacketConn can only be handled by one Transport.
	// Bad things will happen if passed to multiple Transports.
	//
	// A number of optimizations will be enabled if the connections implements the OOBCapablePacketConn interface,
	// as a *net.UDPConn does.
	// 1. It enables the Don't Fragment (DF) bit on the IP header.
	//    This is required to run DPLPMTUD (Path MTU Discovery, RFC 8899).
	// 2. It enables reading of the ECN bits from the IP header.
	//    This allows the remote node to speed up its loss detection and recovery.
	// 3. It uses batched syscalls (recvmmsg) to more efficiently receive packets from the socket.
	// 4. It uses Generic Segmentation Offload (GSO) to efficiently send batches of packets (on Linux).
	//
	// After passing the connection to the Transport, it's invalid to call ReadFrom or WriteTo on the connection.
	Conn net.PacketConn

	// The length of the connection ID in bytes.
	// It can be 0, or any value between 4 and 18.
	// If unset, a 4 byte connection ID will be used.
	ConnectionIDLength int

	// Use for generating new connection IDs.
	// This allows the application to control of the connection IDs used,
	// which allows routing / load balancing based on connection IDs.
	// All Connection IDs returned by the ConnectionIDGenerator MUST
	// have the same length.
	ConnectionIDGenerator ConnectionIDGenerator

	// The StatelessResetKey is used to generate stateless reset tokens.
	// If no key is configured, sending of stateless resets is disabled.
	// It is highly recommended to configure a stateless reset key, as stateless resets
	// allow the peer to quickly recover from crashes and reboots of this node.
	// See section 10.3 of RFC 9000 for details.
	StatelessResetKey *StatelessResetKey

	// The TokenGeneratorKey is used to encrypt session resumption tokens.
	// If no key is configured, a random key will be generated.
	// If multiple servers are authoritative for the same domain, they should use the same key,
	// see section 8.1.3 of RFC 9000 for details.
	TokenGeneratorKey *TokenGeneratorKey

	// MaxTokenAge is the maximum age of the resumption token presented during the handshake.
	// These tokens allow skipping address resumption when resuming a QUIC connection,
	// and are especially useful when using 0-RTT.
	// If not set, it defaults to 24 hours.
	// See section 8.1.3 of RFC 9000 for details.
	MaxTokenAge time.Duration

	// DisableVersionNegotiationPackets disables the sending of Version Negotiation packets.
	// This can be useful if version information is exchanged out-of-band.
	// It has no effect for clients.
	DisableVersionNegotiationPackets bool

	// A Tracer traces events that don't belong to a single QUIC connection.
	Tracer *logging.Tracer
	// contains filtered or unexported fields
}

The Transport is the central point to manage incoming and outgoing QUIC connections. QUIC demultiplexes connections based on their QUIC Connection IDs, not based on the 4-tuple. This means that a single UDP socket can be used for listening for incoming connections, as well as for dialing an arbitrary number of outgoing connections. A Transport handles a single net.PacketConn, and offers a range of configuration options compared to the simple helper functions like Listen and Dial that this package provides.

func (*Transport) Close

func (t *Transport) Close() error

Close closes the underlying connection. If any listener was started, it will be closed as well. It is invalid to start new listeners or connections after that.

func (*Transport) Dial

func (t *Transport) Dial(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (Connection, error)

Dial dials a new connection to a remote host (not using 0-RTT).

func (*Transport) DialEarly

func (t *Transport) DialEarly(ctx context.Context, addr net.Addr, tlsConf *tls.Config, conf *Config) (EarlyConnection, error)

DialEarly dials a new connection, attempting to use 0-RTT if possible.

func (*Transport) Listen

func (t *Transport) Listen(tlsConf *tls.Config, conf *Config) (*Listener, error)

Listen starts listening for incoming QUIC connections. There can only be a single listener on any net.PacketConn. Listen may only be called again after the current Listener was closed.

func (*Transport) ListenEarly

func (t *Transport) ListenEarly(tlsConf *tls.Config, conf *Config) (*EarlyListener, error)

ListenEarly starts listening for incoming QUIC connections. There can only be a single listener on any net.PacketConn. Listen may only be called again after the current Listener was closed.

func (*Transport) ReadNonQUICPacket

func (t *Transport) ReadNonQUICPacket(ctx context.Context, b []byte) (int, net.Addr, error)

ReadNonQUICPacket reads non-QUIC packets received on the underlying connection. The detection logic is very simple: Any packet that has the first and second bit of the packet set to 0. Note that this is stricter than the detection logic defined in RFC 9443.

func (*Transport) WriteTo

func (t *Transport) WriteTo(b []byte, addr net.Addr) (int, error)

WriteTo sends a packet on the underlying connection.

type TransportError

type TransportError = qerr.TransportError

type TransportErrorCode

type TransportErrorCode = qerr.TransportErrorCode

type VersionNegotiationError

type VersionNegotiationError = qerr.VersionNegotiationError

type VersionNumber

type VersionNumber = protocol.VersionNumber

A VersionNumber is a QUIC version number.

Directories

Path Synopsis
fuzzing
integrationtests
internal
mocks
Code generated by MockGen.
Code generated by MockGen.
mocks/ackhandler
Code generated by MockGen.
Code generated by MockGen.
mocks/logging/internal
Code generated by MockGen.
Code generated by MockGen.
mocks/quic
Code generated by MockGen.
Code generated by MockGen.
mocks/tls
Code generated by MockGen.
Code generated by MockGen.
utils/linkedlist
Package list implements a doubly linked list.
Package list implements a doubly linked list.
interop
Package logging defines a logging interface for quic-go.
Package logging defines a logging interface for quic-go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL