use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::net::{TcpListener, UdpSocket};
use tokio::sync::{mpsc, RwLock};
use tokio::time::interval;
use tracing::{debug, info, warn};
use serde::{Serialize, Deserialize};
use rand::Rng;
use crate::{WireError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RelayId([u8; 16]);
impl RelayId {
pub fn new() -> Self {
let mut id = [0u8; 16];
getrandom::getrandom(&mut id).expect("Failed to generate random relay ID");
Self(id)
}
pub fn from_bytes(bytes: [u8; 16]) -> Self {
Self(bytes)
}
pub fn to_bytes(&self) -> [u8; 16] {
self.0
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
}
impl Default for RelayId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayCredentials {
pub username: String,
pub password: String,
pub expires_at: u64,
}
#[derive(Debug, Clone)]
pub struct RelayAllocation {
pub id: RelayId,
pub client_addr: SocketAddr,
pub relay_addr: SocketAddr,
pub credentials: RelayCredentials,
pub created_at: Instant,
pub last_activity: Instant,
pub client_channel: mpsc::Sender<Vec<u8>>,
}
impl RelayAllocation {
pub fn is_expired(&self) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
now > self.credentials.expires_at
}
pub fn is_idle(&self, timeout: Duration) -> bool {
self.last_activity.elapsed() > timeout
}
}
#[derive(Debug, Clone)]
pub struct RelayConfig {
pub bind_addr: SocketAddr,
pub max_allocations: usize,
pub allocation_lifetime: u64,
pub idle_timeout: u64,
pub auth_required: bool,
}
impl Default for RelayConfig {
fn default() -> Self {
Self {
bind_addr: "0.0.0.0:3478".parse().unwrap(),
max_allocations: 1000,
allocation_lifetime: 3600, idle_timeout: 600, auth_required: true,
}
}
}
pub struct RelayServer {
config: RelayConfig,
allocations: Arc<RwLock<HashMap<RelayId, RelayAllocation>>>,
client_map: Arc<RwLock<HashMap<SocketAddr, RelayId>>>,
udp_socket: Option<Arc<UdpSocket>>,
tcp_listener: Option<TcpListener>,
}
impl RelayServer {
pub fn new(config: RelayConfig) -> Self {
Self {
config,
allocations: Arc::new(RwLock::new(HashMap::new())),
client_map: Arc::new(RwLock::new(HashMap::new())),
udp_socket: None,
tcp_listener: None,
}
}
pub async fn start(&mut self) -> Result<()> {
info!("Starting relay server on {}", self.config.bind_addr);
let udp_socket = UdpSocket::bind(self.config.bind_addr).await
.map_err(|e| WireError::BindError(format!("Failed to bind UDP socket: {}", e)))?;
self.udp_socket = Some(Arc::new(udp_socket));
let tcp_listener = TcpListener::bind(self.config.bind_addr).await
.map_err(|e| WireError::BindError(format!("Failed to bind TCP listener: {}", e)))?;
self.tcp_listener = Some(tcp_listener);
info!("Relay server started successfully");
Ok(())
}
pub fn config(&self) -> &RelayConfig {
&self.config
}
pub async fn create_allocation(
&self,
client_addr: SocketAddr,
credentials: RelayCredentials,
) -> Result<RelayId> {
let allocation_count = self.allocations.read().await.len();
if allocation_count >= self.config.max_allocations {
return Err(WireError::ResourceExhausted("Maximum allocations reached".to_string()).into());
}
if self.config.auth_required && self.is_expired(&credentials) {
return Err(WireError::AuthenticationError("Credentials expired".to_string()).into());
}
let relay_addr = self.generate_relay_address(client_addr)?;
let allocation_id = RelayId::new();
let (tx, _rx) = mpsc::channel::<Vec<u8>>(100);
let allocation = RelayAllocation {
id: allocation_id,
client_addr,
relay_addr,
credentials,
created_at: Instant::now(),
last_activity: Instant::now(),
client_channel: tx,
};
self.allocations.write().await.insert(allocation_id, allocation);
self.client_map.write().await.insert(client_addr, allocation_id);
info!("Created relay allocation {} for client {}", allocation_id.to_hex(), client_addr);
let allocations = self.allocations.clone();
let client_map = self.client_map.clone();
let allocation_id_copy = allocation_id;
let idle_timeout = Duration::from_secs(self.config.idle_timeout);
tokio::spawn(async move {
let mut interval = interval(Duration::from_secs(60)); loop {
interval.tick().await;
let should_continue = {
let allocations = allocations.read().await;
if let Some(allocation) = allocations.get(&allocation_id_copy) {
!allocation.is_idle(idle_timeout)
} else {
false }
};
if !should_continue {
allocations.write().await.remove(&allocation_id_copy);
client_map.write().await.remove(&client_addr);
info!("Cleaned up idle allocation {}", allocation_id_copy.to_hex());
break;
}
}
});
Ok(allocation_id)
}
pub async fn relay_data(
&self,
from_addr: SocketAddr,
to_addr: SocketAddr,
data: Vec<u8>,
) -> Result<()> {
let allocation_id = self.client_map.read().await.get(&to_addr)
.copied()
.ok_or_else(|| WireError::NotFound("No allocation for destination".to_string()))?;
let mut allocations = self.allocations.write().await;
if let Some(allocation) = allocations.get_mut(&allocation_id) {
allocation.last_activity = Instant::now();
let data_len = data.len();
if let Err(e) = allocation.client_channel.try_send(data) {
warn!("Failed to send data to client {}: {}", allocation_id.to_hex(), e);
return Err(WireError::ChannelError("Failed to send data".to_string()).into());
}
debug!("Relayed {} bytes from {} to {}", data_len, from_addr, to_addr);
Ok(())
} else {
Err(WireError::NotFound("Allocation not found".to_string()).into())
}
}
pub async fn get_allocation(&self, id: RelayId) -> Option<RelayAllocation> {
self.allocations.read().await.get(&id).cloned()
}
pub async fn remove_allocation(&self, id: RelayId) -> Result<()> {
let allocation = self.allocations.write().await.remove(&id);
if let Some(allocation) = allocation {
self.client_map.write().await.remove(&allocation.client_addr);
info!("Removed allocation {} for client {}", id.to_hex(), allocation.client_addr);
}
Ok(())
}
fn generate_relay_address(&self, client_addr: SocketAddr) -> Result<SocketAddr> {
let base_port = self.config.bind_addr.port();
let client_port = client_addr.port();
let relay_port = base_port + (client_port % 1000) + 1;
Ok(SocketAddr::new(self.config.bind_addr.ip(), relay_port))
}
fn is_expired(&self, credentials: &RelayCredentials) -> bool {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
now > credentials.expires_at
}
pub async fn get_stats(&self) -> RelayStats {
let allocations = self.allocations.read().await;
RelayStats {
active_allocations: allocations.len(),
total_allocations: allocations.len(), uptime: Duration::from_secs(0), }
}
}
#[derive(Debug, Clone)]
pub struct RelayStats {
pub active_allocations: usize,
pub total_allocations: usize,
pub uptime: Duration,
}
pub struct RelayClient {
server_addr: SocketAddr,
socket: Option<Arc<UdpSocket>>,
allocation: Option<RelayId>,
}
impl RelayClient {
pub fn new(server_addr: SocketAddr) -> Self {
Self {
server_addr,
socket: None,
allocation: None,
}
}
pub async fn connect(&mut self, _credentials: RelayCredentials) -> Result<RelayId> {
let socket = UdpSocket::bind("0.0.0.0:0").await
.map_err(|e| WireError::BindError(format!("Failed to bind UDP socket: {}", e)))?;
socket.connect(self.server_addr).await
.map_err(|e| WireError::ConnectionError(format!("Failed to connect to relay: {}", e)))?;
self.socket = Some(Arc::new(socket));
let allocation_id = RelayId::new();
self.allocation = Some(allocation_id);
info!("Connected to relay server at {} with allocation {}", self.server_addr, allocation_id.to_hex());
Ok(allocation_id)
}
pub async fn send(&self, data: Vec<u8>) -> Result<()> {
if let Some(ref socket) = self.socket {
socket.send(&data).await
.map_err(|e| WireError::ConnectionError(format!("Failed to send data: {}", e)))?;
Ok(())
} else {
Err(WireError::NotConnected("Not connected to relay".to_string()).into())
}
}
pub async fn recv(&self, buf: &mut [u8]) -> Result<usize> {
if let Some(ref socket) = self.socket {
let len = socket.recv(buf).await
.map_err(|e| WireError::ConnectionError(format!("Failed to receive data: {}", e)))?;
Ok(len)
} else {
Err(WireError::NotConnected("Not connected to relay".to_string()).into())
}
}
pub async fn disconnect(&mut self) -> Result<()> {
if let Some(allocation_id) = self.allocation {
info!("Disconnecting from relay server, releasing allocation {}", allocation_id.to_hex());
self.allocation = None;
self.socket = None;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_relay_id_generation() {
let id1 = RelayId::new();
let id2 = RelayId::new();
assert_ne!(id1, id2);
assert_eq!(id1.to_bytes().len(), 16);
assert_eq!(id2.to_bytes().len(), 16);
}
#[tokio::test]
async fn test_relay_config_default() {
let config = RelayConfig::default();
assert_eq!(config.bind_addr.port(), 3478);
assert_eq!(config.max_allocations, 1000);
assert_eq!(config.allocation_lifetime, 3600);
assert!(config.auth_required);
}
}