use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration;
use tokio::net::UdpSocket;
pub const MAGIC_COOKIE: u32 = 0x2112_A442;
pub const BINDING_REQUEST: u16 = 0x0001;
pub const BINDING_SUCCESS: u16 = 0x0101;
pub const ATTR_XOR_MAPPED_ADDRESS: u16 = 0x0020;
pub const ATTR_MAPPED_ADDRESS: u16 = 0x0001;
const FAMILY_IPV4: u8 = 0x01;
const FAMILY_IPV6: u8 = 0x02;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum StunError {
#[error("STUN message truncated")]
Truncated,
#[error("bad STUN magic cookie")]
BadMagicCookie,
#[error("STUN transaction id mismatch")]
TransactionIdMismatch,
#[error("no mapped address in STUN response")]
NoMappedAddress,
#[error("unexpected STUN message type: {0:#06x}")]
UnexpectedType(u16),
#[error("STUN io: {0}")]
Io(String),
#[error("STUN request timed out")]
Timeout,
}
pub fn encode_binding_request(transaction_id: &[u8; 12]) -> Vec<u8> {
let mut msg = Vec::with_capacity(20);
msg.extend_from_slice(&BINDING_REQUEST.to_be_bytes()); msg.extend_from_slice(&0u16.to_be_bytes()); msg.extend_from_slice(&MAGIC_COOKIE.to_be_bytes()); msg.extend_from_slice(transaction_id); msg
}
pub fn parse_binding_response(
msg: &[u8],
expected_txid: Option<&[u8; 12]>,
) -> Result<SocketAddr, StunError> {
if msg.len() < 20 {
return Err(StunError::Truncated);
}
let msg_type = u16::from_be_bytes([msg[0], msg[1]]);
let msg_len = u16::from_be_bytes([msg[2], msg[3]]) as usize;
let cookie = u32::from_be_bytes([msg[4], msg[5], msg[6], msg[7]]);
if cookie != MAGIC_COOKIE {
return Err(StunError::BadMagicCookie);
}
if msg_type != BINDING_SUCCESS {
return Err(StunError::UnexpectedType(msg_type));
}
let txid: [u8; 12] = msg[8..20].try_into().map_err(|_| StunError::Truncated)?;
if let Some(expected) = expected_txid {
if &txid != expected {
return Err(StunError::TransactionIdMismatch);
}
}
if msg.len() < 20 + msg_len {
return Err(StunError::Truncated);
}
let mut fallback: Option<SocketAddr> = None;
let mut off = 20usize;
let end = 20 + msg_len;
while off + 4 <= end {
let attr_type = u16::from_be_bytes([msg[off], msg[off + 1]]);
let attr_len = u16::from_be_bytes([msg[off + 2], msg[off + 3]]) as usize;
let val_start = off + 4;
let val_end = val_start + attr_len;
if val_end > end {
return Err(StunError::Truncated);
}
let value = &msg[val_start..val_end];
match attr_type {
ATTR_XOR_MAPPED_ADDRESS => {
return decode_mapped_address(value, &txid, true);
}
ATTR_MAPPED_ADDRESS if fallback.is_none() => {
fallback = decode_mapped_address(value, &txid, false).ok();
}
_ => {}
}
off = val_end + ((4 - (attr_len % 4)) % 4);
}
fallback.ok_or(StunError::NoMappedAddress)
}
fn decode_mapped_address(
value: &[u8],
txid: &[u8; 12],
xor: bool,
) -> Result<SocketAddr, StunError> {
if value.len() < 4 {
return Err(StunError::Truncated);
}
let family = value[1];
let raw_port = u16::from_be_bytes([value[2], value[3]]);
let cookie_be = MAGIC_COOKIE.to_be_bytes();
let port = if xor {
raw_port ^ ((MAGIC_COOKIE >> 16) as u16)
} else {
raw_port
};
match family {
FAMILY_IPV4 => {
if value.len() < 8 {
return Err(StunError::Truncated);
}
let mut octets = [value[4], value[5], value[6], value[7]];
if xor {
for (i, o) in octets.iter_mut().enumerate() {
*o ^= cookie_be[i];
}
}
Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(octets)), port))
}
FAMILY_IPV6 => {
if value.len() < 20 {
return Err(StunError::Truncated);
}
let mut octets = [0u8; 16];
octets.copy_from_slice(&value[4..20]);
if xor {
let mut key = [0u8; 16];
key[..4].copy_from_slice(&cookie_be);
key[4..].copy_from_slice(txid);
for (o, k) in octets.iter_mut().zip(key.iter()) {
*o ^= *k;
}
}
Ok(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(octets)), port))
}
other => Err(StunError::UnexpectedType(other as u16)),
}
}
pub async fn query_reflexive_address(
socket: &UdpSocket,
server: SocketAddr,
timeout: Duration,
) -> Result<SocketAddr, StunError> {
let txid = new_transaction_id();
let req = encode_binding_request(&txid);
socket
.send_to(&req, server)
.await
.map_err(|e| StunError::Io(e.to_string()))?;
let mut buf = [0u8; 512];
let deadline = tokio::time::Instant::now() + timeout;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(StunError::Timeout);
}
let (n, from) = match tokio::time::timeout(remaining, socket.recv_from(&mut buf)).await {
Ok(Ok(x)) => x,
Ok(Err(e)) => return Err(StunError::Io(e.to_string())),
Err(_) => return Err(StunError::Timeout),
};
if from != server {
continue;
}
return parse_binding_response(&buf[..n], Some(&txid));
}
}
pub fn new_transaction_id() -> [u8; 12] {
use ring::rand::{SecureRandom, SystemRandom};
let mut id = [0u8; 12];
SystemRandom::new()
.fill(&mut id)
.expect("OS CSPRNG must be available to generate a STUN transaction id");
id
}