131 lines
4.9 KiB
Rust
131 lines
4.9 KiB
Rust
#![deny(warnings)]
|
|
#![allow(dead_code)]
|
|
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
|
//! xcu-relay -- NAT Traversal Relay Server (STUN/TURN)
|
|
pub mod turn;
|
|
|
|
#[derive(Debug)]
|
|
pub enum RelayError { AllocationFailed(String), PeerNotFound(String), QuotaExceeded(String) }
|
|
impl std::fmt::Display for RelayError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self { Self::AllocationFailed(e) => write!(f, "Alloc: {e}"), Self::PeerNotFound(e) => write!(f, "Peer: {e}"), Self::QuotaExceeded(e) => write!(f, "Quota: {e}") }
|
|
}
|
|
}
|
|
impl std::error::Error for RelayError {}
|
|
|
|
use std::collections::HashMap;
|
|
use std::net::IpAddr;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct SocketAddr { pub ip: IpAddr, pub port: u16 }
|
|
impl SocketAddr {
|
|
pub fn new(ip: IpAddr, port: u16) -> Self { Self { ip, port } }
|
|
}
|
|
|
|
/// STUN binding response: reflexive address (your public IP:port)
|
|
#[derive(Debug, Clone)]
|
|
pub struct StunResponse { pub mapped_addr: SocketAddr, pub transaction_id: [u8; 12] }
|
|
|
|
/// TURN allocation
|
|
#[derive(Debug, Clone)]
|
|
pub struct TurnAllocation {
|
|
pub client_addr: SocketAddr,
|
|
pub relay_addr: SocketAddr,
|
|
pub lifetime_secs: u32,
|
|
pub created_at: u64,
|
|
pub bytes_relayed: u64,
|
|
pub permissions: Vec<IpAddr>,
|
|
}
|
|
|
|
pub struct RelayServer {
|
|
allocations: HashMap<String, TurnAllocation>,
|
|
next_port: u16,
|
|
relay_ip: IpAddr,
|
|
max_allocations: usize,
|
|
max_bytes_per_alloc: u64,
|
|
}
|
|
|
|
impl RelayServer {
|
|
pub fn new(relay_ip: IpAddr, start_port: u16, max_alloc: usize) -> Self {
|
|
Self { allocations: HashMap::new(), next_port: start_port, relay_ip, max_allocations: max_alloc, max_bytes_per_alloc: 100 * 1024 * 1024 }
|
|
}
|
|
|
|
/// STUN binding request → returns reflexive address
|
|
pub fn handle_stun_binding(&self, source: SocketAddr, transaction_id: [u8; 12]) -> StunResponse {
|
|
StunResponse { mapped_addr: source, transaction_id }
|
|
}
|
|
|
|
/// TURN allocate request
|
|
pub fn allocate(&mut self, client: SocketAddr, lifetime: u32, now: u64) -> Result<TurnAllocation, RelayError> {
|
|
if self.allocations.len() >= self.max_allocations {
|
|
return Err(RelayError::AllocationFailed("Max allocations reached".into()));
|
|
}
|
|
let key = format!("{}:{}", client.ip, client.port);
|
|
let relay_port = self.next_port;
|
|
self.next_port += 1;
|
|
let alloc = TurnAllocation {
|
|
client_addr: client,
|
|
relay_addr: SocketAddr::new(self.relay_ip, relay_port),
|
|
lifetime_secs: lifetime.min(3600),
|
|
created_at: now,
|
|
bytes_relayed: 0,
|
|
permissions: Vec::new(),
|
|
};
|
|
self.allocations.insert(key, alloc.clone());
|
|
Ok(alloc)
|
|
}
|
|
|
|
/// Add permission for peer
|
|
pub fn create_permission(&mut self, client_key: &str, peer_ip: IpAddr) -> Result<(), RelayError> {
|
|
let alloc = self.allocations.get_mut(client_key).ok_or_else(|| RelayError::PeerNotFound(client_key.into()))?;
|
|
if !alloc.permissions.contains(&peer_ip) { alloc.permissions.push(peer_ip); }
|
|
Ok(())
|
|
}
|
|
|
|
/// Relay data from client to peer (if permitted)
|
|
pub fn relay_data(&mut self, client_key: &str, peer_ip: IpAddr, data_len: u64) -> Result<(), RelayError> {
|
|
let alloc = self.allocations.get_mut(client_key).ok_or_else(|| RelayError::PeerNotFound(client_key.into()))?;
|
|
if !alloc.permissions.contains(&peer_ip) {
|
|
return Err(RelayError::PeerNotFound(format!("{peer_ip} not permitted")));
|
|
}
|
|
alloc.bytes_relayed += data_len;
|
|
if alloc.bytes_relayed > self.max_bytes_per_alloc {
|
|
return Err(RelayError::QuotaExceeded(format!("{}B > {}B", alloc.bytes_relayed, self.max_bytes_per_alloc)));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Cleanup expired allocations
|
|
pub fn cleanup(&mut self, now: u64) -> usize {
|
|
let before = self.allocations.len();
|
|
self.allocations.retain(|_, a| now - a.created_at < a.lifetime_secs as u64);
|
|
before - self.allocations.len()
|
|
}
|
|
|
|
pub fn active_allocations(&self) -> usize { self.allocations.len() }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::net::Ipv4Addr;
|
|
#[test]
|
|
fn test_stun() {
|
|
let s = RelayServer::new(IpAddr::V4(Ipv4Addr::new(1,2,3,4)), 50000, 100);
|
|
let client = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10,0,0,1)), 12345);
|
|
let resp = s.handle_stun_binding(client, [0u8; 12]);
|
|
assert_eq!(resp.mapped_addr.port, 12345);
|
|
}
|
|
#[test]
|
|
fn test_turn() {
|
|
let mut s = RelayServer::new(IpAddr::V4(Ipv4Addr::new(1,2,3,4)), 50000, 100);
|
|
let client = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10,0,0,1)), 12345);
|
|
let alloc = s.allocate(client, 600, 1000).unwrap();
|
|
assert_eq!(alloc.relay_addr.port, 50000);
|
|
let key = "10.0.0.1:12345";
|
|
let peer = IpAddr::V4(Ipv4Addr::new(10,0,0,2));
|
|
s.create_permission(key, peer).unwrap();
|
|
s.relay_data(key, peer, 1000).unwrap();
|
|
}
|
|
}
|