mirror of
https://github.com/n0-computer/noq.git
synced 2026-09-21 10:43:25 +00:00
Introduce shared module for widely used types
This removes the dependency from connection on endpoint which I always found ugly, and generally shrinks some of our larger modules by extracting types that are not tightly coupled to their those modules' other contents.
This commit is contained in:
committed by
Benjamin Saunders
parent
c66937e55b
commit
f63fd2b811
@@ -13,17 +13,19 @@ use slog::Logger;
|
||||
use crate::assembler::Assembler;
|
||||
use crate::coding::BufMutExt;
|
||||
use crate::crypto::{
|
||||
self, reset_token_for, Crypto, CryptoClientConfig, CryptoSession, HeaderCrypto,
|
||||
RingHeaderCrypto, TlsSession, ACK_DELAY_EXPONENT,
|
||||
reset_token_for, Crypto, CryptoClientConfig, CryptoSession, HeaderCrypto, RingHeaderCrypto,
|
||||
TlsSession, ACK_DELAY_EXPONENT,
|
||||
};
|
||||
use crate::dedup::Dedup;
|
||||
use crate::endpoint::TransportConfig;
|
||||
use crate::frame::FrameStruct;
|
||||
use crate::packet::{
|
||||
set_payload_length, ConnectionId, EcnCodepoint, Header, LongType, Packet, PacketNumber,
|
||||
PartialDecode, SpaceId, LONG_RESERVED_BITS, SHORT_RESERVED_BITS,
|
||||
set_payload_length, Header, LongType, Packet, PacketNumber, PartialDecode, SpaceId,
|
||||
LONG_RESERVED_BITS, SHORT_RESERVED_BITS,
|
||||
};
|
||||
use crate::range_set::RangeSet;
|
||||
use crate::shared::{
|
||||
ClientConfig, ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent, TransportConfig,
|
||||
};
|
||||
use crate::stream::{self, ReadError, Streams, WriteError};
|
||||
use crate::transport_parameters::{self, TransportParameters};
|
||||
use crate::{
|
||||
@@ -3106,12 +3108,6 @@ mod state {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientConfig {
|
||||
pub server_name: String,
|
||||
pub tls_config: Arc<crypto::ClientConfig>,
|
||||
}
|
||||
|
||||
/// Represents one or more packets subject to retransmission
|
||||
#[derive(Debug, Clone)]
|
||||
struct SentPacket {
|
||||
@@ -3134,30 +3130,6 @@ struct SentPacket {
|
||||
/// Ensures we can always fit all our ACKs in a single minimum-MTU packet with room to spare
|
||||
const MAX_ACK_BLOCKS: usize = 64;
|
||||
|
||||
/// Events to be sent to the Connection
|
||||
pub enum ConnectionEvent {
|
||||
Datagram {
|
||||
now: Instant,
|
||||
remote: SocketAddr,
|
||||
ecn: Option<EcnCodepoint>,
|
||||
first_decode: PartialDecode,
|
||||
remaining: Option<BytesMut>,
|
||||
},
|
||||
NewIdentifiers(Vec<(u64, ConnectionId)>),
|
||||
Timer(Instant, Timer),
|
||||
}
|
||||
|
||||
/// Events to be sent to the Endpoint
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EndpointEvent {
|
||||
Closed {
|
||||
remote: SocketAddr,
|
||||
},
|
||||
NeedIdentifiers,
|
||||
/// Stop routing connection ID for this sequence number to this `Connection`
|
||||
RetireConnectionId(u64),
|
||||
}
|
||||
|
||||
/// Encoding of I/O operations to emit on upcoming `poll_io` calls
|
||||
#[derive(Debug)]
|
||||
struct IoQueue {
|
||||
|
||||
@@ -17,7 +17,8 @@ pub use rustls::{ClientConfig, ClientSession, ServerConfig, ServerSession, Sessi
|
||||
use webpki::DNSNameRef;
|
||||
|
||||
use crate::coding::{BufExt, BufMutExt};
|
||||
use crate::packet::{ConnectionId, PacketNumber, LONG_HEADER_FORM};
|
||||
use crate::packet::{PacketNumber, LONG_HEADER_FORM};
|
||||
use crate::shared::ConnectionId;
|
||||
use crate::transport_parameters::TransportParameters;
|
||||
use crate::{ConnectError, Side, TransportError, MAX_CID_SIZE, MIN_CID_SIZE, RESET_TOKEN_SIZE};
|
||||
|
||||
|
||||
+8
-174
@@ -1,4 +1,3 @@
|
||||
use std::cmp;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::net::SocketAddr;
|
||||
use std::ops::{Index, IndexMut};
|
||||
@@ -15,16 +14,20 @@ use slab::Slab;
|
||||
use slog::{self, Logger};
|
||||
|
||||
use crate::coding::BufMutExt;
|
||||
use crate::connection::{initial_close, ClientConfig, Connection, ConnectionEvent, EndpointEvent};
|
||||
use crate::connection::{initial_close, Connection};
|
||||
use crate::crypto::{
|
||||
self, reset_token_for, Crypto, CryptoClientConfig, CryptoServerConfig, RingHeaderCrypto,
|
||||
TokenKey,
|
||||
};
|
||||
use crate::packet::{ConnectionId, EcnCodepoint, Header, Packet, PacketDecodeError, PartialDecode};
|
||||
use crate::packet::{Header, Packet, PacketDecodeError, PartialDecode};
|
||||
use crate::shared::{
|
||||
ClientConfig, ConfigError, ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent,
|
||||
TransportConfig,
|
||||
};
|
||||
use crate::transport_parameters::TransportParameters;
|
||||
use crate::{
|
||||
varint, Side, Transmit, TransportError, LOC_CID_COUNT, MAX_CID_SIZE, MIN_CID_SIZE,
|
||||
MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, VERSION,
|
||||
Side, Transmit, TransportError, LOC_CID_COUNT, MAX_CID_SIZE, MIN_CID_SIZE, MIN_INITIAL_SIZE,
|
||||
RESET_TOKEN_SIZE, VERSION,
|
||||
};
|
||||
|
||||
/// The main entry point to the library
|
||||
@@ -592,163 +595,6 @@ pub(crate) struct ConnectionMeta {
|
||||
loc_cids: HashMap<u64, ConnectionId>,
|
||||
}
|
||||
|
||||
/// Parameters governing the core QUIC state machine
|
||||
///
|
||||
/// This should be tuned to suit the application. In particular, window sizes for streams, stream
|
||||
/// data, and overall connection data should be set differently depending on the expected round trip
|
||||
/// time, link capacity, memory availability, and rate of stream creation. Tuning for higher
|
||||
/// bandwidths and latencies increases worst-case memory consumption, but does not impair
|
||||
/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps
|
||||
/// link with a 100ms round trip time, with remote endpoints opening at most 320 new streams per
|
||||
/// second. Applications which do not require remotely-initiated streams should set the stream
|
||||
/// windows to zero.
|
||||
pub struct TransportConfig {
|
||||
/// Maximum number of bidirectional streams that may be initiated by the peer but not yet
|
||||
/// accepted locally
|
||||
///
|
||||
/// Must be nonzero for the peer to open any bidirectional streams.
|
||||
///
|
||||
/// Any number of streams may be in flight concurrently. However, to ensure predictable resource
|
||||
/// use, the number of streams which the peer has initiated but which the local application has
|
||||
/// not yet accepted will be kept below this threshold.
|
||||
///
|
||||
/// Because it takes at least one round trip for an endpoint to open a new stream and be
|
||||
/// notified of its peer's flow control updates, this imposes a hard upper bound on the number
|
||||
/// of streams that may be opened per round-trip. In other words, this should be set to at least
|
||||
/// the desired number of streams opened per unit time, multiplied by the round trip time.
|
||||
///
|
||||
/// Note that worst-case memory use is directly proportional to `stream_window_bidi *
|
||||
/// stream_receive_window`, with an upper bound proportional to `receive_window`.
|
||||
pub stream_window_bidi: u64,
|
||||
/// Variant of `stream_window_bidi` affecting unidirectional streams
|
||||
pub stream_window_uni: u64,
|
||||
/// Maximum duration of inactivity to accept before timing out the connection (ms).
|
||||
///
|
||||
/// The actual value used is the minimum of this and the peer's own idle timeout. 0 for none.
|
||||
pub idle_timeout: u64,
|
||||
/// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
|
||||
/// before becoming blocked.
|
||||
///
|
||||
/// This should be set to at least the expected connection latency multiplied by the maximum
|
||||
/// desired throughput. Setting this smaller than `receive_window` helps ensure that a single
|
||||
/// stream doesn't monopolize receive buffers, which may otherwise occur if the application
|
||||
/// chooses not to read from a large stream for a time while still requiring data on other
|
||||
/// streams.
|
||||
pub stream_receive_window: u64,
|
||||
/// Maximum number of bytes the peer may transmit across all streams of a connection before
|
||||
/// becoming blocked.
|
||||
///
|
||||
/// This should be set to at least the expected connection latency multiplied by the maximum
|
||||
/// desired throughput. Larger values can be useful to allow maximum throughput within a
|
||||
/// stream while another is blocked.
|
||||
pub receive_window: u64,
|
||||
/// Maximum number of bytes to transmit to a peer without acknowledgment
|
||||
///
|
||||
/// Provides an upper bound on memory when communicating with peers that issue large amounts of
|
||||
/// flow control credit. Endpoints that wish to handle large numbers of connections robustly
|
||||
/// should take care to set this low enough to guarantee memory exhaustion does not occur if
|
||||
/// every connection uses the entire window.
|
||||
pub send_window: u64,
|
||||
|
||||
/// Maximum number of tail loss probes before an RTO fires.
|
||||
pub max_tlps: u32,
|
||||
/// Maximum reordering in packet number space before FACK style loss detection considers a
|
||||
/// packet lost.
|
||||
pub packet_threshold: u32,
|
||||
/// Maximum reordering in time space before time based loss detection considers a packet lost.
|
||||
/// 0.16 format, added to 1
|
||||
pub time_threshold: u16,
|
||||
/// The length of the peer’s delayed ack timer (μs).
|
||||
pub delayed_ack_timeout: u64,
|
||||
/// The RTT used before an RTT sample is taken (μs)
|
||||
pub initial_rtt: u64,
|
||||
|
||||
/// The max packet size that was used for calculating default and minimum congestion windows.
|
||||
pub max_datagram_size: u64,
|
||||
/// Default limit on the amount of outstanding data in bytes.
|
||||
///
|
||||
/// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14600))`
|
||||
pub initial_window: u64,
|
||||
/// Default minimum congestion window.
|
||||
///
|
||||
/// Recommended value: `2 * max_datagram_size`.
|
||||
pub minimum_window: u64,
|
||||
/// Reduction in congestion window when a new loss event is detected. 0.16 format
|
||||
pub loss_reduction_factor: u16,
|
||||
/// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.
|
||||
pub persistent_congestion_threshold: u32,
|
||||
/// Number of milliseconds of inactivity before sending a keep-alive packet
|
||||
///
|
||||
/// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.
|
||||
///
|
||||
/// 0 to disable, which is the default. Only one side of any given connection needs keep-alive
|
||||
/// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both
|
||||
/// peers to be effective.
|
||||
pub keep_alive_interval: u32,
|
||||
}
|
||||
|
||||
impl Default for TransportConfig {
|
||||
fn default() -> Self {
|
||||
const EXPECTED_RTT: u64 = 100; // ms
|
||||
const MAX_STREAM_BANDWIDTH: u64 = 12500 * 1000; // bytes/s
|
||||
// Window size needed to avoid pipeline
|
||||
// stalls
|
||||
const STREAM_RWND: u64 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT;
|
||||
const MAX_DATAGRAM_SIZE: u64 = 1200;
|
||||
|
||||
TransportConfig {
|
||||
stream_window_bidi: 32,
|
||||
stream_window_uni: 32,
|
||||
idle_timeout: 10_000,
|
||||
stream_receive_window: STREAM_RWND,
|
||||
receive_window: 8 * STREAM_RWND,
|
||||
send_window: 8 * STREAM_RWND,
|
||||
|
||||
max_tlps: 2,
|
||||
packet_threshold: 3,
|
||||
time_threshold: 0x2000, // 1/8
|
||||
delayed_ack_timeout: 25 * 1000,
|
||||
initial_rtt: EXPECTED_RTT as u64 * 1000,
|
||||
|
||||
max_datagram_size: MAX_DATAGRAM_SIZE,
|
||||
initial_window: cmp::min(
|
||||
10 * MAX_DATAGRAM_SIZE,
|
||||
cmp::max(2 * MAX_DATAGRAM_SIZE, 14600),
|
||||
),
|
||||
minimum_window: 2 * MAX_DATAGRAM_SIZE,
|
||||
loss_reduction_factor: 0x8000, // 1/2
|
||||
persistent_congestion_threshold: 2,
|
||||
keep_alive_interval: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportConfig {
|
||||
fn validate(&self, log: &Logger) -> Result<(), ConfigError> {
|
||||
if let Some((name, _)) = [
|
||||
("stream_window_bidi", self.stream_window_bidi),
|
||||
("stream_window_uni", self.stream_window_uni),
|
||||
("receive_window", self.receive_window),
|
||||
("stream_receive_window", self.stream_receive_window),
|
||||
("idle_timeout", self.idle_timeout),
|
||||
]
|
||||
.iter()
|
||||
.find(|&&(_, x)| x > varint::MAX_VALUE)
|
||||
{
|
||||
return Err(ConfigError::VarIntBounds(name));
|
||||
}
|
||||
if self.idle_timeout != 0 && self.keep_alive_interval as u64 >= self.idle_timeout {
|
||||
warn!(
|
||||
log,
|
||||
"keep-alive interval {} is ineffective due to lower idle timeout {}",
|
||||
self.keep_alive_interval,
|
||||
self.idle_timeout
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Global configuration for the endpoint, affecting all connections
|
||||
pub struct EndpointConfig {
|
||||
/// Length of connection IDs for the endpoint.
|
||||
@@ -834,18 +680,6 @@ impl Default for ServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors in the configuration of an endpoint
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
/// The supplied configuration contained an invalid value
|
||||
#[error(display = "illegal configuration value: {}", _0)]
|
||||
IllegalValue(&'static str),
|
||||
/// A configuration field that will be encoded as a variable-length integer exceeds the 0..2^62
|
||||
/// range
|
||||
#[error(display = "{} must be at most 2^62-1", _0)]
|
||||
VarIntBounds(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct ConnectionHandle(pub usize);
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::{fmt, io, mem};
|
||||
use bytes::{Buf, BufMut, Bytes};
|
||||
|
||||
use crate::coding::{self, BufExt, BufMutExt, UnexpectedEnd};
|
||||
use crate::packet::EcnCodepoint;
|
||||
use crate::range_set::RangeSet;
|
||||
use crate::shared::EcnCodepoint;
|
||||
use crate::{
|
||||
varint, ConnectionId, Directionality, StreamId, TransportError, TransportErrorCode,
|
||||
MAX_CID_SIZE, MIN_CID_SIZE, RESET_TOKEN_SIZE,
|
||||
|
||||
@@ -18,6 +18,7 @@ use std::time::Duration;
|
||||
mod assembler;
|
||||
pub mod coding;
|
||||
mod dedup;
|
||||
mod packet;
|
||||
mod range_set;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -25,10 +26,7 @@ mod transport_parameters;
|
||||
pub mod varint;
|
||||
|
||||
mod connection;
|
||||
pub use crate::connection::{
|
||||
Connection, ConnectionError, ConnectionEvent, EndpointEvent, Event, Timer, TimerSetting,
|
||||
TimerUpdate,
|
||||
};
|
||||
pub use crate::connection::{Connection, ConnectionError, Event, Timer, TimerSetting, TimerUpdate};
|
||||
|
||||
mod crypto;
|
||||
pub use crate::crypto::{ClientConfig, TokenKey};
|
||||
@@ -39,12 +37,13 @@ pub use crate::frame::{ApplicationClose, ConnectionClose};
|
||||
|
||||
mod endpoint;
|
||||
pub use crate::endpoint::{
|
||||
ConfigError, ConnectError, ConnectionHandle, DatagramEvent, Endpoint, EndpointConfig,
|
||||
ServerConfig, TransportConfig,
|
||||
ConnectError, ConnectionHandle, DatagramEvent, Endpoint, EndpointConfig, ServerConfig,
|
||||
};
|
||||
|
||||
mod packet;
|
||||
pub use crate::packet::{ConnectionId, EcnCodepoint};
|
||||
mod shared;
|
||||
pub use crate::shared::{
|
||||
ConfigError, ConnectionEvent, ConnectionId, EcnCodepoint, EndpointEvent, TransportConfig,
|
||||
};
|
||||
|
||||
mod stream;
|
||||
pub use crate::stream::{ReadError, WriteError};
|
||||
|
||||
+3
-104
@@ -1,14 +1,14 @@
|
||||
use std::{cmp::Ordering, fmt, io, ops::Range, str};
|
||||
use std::{cmp::Ordering, io, ops::Range, str};
|
||||
|
||||
use bytes::{BigEndian, Buf, BufMut, ByteOrder, Bytes, BytesMut};
|
||||
use err_derive::Error;
|
||||
use rand::Rng;
|
||||
use slog;
|
||||
|
||||
use crate::coding::{self, BufExt, BufMutExt};
|
||||
use crate::crypto::{HeaderCrypto, RingHeaderCrypto};
|
||||
use crate::shared::ConnectionId;
|
||||
use crate::varint;
|
||||
use crate::{MAX_CID_SIZE, MIN_CID_SIZE, VERSION};
|
||||
use crate::VERSION;
|
||||
|
||||
// Due to packet number encryption, it is impossible to fully decode a header
|
||||
// (which includes a variable-length packet number) without crypto context.
|
||||
@@ -770,80 +770,6 @@ impl From<coding::UnexpectedEnd> for PacketDecodeError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Protocol-level identifier for a connection.
|
||||
///
|
||||
/// Mainly useful for identifying this connection's packets on the wire with tools like Wireshark.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ConnectionId {
|
||||
pub len: u8,
|
||||
pub bytes: [u8; MAX_CID_SIZE],
|
||||
}
|
||||
|
||||
impl ConnectionId {
|
||||
pub fn new(bytes: &[u8]) -> Self {
|
||||
debug_assert!(
|
||||
bytes.is_empty() || (bytes.len() >= MIN_CID_SIZE && bytes.len() <= MAX_CID_SIZE)
|
||||
);
|
||||
let mut res = Self {
|
||||
len: bytes.len() as u8,
|
||||
bytes: [0; MAX_CID_SIZE],
|
||||
};
|
||||
res.bytes[..bytes.len()].clone_from_slice(&bytes);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn random<R: Rng>(rng: &mut R, len: usize) -> Self {
|
||||
debug_assert!(len <= MAX_CID_SIZE);
|
||||
let mut res = Self {
|
||||
len: len as u8,
|
||||
bytes: [0; MAX_CID_SIZE],
|
||||
};
|
||||
let mut rng_bytes = [0; MAX_CID_SIZE];
|
||||
rng.fill_bytes(&mut rng_bytes);
|
||||
res.bytes[..len].clone_from_slice(&rng_bytes[..len]);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for ConnectionId {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.bytes[0..self.len as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::DerefMut for ConnectionId {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.bytes[0..self.len as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectionId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.bytes[0..self.len as usize].fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for byte in self.iter() {
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for ConnectionId {
|
||||
fn serialize(
|
||||
&self,
|
||||
_: &slog::Record<'_>,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
serializer.emit_arguments(key, &format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_payload_length(packet: &mut [u8], header_len: usize, pn_len: usize, tag_len: usize) {
|
||||
let len = packet.len() - header_len + pn_len + tag_len;
|
||||
assert!(len < 2usize.pow(14)); // Fits in reserved space
|
||||
@@ -860,33 +786,6 @@ pub const SHORT_RESERVED_BITS: u8 = 0x18;
|
||||
pub const LONG_RESERVED_BITS: u8 = 0x0c;
|
||||
const KEY_PHASE_BIT: u8 = 0x04;
|
||||
|
||||
/// Explicit congestion notification codepoint
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum EcnCodepoint {
|
||||
ECT0 = 0b10,
|
||||
ECT1 = 0b01,
|
||||
CE = 0b11,
|
||||
}
|
||||
|
||||
impl EcnCodepoint {
|
||||
pub fn from_bits(x: u8) -> Option<Self> {
|
||||
use self::EcnCodepoint::*;
|
||||
Some(match x & 0b11 {
|
||||
0b10 => ECT0,
|
||||
0b01 => ECT1,
|
||||
0b11 => CE,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bits(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet number space identifiers
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum SpaceId {
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use err_derive::Error;
|
||||
use rand::Rng;
|
||||
use slog::Logger;
|
||||
|
||||
use crate::connection::Timer;
|
||||
use crate::packet::PartialDecode;
|
||||
use crate::{crypto, varint, MAX_CID_SIZE, MIN_CID_SIZE};
|
||||
|
||||
/// Parameters governing the core QUIC state machine
|
||||
///
|
||||
/// This should be tuned to suit the application. In particular, window sizes for streams, stream
|
||||
/// data, and overall connection data should be set differently depending on the expected round trip
|
||||
/// time, link capacity, memory availability, and rate of stream creation. Tuning for higher
|
||||
/// bandwidths and latencies increases worst-case memory consumption, but does not impair
|
||||
/// performance at lower bandwidths and latencies. The default configuration is tuned for a 100Mbps
|
||||
/// link with a 100ms round trip time, with remote endpoints opening at most 320 new streams per
|
||||
/// second. Applications which do not require remotely-initiated streams should set the stream
|
||||
/// windows to zero.
|
||||
pub struct TransportConfig {
|
||||
/// Maximum number of bidirectional streams that may be initiated by the peer but not yet
|
||||
/// accepted locally
|
||||
///
|
||||
/// Must be nonzero for the peer to open any bidirectional streams.
|
||||
///
|
||||
/// Any number of streams may be in flight concurrently. However, to ensure predictable resource
|
||||
/// use, the number of streams which the peer has initiated but which the local application has
|
||||
/// not yet accepted will be kept below this threshold.
|
||||
///
|
||||
/// Because it takes at least one round trip for an endpoint to open a new stream and be
|
||||
/// notified of its peer's flow control updates, this imposes a hard upper bound on the number
|
||||
/// of streams that may be opened per round-trip. In other words, this should be set to at least
|
||||
/// the desired number of streams opened per unit time, multiplied by the round trip time.
|
||||
///
|
||||
/// Note that worst-case memory use is directly proportional to `stream_window_bidi *
|
||||
/// stream_receive_window`, with an upper bound proportional to `receive_window`.
|
||||
pub stream_window_bidi: u64,
|
||||
/// Variant of `stream_window_bidi` affecting unidirectional streams
|
||||
pub stream_window_uni: u64,
|
||||
/// Maximum duration of inactivity to accept before timing out the connection (ms).
|
||||
///
|
||||
/// The actual value used is the minimum of this and the peer's own idle timeout. 0 for none.
|
||||
pub idle_timeout: u64,
|
||||
/// Maximum number of bytes the peer may transmit without acknowledgement on any one stream
|
||||
/// before becoming blocked.
|
||||
///
|
||||
/// This should be set to at least the expected connection latency multiplied by the maximum
|
||||
/// desired throughput. Setting this smaller than `receive_window` helps ensure that a single
|
||||
/// stream doesn't monopolize receive buffers, which may otherwise occur if the application
|
||||
/// chooses not to read from a large stream for a time while still requiring data on other
|
||||
/// streams.
|
||||
pub stream_receive_window: u64,
|
||||
/// Maximum number of bytes the peer may transmit across all streams of a connection before
|
||||
/// becoming blocked.
|
||||
///
|
||||
/// This should be set to at least the expected connection latency multiplied by the maximum
|
||||
/// desired throughput. Larger values can be useful to allow maximum throughput within a
|
||||
/// stream while another is blocked.
|
||||
pub receive_window: u64,
|
||||
/// Maximum number of bytes to transmit to a peer without acknowledgment
|
||||
///
|
||||
/// Provides an upper bound on memory when communicating with peers that issue large amounts of
|
||||
/// flow control credit. Endpoints that wish to handle large numbers of connections robustly
|
||||
/// should take care to set this low enough to guarantee memory exhaustion does not occur if
|
||||
/// every connection uses the entire window.
|
||||
pub send_window: u64,
|
||||
|
||||
/// Maximum number of tail loss probes before an RTO fires.
|
||||
pub max_tlps: u32,
|
||||
/// Maximum reordering in packet number space before FACK style loss detection considers a
|
||||
/// packet lost.
|
||||
pub packet_threshold: u32,
|
||||
/// Maximum reordering in time space before time based loss detection considers a packet lost.
|
||||
/// 0.16 format, added to 1
|
||||
pub time_threshold: u16,
|
||||
/// The length of the peer’s delayed ack timer (μs).
|
||||
pub delayed_ack_timeout: u64,
|
||||
/// The RTT used before an RTT sample is taken (μs)
|
||||
pub initial_rtt: u64,
|
||||
|
||||
/// The max packet size that was used for calculating default and minimum congestion windows.
|
||||
pub max_datagram_size: u64,
|
||||
/// Default limit on the amount of outstanding data in bytes.
|
||||
///
|
||||
/// Recommended value: `min(10 * max_datagram_size, max(2 * max_datagram_size, 14600))`
|
||||
pub initial_window: u64,
|
||||
/// Default minimum congestion window.
|
||||
///
|
||||
/// Recommended value: `2 * max_datagram_size`.
|
||||
pub minimum_window: u64,
|
||||
/// Reduction in congestion window when a new loss event is detected. 0.16 format
|
||||
pub loss_reduction_factor: u16,
|
||||
/// Number of consecutive PTOs after which network is considered to be experiencing persistent congestion.
|
||||
pub persistent_congestion_threshold: u32,
|
||||
/// Number of milliseconds of inactivity before sending a keep-alive packet
|
||||
///
|
||||
/// Keep-alive packets prevent an inactive but otherwise healthy connection from timing out.
|
||||
///
|
||||
/// 0 to disable, which is the default. Only one side of any given connection needs keep-alive
|
||||
/// enabled for the connection to be preserved. Must be set lower than the idle_timeout of both
|
||||
/// peers to be effective.
|
||||
pub keep_alive_interval: u32,
|
||||
}
|
||||
|
||||
impl Default for TransportConfig {
|
||||
fn default() -> Self {
|
||||
const EXPECTED_RTT: u64 = 100; // ms
|
||||
const MAX_STREAM_BANDWIDTH: u64 = 12500 * 1000; // bytes/s
|
||||
// Window size needed to avoid pipeline
|
||||
// stalls
|
||||
const STREAM_RWND: u64 = MAX_STREAM_BANDWIDTH / 1000 * EXPECTED_RTT;
|
||||
const MAX_DATAGRAM_SIZE: u64 = 1200;
|
||||
|
||||
TransportConfig {
|
||||
stream_window_bidi: 32,
|
||||
stream_window_uni: 32,
|
||||
idle_timeout: 10_000,
|
||||
stream_receive_window: STREAM_RWND,
|
||||
receive_window: 8 * STREAM_RWND,
|
||||
send_window: 8 * STREAM_RWND,
|
||||
|
||||
max_tlps: 2,
|
||||
packet_threshold: 3,
|
||||
time_threshold: 0x2000, // 1/8
|
||||
delayed_ack_timeout: 25 * 1000,
|
||||
initial_rtt: EXPECTED_RTT as u64 * 1000,
|
||||
|
||||
max_datagram_size: MAX_DATAGRAM_SIZE,
|
||||
initial_window: cmp::min(
|
||||
10 * MAX_DATAGRAM_SIZE,
|
||||
cmp::max(2 * MAX_DATAGRAM_SIZE, 14600),
|
||||
),
|
||||
minimum_window: 2 * MAX_DATAGRAM_SIZE,
|
||||
loss_reduction_factor: 0x8000, // 1/2
|
||||
persistent_congestion_threshold: 2,
|
||||
keep_alive_interval: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransportConfig {
|
||||
pub(crate) fn validate(&self, log: &Logger) -> Result<(), ConfigError> {
|
||||
if let Some((name, _)) = [
|
||||
("stream_window_bidi", self.stream_window_bidi),
|
||||
("stream_window_uni", self.stream_window_uni),
|
||||
("receive_window", self.receive_window),
|
||||
("stream_receive_window", self.stream_receive_window),
|
||||
("idle_timeout", self.idle_timeout),
|
||||
]
|
||||
.iter()
|
||||
.find(|&&(_, x)| x > varint::MAX_VALUE)
|
||||
{
|
||||
return Err(ConfigError::VarIntBounds(name));
|
||||
}
|
||||
if self.idle_timeout != 0 && self.keep_alive_interval as u64 >= self.idle_timeout {
|
||||
warn!(
|
||||
log,
|
||||
"keep-alive interval {} is ineffective due to lower idle timeout {}",
|
||||
self.keep_alive_interval,
|
||||
self.idle_timeout
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors in the configuration of an endpoint
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
/// The supplied configuration contained an invalid value
|
||||
#[error(display = "illegal configuration value: {}", _0)]
|
||||
IllegalValue(&'static str),
|
||||
/// A configuration field that will be encoded as a variable-length integer exceeds the 0..2^62
|
||||
/// range
|
||||
#[error(display = "{} must be at most 2^62-1", _0)]
|
||||
VarIntBounds(&'static str),
|
||||
}
|
||||
|
||||
/// Events to be sent to the Connection
|
||||
pub enum ConnectionEvent {
|
||||
Datagram {
|
||||
now: Instant,
|
||||
remote: SocketAddr,
|
||||
ecn: Option<EcnCodepoint>,
|
||||
first_decode: PartialDecode,
|
||||
remaining: Option<BytesMut>,
|
||||
},
|
||||
NewIdentifiers(Vec<(u64, ConnectionId)>),
|
||||
Timer(Instant, Timer),
|
||||
}
|
||||
|
||||
/// Events to be sent to the Endpoint
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EndpointEvent {
|
||||
Closed {
|
||||
remote: SocketAddr,
|
||||
},
|
||||
NeedIdentifiers,
|
||||
/// Stop routing connection ID for this sequence number to this `Connection`
|
||||
RetireConnectionId(u64),
|
||||
}
|
||||
|
||||
/// Protocol-level identifier for a connection.
|
||||
///
|
||||
/// Mainly useful for identifying this connection's packets on the wire with tools like Wireshark.
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct ConnectionId {
|
||||
pub len: u8,
|
||||
pub bytes: [u8; MAX_CID_SIZE],
|
||||
}
|
||||
|
||||
impl ConnectionId {
|
||||
pub fn new(bytes: &[u8]) -> Self {
|
||||
debug_assert!(
|
||||
bytes.is_empty() || (bytes.len() >= MIN_CID_SIZE && bytes.len() <= MAX_CID_SIZE)
|
||||
);
|
||||
let mut res = Self {
|
||||
len: bytes.len() as u8,
|
||||
bytes: [0; MAX_CID_SIZE],
|
||||
};
|
||||
res.bytes[..bytes.len()].clone_from_slice(&bytes);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn random<R: Rng>(rng: &mut R, len: usize) -> Self {
|
||||
debug_assert!(len <= MAX_CID_SIZE);
|
||||
let mut res = Self {
|
||||
len: len as u8,
|
||||
bytes: [0; MAX_CID_SIZE],
|
||||
};
|
||||
let mut rng_bytes = [0; MAX_CID_SIZE];
|
||||
rng.fill_bytes(&mut rng_bytes);
|
||||
res.bytes[..len].clone_from_slice(&rng_bytes[..len]);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Deref for ConnectionId {
|
||||
type Target = [u8];
|
||||
fn deref(&self) -> &[u8] {
|
||||
&self.bytes[0..self.len as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::DerefMut for ConnectionId {
|
||||
fn deref_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.bytes[0..self.len as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ConnectionId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.bytes[0..self.len as usize].fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for byte in self.iter() {
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl slog::Value for ConnectionId {
|
||||
fn serialize(
|
||||
&self,
|
||||
_: &slog::Record<'_>,
|
||||
key: slog::Key,
|
||||
serializer: &mut dyn slog::Serializer,
|
||||
) -> slog::Result {
|
||||
serializer.emit_arguments(key, &format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicit congestion notification codepoint
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum EcnCodepoint {
|
||||
ECT0 = 0b10,
|
||||
ECT1 = 0b01,
|
||||
CE = 0b11,
|
||||
}
|
||||
|
||||
impl EcnCodepoint {
|
||||
pub fn from_bits(x: u8) -> Option<Self> {
|
||||
use self::EcnCodepoint::*;
|
||||
Some(match x & 0b11 {
|
||||
0b10 => ECT0,
|
||||
0b01 => ECT1,
|
||||
0b11 => CE,
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn bits(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientConfig {
|
||||
pub server_name: String,
|
||||
pub tls_config: Arc<crypto::ClientConfig>,
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use bytes::{Buf, BufMut};
|
||||
use err_derive::Error;
|
||||
|
||||
use crate::coding::{BufExt, BufMutExt, UnexpectedEnd};
|
||||
use crate::packet::ConnectionId;
|
||||
use crate::shared::ConnectionId;
|
||||
use crate::{
|
||||
varint, Side, TransportConfig, TransportError, MAX_CID_SIZE, MIN_CID_SIZE, RESET_TOKEN_SIZE,
|
||||
VERSION,
|
||||
|
||||
Reference in New Issue
Block a user