Skip to content

Commit

Permalink
Added support for receiving fragmented messages.
Browse files Browse the repository at this point in the history
Implemented fragment reassembly as specified in RFC 7383.
Tested with macOS.
This should help with:
* IPv4 UDP fragmentation
* Reducing the UDP receive buffer size to the MTU value
* Iterating (and logging) encrypted message payloads

Fragmentation of transmitted messages is not yet implemented.
  • Loading branch information
zlogic committed Sep 15, 2024
1 parent 210b7e2 commit 250c4fc
Show file tree
Hide file tree
Showing 3 changed files with 358 additions and 223 deletions.
144 changes: 105 additions & 39 deletions src/ikev2/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,25 @@ impl fmt::Debug for Spi {

pub struct InputMessage<'a> {
data: &'a [u8],
is_nat: bool,
}

// Parse and validate using spec from RFC 7296, Section 3.
impl InputMessage<'_> {
pub fn from_datagram(p: &[u8]) -> Result<InputMessage, FormatError> {
pub fn from_datagram(p: &[u8], is_nat: bool) -> Result<InputMessage, FormatError> {
let data = if is_nat { &p[4..] } else { p };
if p.len() < 28 {
warn!("Not enough data in message");
Err("Not enough data in message".into())
} else {
Ok(InputMessage { data: p })
Ok(InputMessage { data, is_nat })
}
}

pub fn is_nat(&self) -> bool {
self.is_nat
}

pub fn read_initiator_spi(&self) -> u64 {
let mut result = [0u8; 8];
result.copy_from_slice(&self.data[0..8]);
Expand All @@ -168,8 +174,8 @@ impl InputMessage<'_> {
u64::from_be_bytes(result)
}

fn read_next_payload(&self) -> u8 {
self.data[16]
fn read_next_payload(&self) -> PayloadType {
PayloadType::from_u8(self.data[16])
}

fn read_version(&self) -> (u8, u8) {
Expand Down Expand Up @@ -199,6 +205,14 @@ impl InputMessage<'_> {
u32::from_be_bytes(result)
}

pub fn header(&self) -> [u8; 28] {
let mut header = [0u8; 28];
header[16] = PayloadType::NONE.0;
// Don't copy length to keep it empty.
header[..24].copy_from_slice(&self.data[..24]);
header
}

pub fn is_valid(&self) -> bool {
// TODO: validate all required fields.
// TODO: return status in notification (e.g. INVALID_MAJOR_VERSION).
Expand Down Expand Up @@ -338,6 +352,11 @@ impl MessageWriter<'_> {
Ok(())
}

pub fn update_header(header: &mut [u8; 28], next_payload: PayloadType, length: u32) {
header[16] = next_payload.0;
header[24..28].copy_from_slice(&length.to_be_bytes());
}

fn sa_parameters_len(params: &crypto::TransformParameters) -> (usize, usize) {
let (num_transforms, data_len) = params
.iter_parameters()
Expand Down Expand Up @@ -721,7 +740,7 @@ impl MessageWriter<'_> {

pub struct Payload<'a> {
payload_type: PayloadType,
encrypted_next_payload: Option<u8>,
encrypted_next_payload: Option<PayloadType>,
critical: bool,
data: &'a [u8],
start_offset: usize,
Expand Down Expand Up @@ -835,7 +854,17 @@ impl Payload<'_> {
} else {
return Err("Unspecified next encrypted payload".into());
};
Ok(EncryptedMessage::from_payload(encrypted_next_payload, self))
Ok(EncryptedMessage::from_encrypted_payload(
encrypted_next_payload,
self,
))
} else if self.payload_type == PayloadType::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT {
let encrypted_next_payload = if let Some(next_payload) = self.encrypted_next_payload {
next_payload
} else {
return Err("Unspecified next encrypted payload".into());
};
EncryptedMessage::from_encrypted_fragment_payload(encrypted_next_payload, self)
} else {
Err("Payload type is not ENCRYPTED_AND_AUTHENTICATED".into())
}
Expand All @@ -844,7 +873,7 @@ impl Payload<'_> {

pub struct PayloadIter<'a> {
start_offset: usize,
next_payload: u8,
next_payload: PayloadType,
payload_encrypted: bool,
data: &'a [u8],
}
Expand All @@ -854,7 +883,7 @@ impl<'a> Iterator for PayloadIter<'a> {

fn next(&mut self) -> Option<Self::Item> {
const CRITICAL_BIT: u8 = 1 << 7;
if self.next_payload == 0 || self.payload_encrypted {
if self.next_payload == PayloadType::NONE || self.payload_encrypted {
if !self.data.is_empty() {
debug!("Packet has unaccounted data");
}
Expand All @@ -867,7 +896,7 @@ impl<'a> Iterator for PayloadIter<'a> {
let current_payload = self.next_payload;
let start_offset = self.start_offset;
let data = self.data;
let next_payload = self.data[0];
let next_payload = PayloadType::from_u8(self.data[0]);
self.next_payload = next_payload;
let payload_flags = self.data[1];
let mut payload_length = [0u8; 2];
Expand All @@ -890,20 +919,19 @@ impl<'a> Iterator for PayloadIter<'a> {
return Some(Err("Unsupported payload reserved flags".into()));
}
};
let payload_type = match PayloadType::from_u8(current_payload) {
Ok(payload_type) => payload_type,
Err(err) => {
return Some(Err(err));
}
};
self.payload_encrypted = current_payload == PayloadType::ENCRYPTED_AND_AUTHENTICATED.0;
let encrypted_next_payload = if payload_type == PayloadType::ENCRYPTED_AND_AUTHENTICATED {
Some(next_payload)
if !next_payload.is_supported() {
warn!("Unsupported IKEv2 Payload Type {}", self.next_payload);
return Some(Err("Unsupported IKEv2 Payload Type".into()));
}
self.payload_encrypted = current_payload == PayloadType::ENCRYPTED_AND_AUTHENTICATED
|| current_payload == PayloadType::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT;
let encrypted_next_payload = if self.payload_encrypted {
Some(self.next_payload)
} else {
None
};
let item = Payload {
payload_type,
payload_type: current_payload,
encrypted_next_payload,
critical,
data: &data[4..payload_length],
Expand Down Expand Up @@ -935,15 +963,16 @@ impl PayloadType {
pub const CONFIGURATION: PayloadType = PayloadType(47);
pub const EXTENSIBLE_AUTHENTICATION: PayloadType = PayloadType(48);

fn from_u8(value: u8) -> Result<PayloadType, FormatError> {
if (Self::SECURITY_ASSOCIATION.0..=Self::EXTENSIBLE_AUTHENTICATION.0).contains(&value)
|| value == Self::NONE.0
{
Ok(PayloadType(value))
} else {
warn!("Unsupported IKEv2 Payload Type {}", value);
Err("Unsupported IKEv2 Payload Type".into())
}
pub const ENCRYPTED_AND_AUTHENTICATED_FRAGMENT: PayloadType = PayloadType(53);

fn from_u8(value: u8) -> PayloadType {
PayloadType(value)
}

fn is_supported(&self) -> bool {
(Self::SECURITY_ASSOCIATION.0..=Self::EXTENSIBLE_AUTHENTICATION.0).contains(&self.0)
|| self.0 == Self::NONE.0
|| self.0 == Self::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT.0
}
}

Expand All @@ -967,6 +996,9 @@ impl fmt::Display for PayloadType {
Self::ENCRYPTED_AND_AUTHENTICATED => write!(f, "Encrypted and Authenticated")?,
Self::CONFIGURATION => write!(f, "Configuration")?,
Self::EXTENSIBLE_AUTHENTICATION => write!(f, "Extensible Authentication")?,
Self::ENCRYPTED_AND_AUTHENTICATED_FRAGMENT => {
write!(f, "Encrypted and Authenticated Fragment")?
}
_ => write!(f, "Unknown exchange type {}", self.0)?,
}
Ok(())
Expand Down Expand Up @@ -2324,31 +2356,65 @@ impl<'a> Iterator for ConfigurationAttributesIter<'a> {
}

pub struct EncryptedMessage<'a> {
next_payload: u8,
next_payload: PayloadType,
data: &'a [u8],
start_offset: usize,
fragment_number: u16,
total_fragments: u16,
}

impl<'a> EncryptedMessage<'a> {
fn from_payload(next_payload: u8, payload: &'a Payload) -> EncryptedMessage<'a> {
fn from_encrypted_payload(
next_payload: PayloadType,
payload: &'a Payload,
) -> EncryptedMessage<'a> {
EncryptedMessage {
next_payload,
data: payload.data,
start_offset: payload.start_offset,
fragment_number: 1,
total_fragments: 1,
}
}

pub fn encrypted_data(&self) -> &[u8] {
self.data
fn from_encrypted_fragment_payload(
next_payload: PayloadType,
payload: &'a Payload,
) -> Result<EncryptedMessage<'a>, FormatError> {
let data = payload.data;
if payload.data.len() < 4 {
Err("Not enough data in Encrypted Fragment payload".into())
} else {
let mut fragment_number = [0u8; 2];
fragment_number.copy_from_slice(&data[0..2]);
let fragment_number = u16::from_be_bytes(fragment_number);
let mut total_fragments = [0u8; 2];
total_fragments.copy_from_slice(&data[2..4]);
let total_fragments = u16::from_be_bytes(total_fragments);
Ok(EncryptedMessage {
next_payload,
data: &data[4..],
start_offset: payload.start_offset + 4,
fragment_number,
total_fragments,
})
}
}

pub fn iter_decrypted_message<'b>(&self, decrypted_data: &'b [u8]) -> PayloadIter<'b> {
PayloadIter {
next_payload: self.next_payload,
payload_encrypted: false,
data: decrypted_data,
start_offset: 0,
}
pub fn next_payload(&self) -> PayloadType {
self.next_payload
}

pub fn fragment_number(&self) -> u16 {
self.fragment_number
}

pub fn total_fragments(&self) -> u16 {
self.total_fragments
}

pub fn encrypted_data(&self) -> &[u8] {
self.data
}
}

Expand Down
32 changes: 7 additions & 25 deletions src/ikev2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ const IKEV2_PORT: u16 = 500;
const IKEV2_NAT_PORT: u16 = 4500;
const IKEV2_LISTEN_PORTS: [u16; 2] = [IKEV2_PORT, IKEV2_NAT_PORT];

// TODO: for Windows, add IKEV2_FRAGMENTATION_SUPPORTED support. Otherwise, UDP fragmentation will be used to transmit larger packets.
const MAX_DATAGRAM_SIZE: usize = 4096;
const MAX_DATAGRAM_SIZE: usize = 1500;
// Use 1500 as max MTU, real value is likely lower.
const MAX_ESP_PACKET_SIZE: usize = 1500;

Expand Down Expand Up @@ -243,19 +242,6 @@ impl UdpDatagram {
fn is_ikev2(&self) -> bool {
self.local_addr.port() == IKEV2_PORT || self.is_non_esp()
}

fn ikev2_data(&self) -> &[u8] {
if self.local_addr.port() == IKEV2_PORT {
// Regular IKEv2 message sent to port 500.
self.request.as_slice()
} else if self.is_non_esp() {
// Shared IKEv2/ESP-in-UDP port, marked as an IKEv2 message.
&self.request[4..]
} else {
// Shared IKEv2/ESP-in-UDP port, an ESP-in-UDP message.
&[]
}
}
}

enum SessionMessage {
Expand Down Expand Up @@ -513,12 +499,9 @@ impl Sessions {
}
}

async fn process_ikev2_message(
&mut self,
datagram: &mut UdpDatagram,
) -> Result<(), IKEv2Error> {
let request_bytes = datagram.ikev2_data();
let ikev2_request = message::InputMessage::from_datagram(request_bytes)?;
async fn process_ikev2_message(&mut self, datagram: &UdpDatagram) -> Result<(), IKEv2Error> {
let is_nat = datagram.is_non_esp();
let ikev2_request = message::InputMessage::from_datagram(&datagram.request, is_nat)?;
if !ikev2_request.is_valid() {
return Err("Invalid message received".into());
}
Expand Down Expand Up @@ -572,7 +555,7 @@ impl Sessions {
}

let mut response_bytes = [0u8; MAX_DATAGRAM_SIZE];
let start_offset = if datagram.is_non_esp() { 4 } else { 0 };
let start_offset = if ikev2_request.is_nat() { 4 } else { 0 };

if ikev2_request.read_flags()?.has(message::Flags::RESPONSE) {
session.process_response(datagram.remote_addr, datagram.local_addr, &ikev2_request)?;
Expand All @@ -589,10 +572,9 @@ impl Sessions {
)?;
self.reserved_spi = Some(reserved_spi);

let response_bytes = &response_bytes[..response_len + start_offset];

// Response retransmissions are initiated by client.
if !response_bytes.is_empty() {
if response_len > 0 {
let response_bytes = &response_bytes[..response_len + start_offset];
self.sockets
.send_datagram(&datagram.local_addr, &datagram.remote_addr, response_bytes)
.await?;
Expand Down
Loading

0 comments on commit 250c4fc

Please sign in to comment.