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 crate::{
app::QuicScionApplication,
h3::{
client::{app::Http3ClientApp, error::EstablishError},
common::H3_INTERNAL_ERROR,
},
quic::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,
handshake_timeout: Duration,
) -> Result<ConnectionHandle<Http3ClientApp>, EstablishError> {
let (tx, rx) = oneshot::channel();
tokio::spawn(async move {
let handshake_result = match tokio::time::timeout(
handshake_timeout,
handshake(remote, &socket, server_name.as_deref(), &mut quiche_config),
)
.await
{
Ok(result) => result,
Err(_elapsed) => Err(EstablishError::Handshake),
};
let conn = match handshake_result {
Ok(conn) => conn,
Err(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() {
return;
}
let driver = QuicScionConnDriver::new(handle.clone(), socket.clone());
tokio::select! {
res = driver.run() => {
if let Err(err) = res {
tracing::warn!(?err, "client connection driver exited with a socket error");
}
}
_ = ingress::run(handle, socket) => {}
}
});
rx.await.map_err(|_| EstablishError::Handshake)?
}
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())
}