Files
FunConnect/client/src/network/relay.rs

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)
}