From 8363217c96226e44317461aa5bbc8d563c60686f Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 26 Sep 2025 16:15:25 +0200 Subject: [PATCH] Replace STUN/TURN hex literals with RFC/project constants; refactor main to use library exports --- src/constants.rs | 10 ++++++++++ src/main.rs | 28 +++++++++++----------------- src/stun.rs | 22 +++++++++++----------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 686a3f9..f6f01eb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -10,6 +10,10 @@ pub const METHOD_ALLOCATE: u16 = 0x0003; // Common response/error types pub const RESP_BINDING_SUCCESS: u16 = 0x0101; +// STUN/TURN class bits per RFC5389/RFC5766 +pub const CLASS_SUCCESS: u16 = 0x0100; +pub const CLASS_ERROR: u16 = 0x0110; + // Common attribute types pub const ATTR_USERNAME: u16 = 0x0006; pub const ATTR_MESSAGE_INTEGRITY: u16 = 0x0008; @@ -22,3 +26,9 @@ pub const ATTR_XOR_RELAYED_ADDRESS: u16 = 0x0016; // Some helper values pub const FAMILY_IPV4: u8 = 0x01; +// Fingerprint XOR magic (XOR with CRC32 for FINGERPRINT attribute) +pub const FINGERPRINT_XOR: u32 = 0x5354554e; + +// Length of HMAC-SHA1 (MESSAGE-INTEGRITY) +pub const HMAC_SHA1_LEN: usize = 20; + diff --git a/src/main.rs b/src/main.rs index c64d728..addca6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,18 +3,12 @@ use std::sync::Arc; use tokio::net::UdpSocket; use tracing::{info, error}; -mod stun; -mod auth; -mod traits; -mod models; -mod alloc; -mod constants; -use crate::constants::*; -use crate::auth::InMemoryStore; -use crate::stun::{parse_message, build_401_response}; -use crate::traits::CredentialStore; -// use crate::models::stun::StunHeader; // currently unused -use crate::alloc::AllocationManager; +// Use the library crate's public modules instead of local `mod` declarations. +use niom_turn::constants::*; +use niom_turn::auth::InMemoryStore; +use niom_turn::stun::{parse_message, build_401_response, find_message_integrity, validate_message_integrity, build_success_response, encode_xor_relayed_address}; +use niom_turn::traits::CredentialStore; +use niom_turn::alloc::AllocationManager; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -62,7 +56,7 @@ async fn udp_reader_loop(udp: Arc, creds: InMemoryStore, allocs: Allo if let Ok(msg) = parse_message(&buf[..len]) { tracing::info!("STUN/TURN message from {} type=0x{:04x} len={}", peer, msg.header.msg_type, len); // If MESSAGE-INTEGRITY present, attempt validation using credential store - if let Some(_mi_attr) = crate::stun::find_message_integrity(&msg) { + if let Some(_mi_attr) = find_message_integrity(&msg) { // For MVP we expect username attribute (USERNAME) to be present let username_attr = msg.attributes.iter().find(|a| a.typ == ATTR_USERNAME); if let Some(u) = username_attr { @@ -71,7 +65,7 @@ async fn udp_reader_loop(udp: Arc, creds: InMemoryStore, allocs: Allo let store = creds.clone(); let pw = store.get_password(username).await; if let Some(password) = pw { - let valid = crate::stun::validate_message_integrity(&msg, &password); + let valid = validate_message_integrity(&msg, &password); if valid { tracing::info!("MI valid for user {}", username); // If this is an Allocate request, perform allocation @@ -85,7 +79,7 @@ async fn udp_reader_loop(udp: Arc, creds: InMemoryStore, allocs: Allo out.extend_from_slice(&MAGIC_COOKIE_U32.to_be_bytes()); out.extend_from_slice(&msg.header.transaction_id); // RFC: XOR-RELAYED-ADDRESS (0x0016) - let attr_val = crate::stun::encode_xor_relayed_address(&relay_addr, &msg.header.transaction_id); + let attr_val = encode_xor_relayed_address(&relay_addr, &msg.header.transaction_id); out.extend_from_slice(&ATTR_XOR_RELAYED_ADDRESS.to_be_bytes()); out.extend_from_slice(&((attr_val.len() as u16).to_be_bytes())); out.extend_from_slice(&attr_val); @@ -102,7 +96,7 @@ async fn udp_reader_loop(udp: Arc, creds: InMemoryStore, allocs: Allo } } // default success response - let resp = crate::stun::build_success_response(&msg.header); + let resp = build_success_response(&msg.header); let _ = udp.send_to(&resp, &peer).await; continue; } else { @@ -126,7 +120,7 @@ async fn udp_reader_loop(udp: Arc, creds: InMemoryStore, allocs: Allo out.extend_from_slice(&0u16.to_be_bytes()); out.extend_from_slice(&MAGIC_COOKIE_U32.to_be_bytes()); out.extend_from_slice(&msg.header.transaction_id); - let attr_val = crate::stun::encode_xor_relayed_address(&relay_addr, &msg.header.transaction_id); + let attr_val = encode_xor_relayed_address(&relay_addr, &msg.header.transaction_id); out.extend_from_slice(&ATTR_XOR_RELAYED_ADDRESS.to_be_bytes()); out.extend_from_slice(&((attr_val.len() as u16).to_be_bytes())); out.extend_from_slice(&attr_val); diff --git a/src/stun.rs b/src/stun.rs index 58c0232..049bc32 100644 --- a/src/stun.rs +++ b/src/stun.rs @@ -48,23 +48,23 @@ pub fn parse_message(buf: &[u8]) -> Result { pub fn build_401_response(req: &StunHeader, realm: &str, nonce: &str, _err_code: u16) -> Vec { use bytes::BytesMut; let mut buf = BytesMut::new(); - // Error response type for TURN often uses same method with error bit set; here we reuse 0x0111 placeholder - let msg_type: u16 = 0x0111; + // Error response type for TURN: reuse the request method with error class bits set + let msg_type: u16 = req.msg_type | CLASS_ERROR; buf.extend_from_slice(&msg_type.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); // length buf.extend_from_slice(&MAGIC_COOKIE_BYTES); buf.extend_from_slice(&req.transaction_id); - // REALM (0x0014) + // REALM (RFC attr) let realm_bytes = realm.as_bytes(); - buf.extend_from_slice(&0x0014u16.to_be_bytes()); + buf.extend_from_slice(&ATTR_REALM.to_be_bytes()); buf.extend_from_slice(&(realm_bytes.len() as u16).to_be_bytes()); buf.extend_from_slice(realm_bytes); while (buf.len() % 4) != 0 { buf.extend_from_slice(&[0]); } - // NONCE (0x0015) + // NONCE (RFC attr) let nonce_bytes = nonce.as_bytes(); - buf.extend_from_slice(&0x0015u16.to_be_bytes()); + buf.extend_from_slice(&ATTR_NONCE.to_be_bytes()); buf.extend_from_slice(&(nonce_bytes.len() as u16).to_be_bytes()); buf.extend_from_slice(nonce_bytes); while (buf.len() % 4) != 0 { buf.extend_from_slice(&[0]); } @@ -122,7 +122,7 @@ pub fn compute_fingerprint(msg: &[u8]) -> u32 { let mut hasher = Hasher::new(); hasher.update(msg); let crc = hasher.finalize(); - crc ^ 0x5354554e + crc ^ FINGERPRINT_XOR } /// Compute MESSAGE-INTEGRITY (HMAC-SHA1) over the message @@ -147,7 +147,7 @@ pub fn encode_xor_relayed_address(addr: &std::net::SocketAddr, _trans_id: &[u8;1 match addr.ip() { IpAddr::V4(v4) => { out.push(0); // first 8 bits zero per spec - out.push(0x01); // family: 0x01 for IPv4 + out.push(FAMILY_IPV4); // family: IPv4 // xport = port ^ (magic_cookie >> 16) let port = addr.port(); let xport = (port ^ ((MAGIC_COOKIE_U32 >> 16) as u16)) as u16; @@ -168,7 +168,7 @@ pub fn encode_xor_relayed_address(addr: &std::net::SocketAddr, _trans_id: &[u8;1 /// Decode XOR-RELAYED-ADDRESS attribute value into SocketAddr (IPv4 only) pub fn decode_xor_relayed_address(value: &[u8], _trans_id: &[u8;12]) -> Option { if value.len() < 8 { return None; } - if value[1] != 0x01 { return None; } // not IPv4 + if value[1] != FAMILY_IPV4 { return None; } // not IPv4 let xport = u16::from_be_bytes([value[2], value[3]]); let port = xport ^ ((MAGIC_COOKIE_U32 >> 16) as u16); let cookie_bytes = MAGIC_COOKIE_BYTES; @@ -218,7 +218,7 @@ mod tests { let mut buf = BytesMut::new(); buf.extend_from_slice(&METHOD_BINDING.to_be_bytes()); // Binding Request buf.extend_from_slice(&0u16.to_be_bytes()); // length placeholder - buf.extend_from_slice(&0x2112A442u32.to_be_bytes()); + buf.extend_from_slice(&MAGIC_COOKIE_BYTES); let trans = [9u8; 12]; buf.extend_from_slice(&trans); @@ -232,7 +232,7 @@ mod tests { // MESSAGE-INTEGRITY placeholder (0x0008) length 20 let mi_attr_offset = buf.len(); buf.extend_from_slice(&ATTR_MESSAGE_INTEGRITY.to_be_bytes()); - buf.extend_from_slice(&(20u16).to_be_bytes()); + buf.extend_from_slice(&((HMAC_SHA1_LEN as u16).to_be_bytes())); let mi_val_pos = buf.len(); buf.extend_from_slice(&[0u8;20]); while (buf.len() % 4) != 0 { buf.extend_from_slice(&[0u8]); }