Skip to content

Commit b8a96eb

Browse files
committed
types: add parser for the fixed STUN message header
Can be used if the first 20 bytes of a message are received, to indicate whether or not the data is a likely STUN message or not.
1 parent 4410c5b commit b8a96eb

File tree

2 files changed

+126
-41
lines changed

2 files changed

+126
-41
lines changed

stun-types/examples/stunclient.rs

+40-9
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,46 @@ fn tcp_message(out: MessageBuilder<'_>, to: SocketAddr) -> Result<(), std::io::E
7373
trace!("generated to {:?}", buf);
7474
socket.write_all(&buf)?;
7575
let mut buf = [0; 1500];
76-
let amt = socket.read(&mut buf)?;
77-
let buf = &buf[..amt];
78-
trace!("got {:?}", buf);
79-
let msg = Message::from_bytes(buf).map_err(|e| {
80-
std::io::Error::new(
81-
std::io::ErrorKind::InvalidData,
82-
format!("Invalid message: {e:?}"),
83-
)
84-
})?;
76+
let mut offset = 0;
77+
let msg;
78+
loop {
79+
let amt = socket.read(&mut buf[offset..])?;
80+
let data = &buf[..offset + amt];
81+
if amt == 0 {
82+
trace!("got {:?}", data);
83+
msg = Message::from_bytes(data).map_err(|e| {
84+
std::io::Error::new(
85+
std::io::ErrorKind::InvalidData,
86+
format!("Invalid message: {e:?}"),
87+
)
88+
})?;
89+
break;
90+
}
91+
match MessageHeader::from_bytes(data) {
92+
Ok(header) => {
93+
if header.data_length() as usize + MessageHeader::LENGTH > 1500 {
94+
return Err(std::io::Error::new(
95+
std::io::ErrorKind::InvalidData,
96+
"Response data is too large to receive",
97+
));
98+
}
99+
if header.data_length() as usize + MessageHeader::LENGTH < offset + amt {
100+
return Err(std::io::Error::new(
101+
std::io::ErrorKind::InvalidData,
102+
"Response data is too large for message",
103+
));
104+
}
105+
}
106+
Err(StunParseError::NotStun) => {
107+
return Err(std::io::Error::new(
108+
std::io::ErrorKind::InvalidData,
109+
"Did not receive a STUN response",
110+
))
111+
}
112+
Err(e) => trace!("parsing STUN message header produced: \'{e}\'"),
113+
}
114+
offset += amt;
115+
}
85116
info!(
86117
"received from {:?} to {:?} {}",
87118
socket.peer_addr().unwrap(),

stun-types/src/message.rs

+86-32
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,74 @@ impl std::fmt::Display for TransactionId {
508508
}
509509
}
510510

511+
/// The fixed length header of a STUN message. Allows reading the message header for a quick
512+
/// check if this message is a valid STUN message. Can also be used to expose the length of the
513+
/// complete message without needing to receive the entire message.
514+
pub struct MessageHeader {
515+
mtype: MessageType,
516+
transaction_id: TransactionId,
517+
length: u16,
518+
}
519+
520+
impl MessageHeader {
521+
/// The length of the STUN message header.
522+
pub const LENGTH: usize = 20;
523+
524+
/// Deserialize a `MessageHeader`
525+
///
526+
/// # Examples
527+
///
528+
/// ```
529+
/// # use stun_types::message::{MessageHeader, MessageType, MessageClass, BINDING};
530+
/// let msg_data = [0, 1, 0, 8, 33, 18, 164, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 232];
531+
/// let message = MessageHeader::from_bytes(&msg_data).unwrap();
532+
/// assert_eq!(message.get_type(), MessageType::from_class_method(MessageClass::Request, BINDING));
533+
/// assert_eq!(message.transaction_id(), 1000.into());
534+
/// assert_eq!(message.data_length(), 8);
535+
/// ```
536+
pub fn from_bytes(data: &[u8]) -> Result<Self, StunParseError> {
537+
if data.len() < 20 {
538+
return Err(StunParseError::Truncated {
539+
expected: 20,
540+
actual: data.len(),
541+
});
542+
}
543+
let mtype = MessageType::from_bytes(data)?;
544+
let mlength = BigEndian::read_u16(&data[2..]);
545+
let tid = BigEndian::read_u128(&data[4..]);
546+
let cookie = (tid >> 96) as u32;
547+
if cookie != MAGIC_COOKIE {
548+
warn!(
549+
"malformed cookie constant {:?} != stored data {:?}",
550+
MAGIC_COOKIE, cookie
551+
);
552+
return Err(StunParseError::NotStun);
553+
}
554+
555+
Ok(Self {
556+
mtype,
557+
transaction_id: tid.into(),
558+
length: mlength,
559+
})
560+
}
561+
562+
/// The number of bytes of content in this [`MessageHeader`]. Adding both `data_length()`
563+
/// and [`MessageHader::LENGTH`] will result in the size of the complete STUN message.
564+
pub fn data_length(&self) -> u16 {
565+
self.length
566+
}
567+
568+
/// The [`TransactionId`] of this [`MessageHeader`]
569+
pub fn transaction_id(&self) -> TransactionId {
570+
self.transaction_id
571+
}
572+
573+
/// The [`MessageType`] of this [`MessageHeader`]
574+
pub fn get_type(&self) -> MessageType {
575+
self.mtype
576+
}
577+
}
578+
511579
/// The structure that encapsulates the entirety of a STUN message
512580
///
513581
/// Contains the [`MessageType`], a transaction ID, and a list of STUN
@@ -788,40 +856,23 @@ impl<'a> Message<'a> {
788856
pub fn from_bytes(data: &'a [u8]) -> Result<Self, StunParseError> {
789857
let orig_data = data;
790858

791-
if data.len() < 20 {
792-
// always at least 20 bytes long
793-
debug!("messsage data is too short {} < 20", data.len());
794-
return Err(StunParseError::Truncated {
795-
expected: 20,
796-
actual: data.len(),
797-
});
798-
}
799-
let _mtype = MessageType::try_from(data)?;
800-
let mlength = BigEndian::read_u16(&data[2..]) as usize;
801-
if mlength + 20 > data.len() {
859+
let header = MessageHeader::from_bytes(data)?;
860+
let mlength = header.data_length() as usize;
861+
if mlength + MessageHeader::LENGTH > data.len() {
802862
// mlength + header
803863
warn!(
804864
"malformed advertised size {:?} and data size {:?} don't match",
805865
mlength + 20,
806866
data.len()
807867
);
808868
return Err(StunParseError::Truncated {
809-
expected: mlength + 20,
869+
expected: mlength + MessageHeader::LENGTH,
810870
actual: data.len(),
811871
});
812872
}
813-
let tid = BigEndian::read_u128(&data[4..]);
814-
let cookie = (tid >> 96) as u32;
815-
if cookie != MAGIC_COOKIE {
816-
warn!(
817-
"malformed cookie constant {:?} != stored data {:?}",
818-
MAGIC_COOKIE, cookie
819-
);
820-
return Err(StunParseError::NotStun);
821-
}
822873

823-
let mut data_offset = 20;
824-
let mut data = &data[20..];
874+
let mut data_offset = MessageHeader::LENGTH;
875+
let mut data = &data[MessageHeader::LENGTH..];
825876
let mut seen_message_integrity = false;
826877
let mut seen_fingerprint = false;
827878
while !data.is_empty() {
@@ -880,7 +931,7 @@ impl<'a> Message<'a> {
880931
let mut fingerprint_data = orig_data[..data_offset].to_vec();
881932
BigEndian::write_u16(
882933
&mut fingerprint_data[2..4],
883-
(data_offset + padded_len - 20) as u16,
934+
(data_offset + padded_len - MessageHeader::LENGTH) as u16,
884935
);
885936
let calculated_fingerprint = Fingerprint::compute(&fingerprint_data);
886937
if &calculated_fingerprint != msg_fingerprint {
@@ -949,9 +1000,9 @@ impl<'a> Message<'a> {
9491000
// find the location of the original MessageIntegrity attribute: XXX: maybe encode this into
9501001
// the attribute instead?
9511002
let data = self.data;
952-
debug_assert!(data.len() >= 20);
953-
let mut data = &data[20..];
954-
let mut data_offset = 20;
1003+
debug_assert!(data.len() >= MessageHeader::LENGTH);
1004+
let mut data = &data[MessageHeader::LENGTH..];
1005+
let mut data_offset = MessageHeader::LENGTH;
9551006
while !data.is_empty() {
9561007
let attr = RawAttribute::from_bytes(data)?;
9571008
if algo == IntegrityAlgorithm::Sha1 && attr.get_type() == MessageIntegrity::TYPE {
@@ -962,7 +1013,10 @@ impl<'a> Message<'a> {
9621013
// but with a length field including the MESSAGE_INTEGRITY attribute...
9631014
let key = credentials.make_hmac_key();
9641015
let mut hmac_data = self.data[..data_offset].to_vec();
965-
BigEndian::write_u16(&mut hmac_data[2..4], data_offset as u16 + 24 - 20);
1016+
BigEndian::write_u16(
1017+
&mut hmac_data[2..4],
1018+
data_offset as u16 + 24 - MessageHeader::LENGTH as u16,
1019+
);
9661020
return MessageIntegrity::verify(
9671021
&hmac_data,
9681022
&key,
@@ -980,7 +1034,7 @@ impl<'a> Message<'a> {
9801034
let mut hmac_data = self.data[..data_offset].to_vec();
9811035
BigEndian::write_u16(
9821036
&mut hmac_data[2..4],
983-
data_offset as u16 + attr.length() + 4 - 20,
1037+
data_offset as u16 + attr.length() + 4 - MessageHeader::LENGTH as u16,
9841038
);
9851039
return MessageIntegritySha256::verify(&hmac_data, &key, &msg_hmac);
9861040
}
@@ -1067,7 +1121,7 @@ impl<'a> Message<'a> {
10671121
pub fn iter_attributes(&self) -> impl Iterator<Item = RawAttribute> {
10681122
MessageAttributesIter {
10691123
data: self.data,
1070-
data_i: 20,
1124+
data_i: MessageHeader::LENGTH,
10711125
seen_message_integrity: false,
10721126
}
10731127
}
@@ -1344,9 +1398,9 @@ impl<'a> MessageBuilder<'a> {
13441398
for attr in &self.attributes {
13451399
attr_size += padded_attr_size(attr);
13461400
}
1347-
let mut ret = Vec::with_capacity(20 + attr_size);
1401+
let mut ret = Vec::with_capacity(MessageHeader::LENGTH + attr_size);
13481402
ret.extend(self.msg_type.to_bytes());
1349-
ret.resize(20, 0);
1403+
ret.resize(MessageHeader::LENGTH, 0);
13501404
let transaction: u128 = self.transaction_id.into();
13511405
let tid = (MAGIC_COOKIE as u128) << 96 | transaction & 0xffff_ffff_ffff_ffff_ffff_ffff;
13521406
BigEndian::write_u128(&mut ret[4..20], tid);

0 commit comments

Comments
 (0)