stun.rs
1 //! Sovereign STUN Server 2 //! 3 //! Lightweight STUN server for NAT discovery without external dependencies. 4 //! 5 //! # Design 6 //! 7 //! - Single UDP socket (no multi-IP requirement for basic XorMappedAddress) 8 //! - Zero-log policy: NO source IPs, NO transaction IDs logged 9 //! - Uses `stun_proto` v1.0 for protocol handling (aligned with abzu-transport) 10 11 use std::net::SocketAddr; 12 use std::sync::atomic::{AtomicBool, Ordering}; 13 use std::sync::Arc; 14 15 use stun_proto::types::attribute::XorMappedAddress; 16 use stun_proto::types::message::{ 17 Message, MessageClass, MessageType, MessageWrite, MessageWriteExt, MessageWriteVec, 18 TransactionId, BINDING, 19 }; 20 use tokio::net::UdpSocket; 21 use tracing::{debug, trace}; 22 23 /// Default STUN port per RFC 5389 24 pub const DEFAULT_STUN_PORT: u16 = 3478; 25 26 /// Sovereign STUN server configuration 27 #[derive(Debug, Clone)] 28 pub struct StunServerConfig { 29 /// UDP bind address (default: 0.0.0.0:3478) 30 pub bind_addr: SocketAddr, 31 } 32 33 impl Default for StunServerConfig { 34 fn default() -> Self { 35 Self { 36 bind_addr: ([0, 0, 0, 0], DEFAULT_STUN_PORT).into(), 37 } 38 } 39 } 40 41 /// Run the STUN server 42 /// 43 /// Binds to the configured UDP port and responds to STUN Binding Requests. 44 /// This function runs until the shutdown signal is received. 45 /// 46 /// # Zero-Log Policy 47 /// 48 /// This server explicitly does NOT log: 49 /// - Source IP addresses 50 /// - Transaction IDs 51 /// - Any identifying information 52 /// 53 /// Only aggregate statistics (request count) are logged at trace level. 54 pub async fn run_stun_server( 55 config: StunServerConfig, 56 shutdown: Arc<AtomicBool>, 57 ) -> std::io::Result<()> { 58 let socket = UdpSocket::bind(config.bind_addr).await?; 59 60 // Log only the bound address, nothing about clients 61 debug!( 62 bound = %config.bind_addr, 63 "Sovereign STUN server ready" 64 ); 65 66 let mut buf = [0u8; 576]; // RFC recommended minimum 67 let mut request_count: u64 = 0; 68 69 loop { 70 // Check shutdown signal 71 if shutdown.load(Ordering::Relaxed) { 72 debug!("STUN server shutting down"); 73 break; 74 } 75 76 // Use timeout to allow periodic shutdown checks 77 let recv_result = tokio::time::timeout( 78 std::time::Duration::from_secs(1), 79 socket.recv_from(&mut buf), 80 ) 81 .await; 82 83 let (len, source) = match recv_result { 84 Ok(Ok((len, source))) => (len, source), 85 Ok(Err(_)) => continue, // I/O error, continue 86 Err(_) => continue, // Timeout, check shutdown and continue 87 }; 88 89 // Parse STUN message 90 let msg: Message<'_> = match Message::from_bytes(&buf[..len]) { 91 Ok(msg) => msg, 92 Err(_) => continue, // Invalid STUN message, silently drop 93 }; 94 95 // Only handle Binding Requests 96 let mtype = msg.get_type(); 97 if mtype.class() != MessageClass::Request || mtype.method() != BINDING { 98 continue; 99 } 100 101 // Build response with XOR-MAPPED-ADDRESS 102 if let Some(response) = build_binding_response(msg.transaction_id(), source) { 103 // Send response (ignore errors, stateless protocol) 104 let _ = socket.send_to(&response, source).await; 105 request_count += 1; 106 107 // Periodic aggregate stat (no identifying info) 108 if request_count.is_multiple_of(1000) { 109 trace!(count = request_count, "STUN requests served"); 110 } 111 } 112 } 113 114 Ok(()) 115 } 116 117 /// Build a STUN Binding Success Response with XOR-MAPPED-ADDRESS 118 fn build_binding_response(transaction_id: TransactionId, source: SocketAddr) -> Option<Vec<u8>> { 119 let mtype = MessageType::from_class_method(MessageClass::Success, BINDING); 120 let mut msg_builder = Message::builder(mtype, transaction_id, MessageWriteVec::new()); 121 122 // Create XOR-MAPPED-ADDRESS with socket address 123 let xma = XorMappedAddress::new(source, transaction_id); 124 125 // Add attribute (mutable operation, returns Result<()>) 126 msg_builder.add_attribute(&xma).ok()?; 127 Some(msg_builder.finish()) 128 } 129 130 #[cfg(test)] 131 mod tests { 132 use super::*; 133 use stun_proto::types::attribute::AttributeStaticType; 134 135 #[test] 136 fn test_default_config() { 137 let config = StunServerConfig::default(); 138 assert_eq!(config.bind_addr.port(), DEFAULT_STUN_PORT); 139 } 140 141 #[tokio::test] 142 async fn test_build_binding_response() { 143 let tid = TransactionId::generate(); 144 let source: SocketAddr = "203.0.113.1:54321".parse().unwrap(); 145 146 let response = build_binding_response(tid, source); 147 assert!(response.is_some()); 148 149 let response = response.unwrap(); 150 assert!(!response.is_empty()); 151 152 // Parse the response 153 let msg: Message<'_> = Message::from_bytes(&response).expect("Should parse"); 154 assert_eq!(msg.transaction_id(), tid); 155 assert_eq!(msg.get_type().class(), MessageClass::Success); 156 assert_eq!(msg.get_type().method(), BINDING); 157 158 // Verify XOR-MAPPED-ADDRESS is present 159 let raw_attr = msg.raw_attribute(XorMappedAddress::TYPE); 160 assert!(raw_attr.is_some()); 161 162 // Decode and verify address 163 let xma = XorMappedAddress::try_from(&raw_attr.unwrap()).expect("Should decode"); 164 assert_eq!(xma.addr(tid), source); 165 } 166 }