69 lines
2.2 KiB
Rust
69 lines
2.2 KiB
Rust
/// QUIC relay client — used when P2P hole punch fails
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use quinn::Connection;
|
|
use std::net::SocketAddr;
|
|
use std::time::Duration;
|
|
use tokio::time::timeout;
|
|
use uuid::Uuid;
|
|
|
|
use crate::network::quic::open_endpoint;
|
|
use crate::network::relay_selector::DEFAULT_RELAY_PORT;
|
|
|
|
/// Connect to a relay server's QUIC endpoint
|
|
pub async fn connect_relay(
|
|
relay_url: &str,
|
|
room_id: Uuid,
|
|
access_token: &str,
|
|
) -> Result<Connection> {
|
|
let (host, port) = parse_relay_url(relay_url);
|
|
|
|
let relay_addr: SocketAddr = {
|
|
let addrs: Vec<SocketAddr> = tokio::net::lookup_host(format!("{}:{}", host, port))
|
|
.await?
|
|
.collect();
|
|
addrs.into_iter().next().ok_or_else(|| anyhow!("relay DNS failed"))?
|
|
};
|
|
|
|
let ep = open_endpoint("0.0.0.0:0".parse()?)?;
|
|
|
|
tracing::info!("Connecting to relay at {}", relay_addr);
|
|
let connecting = ep.connect(relay_addr, &host)?;
|
|
let conn = timeout(Duration::from_secs(10), connecting).await
|
|
.map_err(|_| anyhow!("relay connection timed out"))?
|
|
.map_err(|e| anyhow!("relay QUIC error: {}", e))?;
|
|
|
|
// Send auth handshake on a unidirectional stream using write_chunk
|
|
let mut stream = conn.open_uni().await?;
|
|
let handshake = serde_json::json!({
|
|
"token": access_token,
|
|
"room_id": room_id.to_string(),
|
|
});
|
|
let msg = serde_json::to_vec(&handshake)?;
|
|
let len = (msg.len() as u32).to_be_bytes();
|
|
stream.write_chunk(bytes::Bytes::copy_from_slice(&len)).await
|
|
.map_err(|e| anyhow!("{}", e))?;
|
|
stream.write_chunk(bytes::Bytes::from(msg)).await
|
|
.map_err(|e| anyhow!("{}", e))?;
|
|
let _ = stream.finish();
|
|
let _ = stream.stopped().await;
|
|
|
|
tracing::info!("Relay handshake sent for room {}", room_id);
|
|
Ok(conn)
|
|
}
|
|
|
|
fn parse_relay_url(url: &str) -> (String, u16) {
|
|
let cleaned = url
|
|
.trim_start_matches("https://")
|
|
.trim_start_matches("http://")
|
|
.trim_start_matches("quic://");
|
|
|
|
if let Some((host, port_str)) = cleaned.rsplit_once(':') {
|
|
if let Ok(port) = port_str.parse::<u16>() {
|
|
return (host.to_string(), port);
|
|
}
|
|
}
|
|
|
|
(cleaned.to_string(), DEFAULT_RELAY_PORT)
|
|
}
|