use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use crate::scope::fold_ip;
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;
pub type TransactionId = [u8; 12];
#[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 usable 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: &TransactionId) -> 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<&TransactionId>,
) -> 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: TransactionId = 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)
}
pub fn parse_binding_request(datagram: &[u8]) -> Result<TransactionId, StunError> {
if datagram.len() < 20 {
return Err(StunError::Truncated);
}
let msg_type = u16::from_be_bytes([datagram[0], datagram[1]]);
let msg_len = u16::from_be_bytes([datagram[2], datagram[3]]) as usize;
let cookie = u32::from_be_bytes([datagram[4], datagram[5], datagram[6], datagram[7]]);
if cookie != MAGIC_COOKIE {
return Err(StunError::BadMagicCookie);
}
if msg_type != BINDING_REQUEST {
return Err(StunError::UnexpectedType(msg_type));
}
if datagram.len() < 20 + msg_len {
return Err(StunError::Truncated);
}
Ok(datagram[8..20]
.try_into()
.expect("slice of exactly 12 bytes"))
}
pub fn encode_binding_success(transaction_id: &TransactionId, reflexive: SocketAddr) -> Vec<u8> {
let folded = SocketAddr::new(fold_ip(reflexive.ip()), reflexive.port());
let cookie_be = MAGIC_COOKIE.to_be_bytes();
let xor_port = folded.port() ^ ((MAGIC_COOKIE >> 16) as u16);
let mut value = Vec::new();
value.push(0); match folded.ip() {
IpAddr::V4(v4) => {
value.push(FAMILY_IPV4);
value.extend_from_slice(&xor_port.to_be_bytes());
let mut octets = v4.octets();
for (i, o) in octets.iter_mut().enumerate() {
*o ^= cookie_be[i];
}
value.extend_from_slice(&octets);
}
IpAddr::V6(v6) => {
value.push(FAMILY_IPV6);
value.extend_from_slice(&xor_port.to_be_bytes());
let mut octets = v6.octets();
let mut key = [0u8; 16];
key[..4].copy_from_slice(&cookie_be);
key[4..].copy_from_slice(transaction_id);
for (o, k) in octets.iter_mut().zip(key.iter()) {
*o ^= *k;
}
value.extend_from_slice(&octets);
}
}
debug_assert_eq!(
value.len() % 4,
0,
"XOR-MAPPED-ADDRESS value must be 4-byte aligned"
);
let mut attr = Vec::with_capacity(4 + value.len());
attr.extend_from_slice(&ATTR_XOR_MAPPED_ADDRESS.to_be_bytes());
attr.extend_from_slice(&(value.len() as u16).to_be_bytes());
attr.extend_from_slice(&value);
let mut msg = Vec::with_capacity(20 + attr.len());
msg.extend_from_slice(&BINDING_SUCCESS.to_be_bytes());
msg.extend_from_slice(&(attr.len() as u16).to_be_bytes());
msg.extend_from_slice(&cookie_be);
msg.extend_from_slice(transaction_id);
msg.extend_from_slice(&attr);
msg
}
fn decode_mapped_address(
value: &[u8],
txid: &TransactionId,
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)),
}
}