use core::net::Ipv4Addr;
use crate::checksum::ipv4_header_checksum;
use crate::error::{CrafterError, Result};
use crate::field::Field;
use crate::packet::{Packet, Raw};
use crate::protocols::ip::shared::{IPPROTO_ICMP, IPPROTO_TCP, IPPROTO_UDP};
use crate::registry::ProtocolRegistry;
use super::constants::{IPV4_FLAG_MORE_FRAGMENTS, IPV4_MIN_HEADER_LEN};
use super::fragment::{flags_from_flags_fragment, fragment_offset_from_flags_fragment};
use super::header::{Ipv4, Ipv4ChecksumStatus};
use super::options::validate_ipv4_options;
const QUOTED_ICMP_HEADER_LEN: usize = 8;
const QUOTED_TCP_MIN_HEADER_LEN: usize = 20;
const QUOTED_UDP_HEADER_LEN: usize = 8;
pub(crate) fn append_ipv4_packet_with_registry(
registry: &ProtocolRegistry,
packet: Packet,
bytes: &[u8],
) -> Result<Packet> {
let decoded = decode_ipv4_parts(bytes, registry.validates_checksums())?;
append_ipv4_payload_with_registry(
registry,
packet.push_ipv4(decoded.ipv4),
decoded.protocol,
decoded.fragment_offset,
decoded.has_more_fragments,
decoded.payload,
decoded.rest,
)
}
struct DecodedIpv4Packet<'a> {
ipv4: Ipv4,
protocol: u8,
fragment_offset: u16,
has_more_fragments: bool,
payload: &'a [u8],
rest: &'a [u8],
}
fn decode_ipv4_parts(bytes: &[u8], validate_checksum: bool) -> Result<DecodedIpv4Packet<'_>> {
if bytes.len() < IPV4_MIN_HEADER_LEN {
return Err(CrafterError::buffer_too_short(
"ipv4 header",
IPV4_MIN_HEADER_LEN,
bytes.len(),
));
}
let version = bytes[0] >> 4;
let ihl = bytes[0] & 0x0f;
if version != 4 {
return Err(CrafterError::invalid_field_value(
"ipv4.version",
"IPv4 packets must have version 4",
));
}
if ihl < 5 {
return Err(CrafterError::invalid_field_value(
"ipv4.ihl",
"internet header length must be at least 5 words",
));
}
let header_len = (ihl as usize) * 4;
if bytes.len() < header_len {
return Err(CrafterError::buffer_too_short(
"ipv4 header",
header_len,
bytes.len(),
));
}
let total_length = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
if total_length < header_len {
return Err(CrafterError::invalid_field_value(
"ipv4.total_length",
"total length must be at least the IPv4 header length",
));
}
if bytes.len() < total_length {
return Err(CrafterError::buffer_too_short(
"ipv4 packet",
total_length,
bytes.len(),
));
}
let flags_fragment = u16::from_be_bytes([bytes[6], bytes[7]]);
let flags = flags_from_flags_fragment(flags_fragment);
let fragment_offset = fragment_offset_from_flags_fragment(flags_fragment);
let options = if header_len > IPV4_MIN_HEADER_LEN {
bytes[IPV4_MIN_HEADER_LEN..header_len].to_vec()
} else {
Vec::new()
};
if !options.is_empty() {
validate_ipv4_options(&options)?;
}
let checksum_status = if validate_checksum {
decoded_ipv4_checksum_status(&bytes[..header_len])
} else {
Ipv4ChecksumStatus::NotChecked
};
let protocol = bytes[9];
let ipv4 = Ipv4 {
version: Field::user(version),
ihl: Field::user(ihl),
tos: Field::user(bytes[1]),
total_length: Field::user(total_length as u16),
identification: Field::user(u16::from_be_bytes([bytes[4], bytes[5]])),
flags: Field::user(flags),
fragment_offset: Field::user(fragment_offset),
ttl: Field::user(bytes[8]),
protocol: Field::user(protocol),
checksum: Field::user(u16::from_be_bytes([bytes[10], bytes[11]])),
checksum_status,
source: Field::user(Ipv4Addr::new(bytes[12], bytes[13], bytes[14], bytes[15])),
destination: Field::user(Ipv4Addr::new(bytes[16], bytes[17], bytes[18], bytes[19])),
options,
};
Ok(DecodedIpv4Packet {
ipv4,
protocol,
fragment_offset,
has_more_fragments: flags & IPV4_FLAG_MORE_FRAGMENTS != 0,
payload: &bytes[header_len..total_length],
rest: &bytes[total_length..],
})
}
fn append_ipv4_payload_with_registry(
registry: &ProtocolRegistry,
mut packet: Packet,
protocol: u8,
fragment_offset: u16,
has_more_fragments: bool,
payload: &[u8],
rest: &[u8],
) -> Result<Packet> {
if fragment_offset != 0 {
if !payload.is_empty() {
packet = packet.push_raw(Raw::from_bytes(payload));
}
} else if has_more_fragments {
packet = match registry.decode_ipv4_protocol(packet.clone(), protocol, payload) {
Ok(decoded) => decoded,
Err(_) => {
if payload.is_empty() {
packet
} else {
packet.push_raw(Raw::from_bytes(payload))
}
}
};
} else {
packet = registry.decode_ipv4_protocol(packet, protocol, payload)?;
}
if !rest.is_empty() {
packet = packet.push_raw(Raw::from_bytes(rest));
}
Ok(packet)
}
pub(crate) fn decode_quoted_ipv4(bytes: &[u8], validate_checksum: bool) -> Option<(Packet, usize)> {
if bytes.len() < IPV4_MIN_HEADER_LEN {
return None;
}
let version = bytes[0] >> 4;
let ihl = bytes[0] & 0x0f;
if version != 4 || ihl < 5 {
return None;
}
let header_len = (ihl as usize) * 4;
if bytes.len() < header_len {
return None;
}
let total_length = u16::from_be_bytes([bytes[2], bytes[3]]) as usize;
let consumed = if total_length >= header_len && total_length <= bytes.len() {
total_length
} else {
bytes.len()
};
let datagram = &bytes[..consumed];
let flags_fragment = u16::from_be_bytes([datagram[6], datagram[7]]);
let flags = flags_from_flags_fragment(flags_fragment);
let fragment_offset = fragment_offset_from_flags_fragment(flags_fragment);
let protocol = datagram[9];
let options = if header_len > IPV4_MIN_HEADER_LEN {
datagram[IPV4_MIN_HEADER_LEN..header_len].to_vec()
} else {
Vec::new()
};
if !options.is_empty() && validate_ipv4_options(&options).is_err() {
return None;
}
let checksum_status = if validate_checksum {
decoded_ipv4_checksum_status(&datagram[..header_len])
} else {
Ipv4ChecksumStatus::NotChecked
};
let ipv4 = Ipv4 {
version: Field::user(version),
ihl: Field::user(ihl),
tos: Field::user(datagram[1]),
total_length: Field::user(total_length as u16),
identification: Field::user(u16::from_be_bytes([datagram[4], datagram[5]])),
flags: Field::user(flags),
fragment_offset: Field::user(fragment_offset),
ttl: Field::user(datagram[8]),
protocol: Field::user(protocol),
checksum: Field::user(u16::from_be_bytes([datagram[10], datagram[11]])),
checksum_status,
source: Field::user(Ipv4Addr::new(
datagram[12],
datagram[13],
datagram[14],
datagram[15],
)),
destination: Field::user(Ipv4Addr::new(
datagram[16],
datagram[17],
datagram[18],
datagram[19],
)),
options,
};
let payload = &datagram[header_len..];
let can_decode_transport = fragment_offset == 0
&& !payload.is_empty()
&& quoted_transport_decode_can_succeed(protocol, payload);
let packet_capacity = match (payload.is_empty(), can_decode_transport) {
(true, _) => 1,
(_, true) => 3,
_ => 2,
};
let mut packet = Packet::with_capacity(packet_capacity);
packet.push_ipv4_mut(ipv4);
if fragment_offset != 0 {
if !payload.is_empty() {
packet.push_raw_mut(Raw::from_bytes(payload));
}
} else if payload.is_empty() {
} else if can_decode_transport {
let registry = ProtocolRegistry::transport_only_builtin();
packet = match registry.decode_ipv4_protocol(packet.clone(), protocol, payload) {
Ok(typed) => typed,
Err(_) => {
if payload.is_empty() {
packet
} else {
packet.push_raw(Raw::from_bytes(payload))
}
}
};
} else {
packet.push_raw_mut(Raw::from_bytes(payload));
}
Some((packet, consumed))
}
fn quoted_transport_decode_can_succeed(protocol: u8, payload: &[u8]) -> bool {
match protocol {
IPPROTO_TCP => {
if payload.len() < QUOTED_TCP_MIN_HEADER_LEN {
return false;
}
let data_offset = payload[12] >> 4;
data_offset >= 5 && payload.len() >= (data_offset as usize) * 4
}
IPPROTO_UDP => {
if payload.len() < QUOTED_UDP_HEADER_LEN {
return false;
}
let length = u16::from_be_bytes([payload[4], payload[5]]) as usize;
length >= QUOTED_UDP_HEADER_LEN && payload.len() >= length
}
IPPROTO_ICMP => payload.len() >= QUOTED_ICMP_HEADER_LEN,
_ => true,
}
}
fn decoded_ipv4_checksum_status(header: &[u8]) -> Ipv4ChecksumStatus {
if ipv4_header_checksum(header) == 0 {
Ipv4ChecksumStatus::Valid
} else {
Ipv4ChecksumStatus::Invalid
}
}
#[cfg(test)]
mod tests {
use super::decode_quoted_ipv4;
use crate::{Raw, Udp};
fn quoted_udp_prefix(total_len: u16, udp_len: u16, udp_payload: &[u8]) -> Vec<u8> {
let mut bytes = vec![
0x45,
0,
(total_len >> 8) as u8,
total_len as u8,
0x12,
0x34,
0,
0,
64,
17,
0,
0,
192,
0,
2,
1,
198,
51,
100,
1,
0x9c,
0x40,
0,
53,
(udp_len >> 8) as u8,
udp_len as u8,
0,
0,
];
bytes.extend_from_slice(udp_payload);
bytes
}
#[test]
fn quoted_ipv4_keeps_truncated_udp_header_raw() {
let quoted = quoted_udp_prefix(34, 14, &[]);
let (packet, consumed) = decode_quoted_ipv4("ed, true).unwrap();
assert_eq!(consumed, quoted.len());
assert!(packet.layer::<Udp>().is_none());
assert_eq!(packet.layer::<Raw>().unwrap().as_bytes(), "ed[20..]);
}
#[test]
fn quoted_ipv4_still_types_complete_udp_datagram() {
let quoted = quoted_udp_prefix(34, 14, b"quoted");
let (packet, consumed) = decode_quoted_ipv4("ed, true).unwrap();
assert_eq!(consumed, quoted.len());
assert!(packet.layer::<Udp>().is_some());
assert_eq!(packet.layer::<Raw>().unwrap().as_bytes(), b"quoted");
}
}