mod ingress;
use std::{sync::Arc, time::Duration};
use ring::rand::{SecureRandom, SystemRandom};
use sciparse::address::ip_socket_addr::ScionSocketIpAddr;
use tokio::sync::{Notify, oneshot};
use tokio_util::sync::CancellationToken;
use crate::{
app::QuicScionApplication,
h3::{
client::{
app::{Http3ClientApp, close_connection, on_driver_stopped},
error::EstablishError,
lifetime::{CloseReason, ConnLifetime},
},
common::H3_INTERNAL_ERROR,
},
quic::{
cert_verifier::RejectionReport,
connection::{ConnectionHandle, IsdAsnPair, QuicScionConn, QuicScionConnDriver},
},
socket::GenericScionUdpSocket,
};
pub(crate) async fn connect(
remote: ScionSocketIpAddr,
socket: Arc<dyn GenericScionUdpSocket>,
server_name: Option<String>,
mut quiche_config: squiche::Config,
rejection: Option<RejectionReport>,
handshake_timeout: Duration,
closed: CancellationToken,
lifetime: Arc<ConnLifetime>,
) -> Result<ConnectionHandle<Http3ClientApp>, EstablishError> {
let (mut tx, rx) = oneshot::channel();
tokio::spawn(async move {
let _lifetime_guard = CloseOnDrop(lifetime.clone());
let handshake_result = tokio::select! {
res = tokio::time::timeout(
handshake_timeout,
handshake(remote, &socket, server_name.as_deref(), &mut quiche_config),
) => match res {
Ok(result) => result,
Err(_elapsed) => Err(EstablishError::Handshake),
},
_ = closed.cancelled() => return,
_ = tx.closed() => return,
};
let conn = match handshake_result {
Ok(conn) => conn,
Err(err) => {
let err = match rejection.as_ref().and_then(RejectionReport::take) {
Some(rejected) => EstablishError::CertificateRejected(rejected),
None => err,
};
let _ = tx.send(Err(err));
return;
}
};
let mut inner = conn;
if inner.application_proto() != b"h3" {
let _ = inner.close(true, H3_INTERNAL_ERROR, b"expected h3 alpn");
let _ = tx.send(Err(EstablishError::AlpnMismatch));
return;
}
let app = Http3ClientApp::on_established(&mut inner, &());
if app.h3.is_none() {
let _ = tx.send(Err(EstablishError::H3Init));
return;
}
let asn_pair = IsdAsnPair {
from: socket.local_addr().isd_asn(),
to: remote.isd_asn(),
};
let handle = ConnectionHandle::new(
Notify::new(),
QuicScionConn {
asn_pair,
inner,
app,
},
);
if tx.send(Ok(handle.clone())).is_err() {
close_connection(&handle);
}
let driver = QuicScionConnDriver::new(handle.clone(), socket.clone());
let reason = tokio::select! {
res = driver.run() => match res {
Ok(()) => close_reason(&handle),
Err(err) => {
tracing::warn!(?err, "client connection driver exited with a socket error");
CloseReason::Io
}
},
_ = ingress::run(handle.clone(), socket) =>{
if handle.lock().inner.is_closed() {
close_reason(&handle)
} else {
CloseReason::Io
}
},
_ = close_on_cancel(&handle, &closed) => CloseReason::Local,
};
on_driver_stopped(&handle);
lifetime.close(reason);
});
rx.await.map_err(|_| EstablishError::Handshake)?
}
struct CloseOnDrop(Arc<ConnLifetime>);
impl Drop for CloseOnDrop {
fn drop(&mut self) {
self.0.close(CloseReason::Other);
}
}
fn close_reason(handle: &ConnectionHandle<Http3ClientApp>) -> CloseReason {
let conn = handle.lock();
if conn.app.locally_closed {
CloseReason::Local
} else if conn.inner.is_timed_out() {
CloseReason::IdleTimeout
} else if conn.inner.peer_error().is_some() {
CloseReason::Peer
} else {
CloseReason::Other
}
}
async fn close_on_cancel(handle: &ConnectionHandle<Http3ClientApp>, closed: &CancellationToken) {
closed.cancelled().await;
close_connection(handle);
std::future::pending::<()>().await
}
async fn handshake(
remote: ScionSocketIpAddr,
socket: &Arc<dyn GenericScionUdpSocket>,
server_name: Option<&str>,
config: &mut squiche::Config,
) -> Result<squiche::Connection, EstablishError> {
let scid = generate_connection_id();
let local_addr = socket.local_addr().socket_addr();
let remote_addr = remote.socket_addr();
let mut conn = squiche::connect(server_name, &scid, local_addr, remote_addr, config)
.map_err(EstablishError::Quic)?;
let mut send_buf = Box::new([0u8; 65535]);
let mut recv_buf = Box::new([0u8; 65535]);
loop {
loop {
match conn.send(send_buf.as_mut()) {
Ok((n, info)) => {
let to = ScionSocketIpAddr::new(remote.isd_asn(), info.to.ip(), info.to.port());
socket
.send_to(&send_buf[..n], to)
.await
.map_err(EstablishError::Io)?;
}
Err(squiche::Error::Done) => break,
Err(err) => return Err(EstablishError::Quic(err)),
}
}
if conn.is_established() {
return Ok(conn);
}
if conn.is_closed() {
return Err(EstablishError::Handshake);
}
let sleep = conn.timeout().unwrap_or(Duration::from_secs(1));
tokio::select! {
res = socket.recv_from(recv_buf.as_mut()) => {
let (len, from) = res.map_err(EstablishError::Io)?;
let from = from.socket_addr();
let to = socket.local_addr().socket_addr();
let recv_info = squiche::RecvInfo { from, to };
if let Err(err) = conn.recv(&mut recv_buf[..len], recv_info) {
tracing::warn!(?err, "client handshake: error feeding inbound packet");
}
}
_ = tokio::time::sleep(sleep) => {
conn.on_timeout();
}
}
}
}
fn generate_connection_id() -> squiche::ConnectionId<'static> {
let mut scid = [0u8; squiche::MAX_CONN_ID_LEN];
SystemRandom::new()
.fill(&mut scid)
.expect("system RNG fill");
squiche::ConnectionId::from_vec(scid.to_vec())
}