pub const TRANSACTION_ID_LEN: usize = 12;
pub const REQUEST_LEN: usize = 40;
const HEADER_LEN: usize = 20;
const COOKIE: u32 = 0x2112_a442;
const BINDING_REQUEST: u16 = 0x0001;
const BINDING_SUCCESS: u16 = 0x0101;
const SOFTWARE: u16 = 0x8022;
const XOR_MAPPED_ADDRESS: u16 = 0x0020;
const FINGERPRINT: u16 = 0x8028;
const FINGERPRINT_XOR: u32 = 0x5354_554e;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
Length,
Type,
Cookie,
Transaction,
Attribute,
Fingerprint,
MissingAddress,
AddressFamily,
}
pub fn request(transaction: [u8; TRANSACTION_ID_LEN]) -> [u8; REQUEST_LEN] {
let mut packet = [0; REQUEST_LEN];
packet[..2].copy_from_slice(&BINDING_REQUEST.to_be_bytes());
packet[2..4].copy_from_slice(&20u16.to_be_bytes());
packet[4..8].copy_from_slice(&COOKIE.to_be_bytes());
packet[8..20].copy_from_slice(&transaction);
packet[20..22].copy_from_slice(&SOFTWARE.to_be_bytes());
packet[22..24].copy_from_slice(&8u16.to_be_bytes());
packet[24..32].copy_from_slice(b"tailnode");
packet[32..34].copy_from_slice(&FINGERPRINT.to_be_bytes());
packet[34..36].copy_from_slice(&4u16.to_be_bytes());
let fingerprint = crc32(&packet[..32]) ^ FINGERPRINT_XOR;
packet[36..40].copy_from_slice(&fingerprint.to_be_bytes());
packet
}
pub fn response(
packet: &[u8],
transaction: [u8; TRANSACTION_ID_LEN],
) -> Result<([u8; 4], u16), Error> {
if packet.len() < HEADER_LEN {
return Err(Error::Length);
}
if u16::from_be_bytes([packet[0], packet[1]]) != BINDING_SUCCESS {
return Err(Error::Type);
}
let declared = usize::from(u16::from_be_bytes([packet[2], packet[3]]));
if declared % 4 != 0 || HEADER_LEN + declared != packet.len() {
return Err(Error::Length);
}
if u32::from_be_bytes(packet[4..8].try_into().unwrap()) != COOKIE {
return Err(Error::Cookie);
}
if packet[8..20] != transaction {
return Err(Error::Transaction);
}
let cookie = COOKIE.to_be_bytes();
let mut mapped = None;
let mut offset = HEADER_LEN;
while offset < packet.len() {
if packet.len() - offset < 4 {
return Err(Error::Attribute);
}
let kind = u16::from_be_bytes([packet[offset], packet[offset + 1]]);
let len = usize::from(u16::from_be_bytes([packet[offset + 2], packet[offset + 3]]));
let value_start = offset + 4;
let value_end = value_start.checked_add(len).ok_or(Error::Attribute)?;
let padded_end = value_start
.checked_add(len.div_ceil(4) * 4)
.ok_or(Error::Attribute)?;
if value_end > packet.len() || padded_end > packet.len() {
return Err(Error::Attribute);
}
match kind {
XOR_MAPPED_ADDRESS => {
if len != 8 {
return Err(Error::Attribute);
}
if packet[value_start + 1] != 1 {
return Err(Error::AddressFamily);
}
let port = u16::from_be_bytes([
packet[value_start + 2] ^ cookie[0],
packet[value_start + 3] ^ cookie[1],
]);
let address = [
packet[value_start + 4] ^ cookie[0],
packet[value_start + 5] ^ cookie[1],
packet[value_start + 6] ^ cookie[2],
packet[value_start + 7] ^ cookie[3],
];
mapped = Some((address, port));
}
FINGERPRINT => {
if len != 4 || padded_end != packet.len() {
return Err(Error::Fingerprint);
}
let actual = u32::from_be_bytes(packet[value_start..value_end].try_into().unwrap());
let expected = crc32(&packet[..offset]) ^ FINGERPRINT_XOR;
if actual != expected {
return Err(Error::Fingerprint);
}
}
_ => {}
}
offset = padded_end;
}
mapped.ok_or(Error::MissingAddress)
}
fn crc32(bytes: &[u8]) -> u32 {
let mut crc = u32::MAX;
for byte in bytes {
crc ^= u32::from(*byte);
for _ in 0..8 {
let mask = 0u32.wrapping_sub(crc & 1);
crc = (crc >> 1) ^ (0xedb8_8320 & mask);
}
}
!crc
}
#[cfg(test)]
mod tests {
use super::*;
fn success(transaction: [u8; 12], address: [u8; 4], port: u16) -> [u8; 40] {
let cookie = COOKIE.to_be_bytes();
let mut packet = [0; 40];
packet[..2].copy_from_slice(&BINDING_SUCCESS.to_be_bytes());
packet[2..4].copy_from_slice(&20u16.to_be_bytes());
packet[4..8].copy_from_slice(&cookie);
packet[8..20].copy_from_slice(&transaction);
packet[20..22].copy_from_slice(&XOR_MAPPED_ADDRESS.to_be_bytes());
packet[22..24].copy_from_slice(&8u16.to_be_bytes());
packet[25] = 1;
let encoded_port = port ^ (COOKIE >> 16) as u16;
packet[26..28].copy_from_slice(&encoded_port.to_be_bytes());
for index in 0..4 {
packet[28 + index] = address[index] ^ cookie[index];
}
packet[32..34].copy_from_slice(&FINGERPRINT.to_be_bytes());
packet[34..36].copy_from_slice(&4u16.to_be_bytes());
let fingerprint = crc32(&packet[..32]) ^ FINGERPRINT_XOR;
packet[36..40].copy_from_slice(&fingerprint.to_be_bytes());
packet
}
#[test]
fn request_has_tailnode_marker_and_a_valid_fingerprint() {
let packet = request([3; 12]);
assert_eq!(&packet[24..32], b"tailnode");
assert_eq!(
u32::from_be_bytes(packet[36..40].try_into().unwrap()),
crc32(&packet[..32]) ^ FINGERPRINT_XOR
);
}
#[test]
fn xor_mapped_ipv4_round_trips() {
let transaction = [9; 12];
assert_eq!(
response(&success(transaction, [203, 0, 113, 7], 41_641), transaction),
Ok(([203, 0, 113, 7], 41_641))
);
}
#[test]
fn response_without_optional_fingerprint_is_accepted() {
let transaction = [9; 12];
let mut packet = success(transaction, [203, 0, 113, 7], 41_641);
packet[2..4].copy_from_slice(&12u16.to_be_bytes());
assert_eq!(
response(&packet[..32], transaction),
Ok(([203, 0, 113, 7], 41_641))
);
}
#[test]
fn wrong_transaction_and_fingerprint_are_rejected() {
let transaction = [9; 12];
let mut packet = success(transaction, [203, 0, 113, 7], 41_641);
assert_eq!(response(&packet, [8; 12]), Err(Error::Transaction));
packet[39] ^= 1;
assert_eq!(response(&packet, transaction), Err(Error::Fingerprint));
}
}