/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use firefox_on_glean::metrics::networking; use firefox_on_glean::private::{LocalCustomDistribution, LocalMemoryDistribution}; #[cfg(not(windows))] use libc::{AF_INET, AF_INET6}; use neqo_common::event::Provider; use neqo_common::{qdebug, qerror, qlog::NeqoQlog, qwarn, Datagram, Header, IpTos, Role}; use neqo_crypto::{init, PRErrorCode}; use neqo_http3::{
features::extended_connect::SessionCloseReason, Error as Http3Error, Http3Client,
Http3ClientEvent, Http3Parameters, Http3State, Priority, WebTransportEvent,
}; use neqo_transport::{
stream_id::StreamType, CongestionControlAlgorithm, Connection, ConnectionParameters,
Error as TransportError, Output, RandomConnectionIdGenerator, StreamId, Version,
}; use nserror::*; use nsstring::*; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::{max, min}; use std::convert::TryFrom; use std::convert::TryInto; use std::ffi::c_void; use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; use std::rc::Rc; use std::slice; use std::str; #[cfg(feature = "fuzzing")] use std::time::Duration; use std::time::{Duration, Instant}; use std::{io, ptr}; use thin_vec::ThinVec; use uuid::Uuid; #[cfg(windows)] use winapi::shared::ws2def::{AF_INET, AF_INET6}; use xpcom::{interfaces::nsISocketProvider, AtomicRefcnt, RefCounted, RefPtr};
#[repr(C)] pubstruct NeqoHttp3Conn {
conn: Http3Client,
local_addr: SocketAddr,
refcnt: AtomicRefcnt,
last_output_time: Instant,
max_accumlated_time: Duration, /// Socket to use for IO. /// /// When [`None`], NSPR is used for IO. // // Use a `BorrowedSocket` instead of e.g. `std::net::UdpSocket`. The latter // would close the file descriptor on `Drop`. The lifetime of the underlying // OS socket is managed not by `neqo_glue` but `NSPR`.
socket: Option<neqo_udp::Socket<BorrowedSocket>>,
unsafe { let family = moz_netaddr_get_family(arg) as i32; if family == AF_INET { let port = u16::from_be(moz_netaddr_get_network_order_port(arg)); let ipv4 = Ipv4Addr::from(u32::from_be(moz_netaddr_get_network_order_ip(arg))); return Ok(SocketAddr::new(IpAddr::V4(ipv4), port));
}
if family == AF_INET6 { let port = u16::from_be(moz_netaddr_get_network_order_port(arg)); let ipv6_slice: [u8; 16] = slice::from_raw_parts(moz_netaddr_get_ipv6(arg), 16)
.try_into()
.expect("slice with incorrect length"); let ipv6 = Ipv6Addr::from(ipv6_slice); return Ok(SocketAddr::new(IpAddr::V6(ipv6), port));
}
}
type SetTimerFunc = extern"C"fn(context: *mut c_void, timeout: u64);
#[cfg(unix)] type BorrowedSocket = std::os::fd::BorrowedFd<'static>; #[cfg(windows)] type BorrowedSocket = std::os::windows::io::BorrowedSocket<'static>;
impl NeqoHttp3Conn { /// Create a new [`NeqoHttp3Conn`]. /// /// Note that [`NeqoHttp3Conn`] works under the assumption that the UDP /// socket of the connection, i.e. the one provided to /// [`NeqoHttp3Conn::new`], does not change throughout the lifetime of /// [`NeqoHttp3Conn`]. fn new(
origin: &nsACString,
alpn: &nsACString,
local_addr: *const NetAddr,
remote_addr: *const NetAddr,
max_table_size: u64,
max_blocked_streams: u16,
max_data: u64,
max_stream_data: u64,
version_negotiation: bool,
webtransport: bool,
qlog_dir: &nsACString,
webtransport_datagram_size: u32,
max_accumlated_time_ms: u32,
provider_flags: u32,
socket: Option<i64>,
) -> Result<RefPtr<NeqoHttp3Conn>, nsresult> { // Nss init.
init().map_err(|_| NS_ERROR_UNEXPECTED)?;
let socket = socket
.map(|socket| { #[cfg(unix)] let borrowed = { use std::os::fd::{BorrowedFd, RawFd}; if socket == -1 {
qerror!("got invalid socked {}", socket); return Err(NS_ERROR_INVALID_ARG);
} let raw: RawFd = socket.try_into().map_err(|e| {
qerror!("got invalid socked {}: {}", socket, e);
NS_ERROR_INVALID_ARG
})?; unsafe { BorrowedFd::borrow_raw(raw) }
}; #[cfg(windows)] let borrowed = { use std::os::windows::io::{BorrowedSocket, RawSocket}; if socket as usize == winapi::um::winsock2::INVALID_SOCKET {
qerror!("got invalid socked {}", socket); return Err(NS_ERROR_INVALID_ARG);
} let raw: RawSocket = socket.try_into().map_err(|e| {
qerror!("got invalid socked {}: {}", socket, e);
NS_ERROR_INVALID_ARG
})?; unsafe { BorrowedSocket::borrow_raw(raw) }
};
neqo_udp::Socket::new(borrowed).map_err(|e| {
qerror!("failed to initialize socket {}: {}", socket, e);
into_nsresult(e)
})
})
.transpose()?;
let origin_conv = str::from_utf8(origin).map_err(|_| NS_ERROR_INVALID_ARG)?;
let alpn_conv = str::from_utf8(alpn).map_err(|_| NS_ERROR_INVALID_ARG)?;
let local: SocketAddr = netaddr_to_socket_addr(local_addr)?;
let remote: SocketAddr = netaddr_to_socket_addr(remote_addr)?;
// Set a short timeout when fuzzing. #[cfg(feature = "fuzzing")] if static_prefs::pref!("fuzzing.necko.http3") {
params = params.idle_timeout(Duration::from_millis(10));
}
if webtransport_datagram_size > 0 {
params = params.datagram_size(webtransport_datagram_size.into());
}
if !qlog_dir.is_empty() { let qlog_dir_conv = str::from_utf8(qlog_dir).map_err(|_| NS_ERROR_INVALID_ARG)?; let qlog_path = PathBuf::from(qlog_dir_conv);
match NeqoQlog::enabled_with_file(
qlog_path.clone(),
Role::Client,
Some("Firefox Client qlog".to_string()),
Some("Firefox Client qlog".to_string()),
format!("{}_{}.qlog", origin, Uuid::new_v4()),
) {
Ok(qlog) => conn.set_qlog(qlog),
Err(e) => { // Emit warnings but to not return an error if qlog initialization // fails.
qwarn!( "failed to create NeqoQlog at {}: {}",
qlog_path.display(),
e
);
}
}
}
#[cfg(not(target_os = "android"))] fn record_stats_in_glean(&self) { use firefox_on_glean::metrics::networking as glean; use neqo_common::IpTosEcn;
// Metric values must be recorded as integers. Glean does not support // floating point distributions. In order to represent values <1, they // are multiplied by `PRECISION_FACTOR`. A `PRECISION_FACTOR` of // `10_000` allows one to represent fractions down to 0.0001. const PRECISION_FACTOR: u64 = 10_000;
let stats = self.conn.transport_stats();
if stats.packets_tx == 0 { return;
}
for (s, postfix) in [(stats.frame_tx, "_tx"), (stats.frame_rx, "_rx")] { let add = |label: &str, value: usize| {
glean::http_3_quic_frame_count
.get(&(label.to_string() + postfix))
.add(value.try_into().unwrap_or(i32::MAX));
};
if static_prefs::pref!("network.http.http3.ecn") { if stats.ecn_tx[IpTosEcn::Ect0] > 0 { let ratio =
(stats.ecn_tx[IpTosEcn::Ce] * PRECISION_FACTOR) / stats.ecn_tx[IpTosEcn::Ect0];
glean::http_3_ecn_ce_ect0_ratio_sent.accumulate_single_sample_signed(ratio as i64);
} if stats.ecn_rx[IpTosEcn::Ect0] > 0 { let ratio =
(stats.ecn_rx[IpTosEcn::Ce] * PRECISION_FACTOR) / stats.ecn_rx[IpTosEcn::Ect0];
glean::http_3_ecn_ce_ect0_ratio_received
.accumulate_single_sample_signed(ratio as i64);
}
glean::http_3_ecn_path_capability
.get(&"capable")
.add(stats.ecn_paths_capable as i32);
glean::http_3_ecn_path_capability
.get(&"not-capable")
.add(stats.ecn_paths_not_capable as i32);
}
// Ignore connections into the void. if stats.packets_rx != 0 { let loss = (stats.lost * PRECISION_FACTOR as usize) / stats.packets_tx;
glean::http_3_loss_ratio.accumulate_single_sample_signed(loss as i64);
}
}
/// Process input, reading incoming datagrams from the socket and passing them /// to the Neqo state machine. #[no_mangle] pubunsafeextern"C"fn neqo_http3conn_process_input(
conn: &mut NeqoHttp3Conn,
) -> ProcessInputResult { letmut bytes_read = 0;
// Attach metric instrumentation to `dgrams` iterator. letmut sum = 0;
conn.datagram_segments_received
.accumulate(dgrams.len() as u64); let datagram_segment_size_received = &mut conn.datagram_segment_size_received; let dgrams = dgrams.map(|d| {
datagram_segment_size_received.accumulate(d.len() as u64);
sum += d.len();
d
});
// Override `dgrams` ECN marks according to prefs. let ecn_enabled = static_prefs::pref!("network.http.http3.ecn"); let dgrams = dgrams.map(|mut d| { if !ecn_enabled {
d.set_tos(Default::default());
}
d
});
let now = Instant::now(); if conn.last_output_time > now { // The timer fired too early, so reschedule it. // The 1ms of extra delay is not ideal, but this is a fail
set_timer_func(
context,
u64::try_from((conn.last_output_time - now + conn.max_accumlated_time).as_millis())
.unwrap(),
); return NS_OK;
}
/// Process output, retrieving outgoing datagrams from the Neqo state machine /// and writing them to the socket. #[no_mangle] pubextern"C"fn neqo_http3conn_process_output_and_send(
conn: &mut NeqoHttp3Conn,
context: *mut c_void,
set_timer_func: SetTimerFunc,
) -> ProcessOutputAndSendResult { let now = Instant::now(); if conn.last_output_time > now { // The timer fired too early, so reschedule it. // The 1ms of extra delay is not ideal, but this is a fail
set_timer_func(
context,
u64::try_from((conn.last_output_time - now + conn.max_accumlated_time).as_millis())
.unwrap(),
); return ProcessOutputAndSendResult {
result: NS_OK,
bytes_written: 0,
};
}
letmut accumulated_time = Duration::from_nanos(0); letmut bytes_written: usize = 0; loop {
conn.last_output_time = if accumulated_time.is_zero() {
Instant::now()
} else {
now + accumulated_time
}; match conn.conn.process_output(conn.last_output_time) {
Output::Datagram(mut dg) => { if !static_prefs::pref!("network.http.http3.ecn") {
dg.set_tos(Default::default());
}
if static_prefs::pref!("network.http.http3.block_loopback_ipv6_addr")
&& matches!(dg.destination(), SocketAddr::V6(addr) if addr.ip().is_loopback())
{
qdebug!("network.http.http3.block_loopback_ipv6_addr is set, returning NS_ERROR_CONNECTION_REFUSED for localhost IPv6"); return ProcessOutputAndSendResult {
result: NS_ERROR_CONNECTION_REFUSED,
bytes_written: 0,
};
}
match conn.socket.as_mut().expect("non NSPR IO").send(&dg) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
qwarn!("dropping datagram as socket would block"); break;
}
Err(e) => {
qwarn!("failed to send datagram: {}", e); return ProcessOutputAndSendResult {
result: into_nsresult(e),
bytes_written: 0,
};
}
}
bytes_written += dg.len();
conn.datagram_segment_size_sent.accumulate(dg.len() as u64);
}
Output::Callback(to) => { if to.is_zero() {
set_timer_func(context, 1); break;
}
fn parse_headers(headers: &nsACString) -> Result<Vec<Header>, nsresult> { letmut hdrs = Vec::new(); // this is only used for headers built by Firefox. // Firefox supplies all headers already prepared for sending over http1. // They need to be split into (String, String) pairs. match str::from_utf8(headers) {
Err(_) => { return Err(NS_ERROR_INVALID_ARG);
}
Ok(h) => { for elem in h.split("\r\n").skip(1) { if elem.starts_with(':') { // colon headers are for http/2 and 3 and this is http/1 // input, so that is probably a smuggling attack of some // kind. continue;
} if elem.len() == 0 { continue;
} let hdr_str: Vec<_> = elem.splitn(2, ":").collect(); let name = hdr_str[0].trim().to_lowercase(); if is_excluded_header(&name) { continue;
} let value = if hdr_str.len() > 1 {
String::from(hdr_str[1].trim())
} else {
String::new()
};
// This is only used for telemetry. Therefore we only return error code // numbers and do not label them. Recording telemetry is easier with a // number. #[repr(C)] pubenum CloseError {
TransportInternalError,
TransportInternalErrorOther(u16),
TransportError(u64),
CryptoError(u64),
CryptoAlert(u8),
PeerAppError(u64),
PeerError(u64),
AppError(u64),
EchRetry,
}
// Close sending side of a stream with stream_id #[no_mangle] pubextern"C"fn neqo_http3conn_close_stream(
conn: &mut NeqoHttp3Conn,
stream_id: u64,
) -> nsresult { match conn.conn.stream_close_send(StreamId::from(stream_id)) {
Ok(()) => NS_OK,
Err(_) => NS_ERROR_INVALID_ARG,
}
}
// WebTransport streams can be unidirectional and bidirectional. // It is mapped to and from neqo's StreamType enum. #[repr(C)] pubenum WebTransportStreamType {
BiDi,
UniDi,
}
impl From<StreamType> for WebTransportStreamType { fn from(t: StreamType) -> WebTransportStreamType { match t {
StreamType::BiDi => WebTransportStreamType::BiDi,
StreamType::UniDi => WebTransportStreamType::UniDi,
}
}
}
impl From<WebTransportStreamType> for StreamType { fn from(t: WebTransportStreamType) -> StreamType { match t {
WebTransportStreamType::BiDi => StreamType::BiDi,
WebTransportStreamType::UniDi => StreamType::UniDi,
}
}
}
#[repr(C)] #[derive(Default)] pubstruct Http3Stats { /// Total packets received, including all the bad ones. pub packets_rx: usize, /// Duplicate packets received. pub dups_rx: usize, /// Dropped packets or dropped garbage. pub dropped_rx: usize, /// The number of packet that were saved for later processing. pub saved_datagrams: usize, /// Total packets sent. pub packets_tx: usize, /// Total number of packets that are declared lost. pub lost: usize, /// Late acknowledgments, for packets that were declared lost already. pub late_ack: usize, /// Acknowledgments for packets that contained data that was marked /// for retransmission when the PTO timer popped. pub pto_ack: usize, /// Count PTOs. Single PTOs, 2 PTOs in a row, 3 PTOs in row, etc. are counted /// separately. pub pto_counts: [usize; 16],
}
// > We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We // > could get better diagnostics by adding distinct XPCOM error codes for // > each of these, but there are a lot of places in Gecko that check // > specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to // > be checked. // // <https://searchfox.org/mozilla-central/rev/a965e3c683ecc035dee1de72bd33a8d91b1203ed/netwerk/base/nsSocketTransport2.cpp#164-168> // // TODO: `HostUnreachable` and `NetworkUnreachable` available since Rust // v1.83.0 only <https://doc.rust-lang.org/std/io/enum.ErrorKind.html>. // io::ErrorKind::HostUnreachable | io::ErrorKind::NetworkUnreachable |
io::ErrorKind::AddrNotAvailable => NS_ERROR_CONNECTION_REFUSED,
// The errors below are either not relevant for `neqo_glue`, or not // defined as `nsresult`.
io::ErrorKind::NotFound
| io::ErrorKind::PermissionDenied
| io::ErrorKind::BrokenPipe
| io::ErrorKind::InvalidData
| io::ErrorKind::WriteZero
| io::ErrorKind::Unsupported
| io::ErrorKind::Other => NS_ERROR_FAILURE,
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.