/// A `ServerZeroRttChecker` is a simple wrapper around a single checker. /// It uses `RefCell` so that the wrapped checker can be shared between /// multiple connections created by the server. #[derive(Clone, Debug)] struct ServerZeroRttChecker {
checker: Rc<RefCell<Box<dyn ZeroRttChecker>>>,
}
pubstruct Server { /// The names of certificates.
certs: Vec<String>, /// The ALPN values that the server supports.
protocols: Vec<String>, /// The cipher suites that the server supports.
ciphers: Vec<Cipher>, /// Anti-replay configuration for 0-RTT.
anti_replay: AntiReplay, /// A function for determining if 0-RTT can be accepted.
zero_rtt_checker: ServerZeroRttChecker, /// A connection ID generator.
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>, /// Connection parameters.
conn_params: ConnectionParameters, /// All connections.
connections: Vec<Rc<RefCell<Connection>>>, /// Address validation logic, which determines whether we send a Retry.
address_validation: Rc<RefCell<AddressValidation>>, /// Directory to create qlog traces in
qlog_dir: Option<PathBuf>, /// Encrypted client hello (ECH) configuration.
ech_config: Option<EchConfig>, /// Remaining datagrams of a batch of datagrams provided via /// [`Server::process_multiple`]. An earlier datagram in the batch required /// an immediate return without further processing of the remaining /// datagrams. To be processed on consecutive calls to /// [`Server::process_multiple`].
saved_datagrams: VecDeque<SavedDatagram>,
}
impl Server { /// Construct a new server. /// * `now` is the time that the server is instantiated. /// * `certs` is a list of the certificates that should be configured. /// * `protocols` is the preference list of ALPN values. /// * `anti_replay` is an anti-replay context. /// * `zero_rtt_checker` determines whether 0-RTT should be accepted. This will be passed the /// value of the `extra` argument that was passed to `Connection::send_ticket` to see if it is /// OK. /// * `cid_generator` is responsible for generating connection IDs and parsing them; connection /// IDs produced by the manager cannot be zero-length. /// # Errors /// When address validation state cannot be created. pubfn new<A1: AsRef<str>, A2: AsRef<str>>(
now: Instant,
certs: &[A1],
protocols: &[A2],
anti_replay: AntiReplay,
zero_rtt_checker: Box<dyn ZeroRttChecker>,
cid_generator: Rc<RefCell<dyn ConnectionIdGenerator>>,
conn_params: ConnectionParameters,
) -> Res<Self> { let validation = AddressValidation::new(now, ValidateAddress::Never)?;
Ok(Self {
certs: certs.iter().map(|x| String::from(x.as_ref())).collect(),
protocols: protocols.iter().map(|x| String::from(x.as_ref())).collect(),
ciphers: Vec::new(),
anti_replay,
zero_rtt_checker: ServerZeroRttChecker::new(zero_rtt_checker),
cid_generator,
conn_params,
connections: Vec::new(),
address_validation: Rc::new(RefCell::new(validation)),
qlog_dir: None,
ech_config: None,
saved_datagrams: VecDeque::new(),
})
}
/// Set or clear directory to create logs of connection events in QLOG format. pubfn set_qlog_dir(&mutself, dir: Option<PathBuf>) { self.qlog_dir = dir;
}
/// Set the policy for address validation. pubfn set_validation(&self, v: ValidateAddress) { self.address_validation.borrow_mut().set_validation(v);
}
/// Set the cipher suites that should be used. Set an empty value to use /// default values. pubfn set_ciphers<A: AsRef<[Cipher]>>(&mutself, ciphers: A) { self.ciphers = Vec::from(ciphers.as_ref());
}
// > This Destination Connection ID MUST be at least 8 bytes in length. // // <https://www.rfc-editor.org/rfc/rfc9000.html#section-7.2> if initial.dst_cid.len() < 8 {
qerror!( "[{self}] DCID too short ({} bytes), dropping packet",
initial.dst_cid.len()
); return Output::None;
}
let res = self.address_validation.borrow().generate_retry_token(
&initial.dst_cid,
dgram.source(),
now,
); let Ok(token) = res else {
qerror!("[{self}] unable to generate token, dropping packet"); return Output::None;
}; iflet Some(new_dcid) = self.cid_generator.borrow_mut().generate_cid() { let packet = packet::Builder::retry(
initial.version,
&initial.src_cid,
&new_dcid,
&token,
&initial.dst_cid,
);
packet.map_or_else(
|_| {
qerror!("[{self}] unable to encode retry, dropping packet");
Output::None
},
|p| {
qdebug!( "[{self}] type={:?} path:{} {}->{} {:?} len {}",
packet::Type::Retry,
initial.dst_cid,
dgram.destination(),
dgram.source(),
Tos::default(),
p.len(),
);
Output::Datagram(Datagram::new(
dgram.destination(),
dgram.source(),
Tos::default(),
p,
))
},
)
} else {
qerror!("[{self}] no connection ID for retry, dropping packet");
Output::None
}
}
}
}
fn setup_connection(
&self,
c: &mut Connection,
initial: InitialDetails,
orig_dcid: Option<ConnectionId>,
now: Instant,
) { let zcheck = self.zero_rtt_checker.clone(); if c.server_enable_0rtt(&self.anti_replay, zcheck).is_err() {
qwarn!("[{self}] Unable to enable 0-RTT");
} iflet Some(odcid) = &orig_dcid { // There was a retry, so set the connection IDs for.
c.set_retry_cids(odcid, initial.src_cid, &initial.dst_cid);
}
c.set_validation(&self.address_validation);
c.set_qlog(self.create_qlog_trace(orig_dcid.unwrap_or(initial.dst_cid).as_cid_ref(), now)); iflet Some(cfg) = &self.ech_config
&& c.server_enable_ech(cfg.config, &cfg.public_name, &cfg.sk, &cfg.pk)
.is_err()
{
qwarn!("[{self}] Unable to enable ECH");
}
}
fn accept_connection(
&mutself,
initial: InitialDetails,
dgram: Datagram<impl AsRef<[u8]> + AsMut<[u8]>>,
orig_dcid: Option<ConnectionId>,
now: Instant,
) -> Output {
qinfo!( "[{self}] Accept connection {:?}",
orig_dcid.as_ref().unwrap_or(&initial.dst_cid)
); // The internal connection ID manager that we use is not used directly. // Instead, wrap it so that we can save connection IDs.
match sconn {
Ok(mut c) => { self.setup_connection(&mut c, initial, orig_dcid, now); let out = c.process(Some(dgram), now); self.connections.push(Rc::new(RefCell::new(c)));
out
}
Err(e) => {
qwarn!("[{self}] Unable to create connection"); if e == crate::Error::VersionNegotiation { crate::qlog::server_version_information_failed(
&mutself.create_qlog_trace(
orig_dcid.unwrap_or(initial.dst_cid).as_cid_ref(),
now,
), self.conn_params.get_versions().all(),
initial.version.wire_version(),
now,
);
}
Output::None
}
}
}
/// Process new input datagrams on the connection. pubfn process_multiple_input<
A: AsRef<[u8]> + AsMut<[u8]>,
I: IntoIterator<Item = Datagram<A>>,
>(
&mutself,
dgrams: I,
now: Instant,
) -> OutputBatch { // Process input datagrams from previous call. whilelet Some(SavedDatagram { d, t }) = self.saved_datagrams.pop_front() { iflet OutputBatch::DatagramBatch(b) = self.process_input(std::iter::once(d), t) { self.saved_datagrams
.extend(dgrams.into_iter().map(|d| SavedDatagram {
d: d.to_owned(),
t: now,
})); return OutputBatch::DatagramBatch(b);
}
}
// Process input datagrams from this call. iflet o @ OutputBatch::DatagramBatch(_) = self.process_input(dgrams, now) { return o;
}
OutputBatch::None
}
// Process a new input datagram on the connection. fn process_input<A: AsRef<[u8]> + AsMut<[u8]>, I: IntoIterator<Item = Datagram<A>>>(
&mutself,
dgrams: I,
now: Instant,
) -> OutputBatch { letmut dgrams = dgrams.into_iter(); whilelet Some(mut dgram) = dgrams.next() {
qtrace!("Process datagram: {}", hex(&dgram[..]));
// This is only looking at the first packet header in the datagram. // All packets in the datagram are routed to the same connection. let len = dgram.len(); let destination = dgram.destination(); let source = dgram.source(); let res =
Public::decode_server(&mut dgram[..], self.cid_generator.borrow().as_decoder()); let Ok((packet, _remainder)) = res else {
qtrace!("[{self}] Discarding {dgram:?}"); continue;
};
// Finding an existing connection. Should be the most common case. iflet Some(c) = self
.connections
.iter_mut()
.find(|c| c.borrow().is_valid_local_cid(packet.dcid()))
{
c.borrow_mut().process_input(dgram, now); continue;
}
if packet.packet_type() == packet::Type::Short { // TODO send a stateless reset here.
qtrace!("[{self}] Short header packet for an unknown connection"); continue;
}
if packet.packet_type() == packet::Type::OtherVersion
|| (packet.packet_type() == packet::Type::Initial
&& !self
.conn_params
.get_versions()
.all()
.contains(&packet.version().expect("packet has version")))
{ if len < MIN_INITIAL_PACKET_SIZE {
qdebug!("[{self}] Unsupported version: too short"); continue;
}
match packet.packet_type() {
packet::Type::Initial => { if len < MIN_INITIAL_PACKET_SIZE {
qdebug!("[{self}] Drop initial: too short"); continue;
} // Copy values from `packet` because they are currently still borrowing from // `dgram`. let initial = InitialDetails::new(&packet); iflet o @ Output::Datagram(_) = self.handle_initial(initial, dgram, now) { self.saved_datagrams.extend(dgrams.map(|d| SavedDatagram {
d: d.to_owned(),
t: now,
})); return o.into();
}
}
packet::Type::ZeroRtt => {
qdebug!( "[{self}] Dropping 0-RTT for unknown connection {}",
ConnectionId::from(packet.dcid())
);
}
packet::Type::OtherVersion => unreachable!(),
_ => {
qtrace!("[{self}] Not an initial packet");
}
}
}
OutputBatch::None
}
/// Iterate through the pending connections looking for any that might want /// to send a datagram. Stop at the first one that does. fn process_next_output(&mutself, now: Instant, max_datagrams: NonZeroUsize) -> OutputBatch {
assert!( self.saved_datagrams.is_empty(), "Always process all inbound datagrams first."
); letmut callback = None;
for connection in &mutself.connections { match connection
.borrow_mut()
.process_multiple_output(now, max_datagrams)
{
OutputBatch::None => {}
d @ OutputBatch::DatagramBatch(_) => return d,
OutputBatch::Callback(next) => match callback {
Some(previous) => callback = Some(min(previous, next)),
None => callback = Some(next),
},
}
}
/// Short-hand for [`Server::process`] without an input datagram. #[must_use] pubfn process_output(&mutself, now: Instant) -> Output { self.process(None::<Datagram>, now)
}
/// Wrapper around [`Server::process_multiple`] that processes a single output /// datagram only. #[expect(clippy::missing_panics_doc, reason = "see expect()")] #[must_use] pubfn process<A: AsRef<[u8]> + AsMut<[u8]>, I: IntoIterator<Item = Datagram<A>>>(
&mutself,
dgrams: I,
now: Instant,
) -> Output { self.process_multiple(dgrams, now, 1.try_into().expect(">0"))
.try_into()
.expect("max_datagrams is 1")
}
pubfn process_multiple<A: AsRef<[u8]> + AsMut<[u8]>, I: IntoIterator<Item = Datagram<A>>>(
&mutself,
dgrams: I,
now: Instant,
max_datagrams: NonZeroUsize,
) -> OutputBatch { iflet o @ OutputBatch::DatagramBatch(_) = self.process_multiple_input(dgrams, now) { // Return immediately. Do any maintenance on next call. return o;
}
// Process output datagrams. let maybe_callback = matchself.process_next_output(now, max_datagrams) { // Return immediately. Do any maintenance on next call.
o @ OutputBatch::DatagramBatch(_) => return o,
o @ (OutputBatch::Callback(_) | OutputBatch::None) => o,
};
/// This lists the connections that have received new events /// as a result of calling `process()`. #[expect(
clippy::mutable_key_type,
reason = "ActiveConnectionRef::Hash doesn't access any of the interior mutable types."
)] #[must_use] pubfn active_connections(&self) -> HashSet<ConnectionRef> { self.connections
.iter()
.filter(|c| c.borrow().has_events())
.map(|c| ConnectionRef { c: Rc::clone(c) })
.collect()
}
/// Whether any connections have received new events as a result of calling /// `process()`. #[must_use] pubfn has_active_connections(&self) -> bool { self.connections.iter().any(|c| c.borrow().has_events())
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.