Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

icmpv4 time exceeded message #102

Merged
merged 2 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions core/src/packets/icmp/v4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@

mod echo_reply;
mod echo_request;
mod time_exceeded;

pub use self::echo_reply::*;
pub use self::echo_request::*;
pub use self::time_exceeded::*;
pub use capsule_macros::Icmpv4Packet;

use crate::packets::ip::v4::Ipv4;
Expand Down Expand Up @@ -276,6 +278,11 @@ pub mod Icmpv4Types {
///
/// [Echo Reply]: crate::packets::icmp::v4::EchoReply
pub const EchoReply: Icmpv4Type = Icmpv4Type(0);

/// Message type for [Time Exceeded].
///
/// [Time Exceeded]: crate::packets::icmp::v4::TimeExceeded
pub const TimeExceeded: Icmpv4Type = Icmpv4Type(11);
}

impl fmt::Display for Icmpv4Type {
Expand All @@ -286,6 +293,7 @@ impl fmt::Display for Icmpv4Type {
match *self {
Icmpv4Types::EchoRequest => "Echo Request".to_string(),
Icmpv4Types::EchoReply => "Echo Reply".to_string(),
Icmpv4Types::TimeExceeded => "Time Exceeded".to_string(),
_ => format!("{}", self.0),
}
)
Expand Down
231 changes: 231 additions & 0 deletions core/src/packets/icmp/v4/time_exceeded.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/*
* Copyright 2019 Comcast Cable Communications Management, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/

use crate::packets::icmp::v4::{Icmpv4, Icmpv4Message, Icmpv4Packet, Icmpv4Type, Icmpv4Types};
use crate::packets::ip::v4::IPV4_MIN_MTU;
use crate::packets::types::u32be;
use crate::packets::{Internal, Packet};
use crate::SizeOf;
use failure::Fallible;
use std::fmt;
use std::ptr::NonNull;

/// Time Exceeded Message defined in [IETF RFC 792].
///
/// ```
/// 0 1 2 3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Type | Code | Checksum |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Unused |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// | Internet Header + 64 bits of Original Data Datagram |
/// ```
///
/// [IETF RFC 792]: https://tools.ietf.org/html/rfc792
#[derive(Icmpv4Packet)]
pub struct TimeExceeded {
icmp: Icmpv4,
body: NonNull<TimeExceededBody>,
}

impl TimeExceeded {
/// Returns the offset where the data field in the message body starts.
#[inline]
fn data_offset(&self) -> usize {
self.payload_offset() + TimeExceededBody::size_of()
}

/// Returns the length of the data field in the message body.
#[inline]
fn data_len(&self) -> usize {
self.payload_len() - TimeExceededBody::size_of()
}

/// Returns the invoking packet as a `u8` slice.
#[inline]
pub fn data(&self) -> &[u8] {
if let Ok(data) = self
.icmp()
.mbuf()
.read_data_slice(self.data_offset(), self.data_len())
{
unsafe { &*data.as_ptr() }
} else {
&[]
}
}
}

impl fmt::Debug for TimeExceeded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TimeExceeded")
.field("type", &format!("{}", self.msg_type()))
.field("code", &self.code())
.field("checksum", &format!("0x{:04x}", self.checksum()))
.field("$offset", &self.offset())
.field("$len", &self.len())
.field("$header_len", &self.header_len())
.finish()
}
}

impl Icmpv4Message for TimeExceeded {
#[inline]
fn msg_type() -> Icmpv4Type {
Icmpv4Types::TimeExceeded
}

#[inline]
fn icmp(&self) -> &Icmpv4 {
&self.icmp
}

#[inline]
fn icmp_mut(&mut self) -> &mut Icmpv4 {
&mut self.icmp
}

#[inline]
fn into_icmp(self) -> Icmpv4 {
self.icmp
}

#[inline]
unsafe fn clone(&self, internal: Internal) -> Self {
TimeExceeded {
icmp: self.icmp.clone(internal),
body: self.body,
}
}

#[inline]
fn try_parse(icmp: Icmpv4, _internal: Internal) -> Fallible<Self> {
let mbuf = icmp.mbuf();
let offset = icmp.payload_offset();
let body = mbuf.read_data(offset)?;

Ok(TimeExceeded { icmp, body })
}

#[inline]
fn try_push(mut icmp: Icmpv4, _internal: Internal) -> Fallible<Self> {
let offset = icmp.payload_offset();
let mbuf = icmp.mbuf_mut();

mbuf.extend(offset, TimeExceededBody::size_of())?;
let body = mbuf.write_data(offset, &TimeExceededBody::default())?;

Ok(TimeExceeded { icmp, body })
}

/// Reconciles the derivable header fields against the changes made to
/// the packet.
///
/// * the data field in the message body is trimmed if it exceeds the
/// [minimum IPV4 MTU], as we only need enough for port information.
/// * [`checksum`] is computed based on the `TimeExceeded` message.
///
/// [minimum IPv4 MTU]: IPV4_MIN_MTU
/// [`checksum`]: Icmpv4::checksum
#[inline]
fn reconcile(&mut self) {
let len = self.data_len();
let offset = self.data_offset();

if len > IPV4_MIN_MTU {
let _ = self
.mbuf_mut()
.shrink(offset + IPV4_MIN_MTU, len - IPV4_MIN_MTU);
}

self.icmp_mut().compute_checksum();
}
}

#[derive(Clone, Copy, Debug, Default, SizeOf)]
#[repr(C, packed)]
struct TimeExceededBody {
_unused: u32be,
}

#[cfg(test)]
mod tests {
use super::*;
use crate::packets::ip::v4::Ipv4;
use crate::packets::Ethernet;
use crate::testils::byte_arrays::IPV4_TCP_PACKET;
use crate::Mbuf;

#[test]
fn size_of_time_exceeded_body() {
assert_eq!(4, TimeExceededBody::size_of());
}

#[capsule::test]
fn push_and_set_time_exceeded() {
let packet = Mbuf::from_bytes(&IPV4_TCP_PACKET).unwrap();
let ethernet = packet.parse::<Ethernet>().unwrap();
let ipv4 = ethernet.parse::<Ipv4>().unwrap();
let tcp_len = ipv4.payload_len();

let mut exceeded = ipv4.push::<TimeExceeded>().unwrap();

assert_eq!(4, exceeded.header_len());
assert_eq!(
TimeExceededBody::size_of() + tcp_len,
exceeded.payload_len()
);
assert_eq!(Icmpv4Types::TimeExceeded, exceeded.msg_type());
assert_eq!(0, exceeded.code());
assert_eq!(tcp_len, exceeded.data().len());

exceeded.set_code(1);
assert_eq!(1, exceeded.code());

exceeded.reconcile_all();
assert!(exceeded.checksum() != 0);
}

#[capsule::test]
fn shrinks_to_ipv4_min_mtu() {
// starts with a buffer with a message body larger than min MTU.
let packet = Mbuf::from_bytes(&[42; 100]).unwrap();
let ethernet = packet.push::<Ethernet>().unwrap();
let ipv4 = ethernet.push::<Ipv4>().unwrap();
let mut exceeded = ipv4.push::<TimeExceeded>().unwrap();
assert!(exceeded.data_len() > IPV4_MIN_MTU);

exceeded.reconcile_all();
assert_eq!(IPV4_MIN_MTU, exceeded.data_len());
}

#[capsule::test]
fn message_body_no_shrink() {
// starts with a buffer with a message body smaller than min MTU.
let packet = Mbuf::from_bytes(&[42; 50]).unwrap();
let ethernet = packet.push::<Ethernet>().unwrap();
let ipv4 = ethernet.push::<Ipv4>().unwrap();
let mut exceeded = ipv4.push::<TimeExceeded>().unwrap();
assert!(exceeded.data_len() < IPV4_MIN_MTU);

exceeded.reconcile_all();
assert_eq!(50, exceeded.data_len());
}
}
8 changes: 4 additions & 4 deletions core/src/packets/icmp/v6/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ impl<E: Ipv6Packet> Packet for Icmpv6<E> {
/// * [`checksum`] is computed based on the [`pseudo-header`] and the
/// full packet.
///
/// [`checksum`]: Icmpv6::checksum
/// [`checksum`]: Icmpv6Packet::checksum
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noticed that functions on Icmpv4 and Icmpv6 have inconsistent visibility scopes. msg_type, code, set_code, checksum, compute_checksum should all be pub on Icmpv6. then don't need to fix these links anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good. I was going to do this, but wasn't sure why we had the inconsistency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep not sure how I missed this, but they need to be public so extending Icmpv6 is possible from outside the crate.

/// [`pseudo-header`]: crate::packets::checksum::PseudoHeader
#[inline]
fn reconcile(&mut self) {
Expand Down Expand Up @@ -412,7 +412,7 @@ pub trait Icmpv6Message {
/// the packet matches the assigned number before invoking this function.
///
/// [`Icmpv6::downcast`]: Icmpv6::downcast
/// [`msg_type`]: Icmpv6::msg_type
/// [`msg_type`]: Icmpv6Packet::msg_type
fn try_parse(icmp: Icmpv6<Self::Envelope>, internal: Internal) -> Fallible<Self>
where
Self: Sized;
Expand All @@ -429,7 +429,7 @@ pub trait Icmpv6Message {
/// This function cannot be invoked directly. It is internally used by
/// [`Packet::push`].
///
/// [`msg_type`]: Icmpv6::msg_type
/// [`msg_type`]: Icmpv6Packet::msg_type
/// [`Packet::push`]: Packet::push
fn try_push(icmp: Icmpv6<Self::Envelope>, internal: Internal) -> Fallible<Self>
where
Expand All @@ -439,7 +439,7 @@ pub trait Icmpv6Message {
/// the packet. The default implementation computes the [`checksum`]
/// based on the pseudo-header and the ICMPv6 message.
///
/// [`checksum`]: Icmpv6::checksum
/// [`checksum`]: Icmpv6Packet::checksum
#[inline]
fn reconcile(&mut self) {
self.icmp_mut().compute_checksum()
Expand Down
2 changes: 1 addition & 1 deletion core/src/packets/icmp/v6/ndp/redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl<E: Ipv6Packet> Icmpv6Message for Redirect<E> {
/// is set to account for the original IP header and data.
///
/// [minimum IPv6 MTU]: IPV6_MIN_MTU
/// [`checksum`]: Icmpv6::checksum
/// [`checksum`]: Icmpv6Packet::checksum
/// [`RedirectedHeader`]: RedirectedHeader
#[inline]
fn reconcile(&mut self) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/packets/icmp/v6/time_exceeded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl<E: Ipv6Packet> Icmpv6Message for TimeExceeded<E> {
/// `TimeExceeded` message.
///
/// [minimum IPv6 MTU]: IPV6_MIN_MTU
/// [`checksum`]: Icmpv6::checksum
/// [`checksum`]: Icmpv6Packet::checksum
#[inline]
fn reconcile(&mut self) {
let _ = self.envelope_mut().truncate(IPV6_MIN_MTU);
Expand Down
2 changes: 1 addition & 1 deletion core/src/packets/icmp/v6/too_big.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ impl<E: Ipv6Packet> Icmpv6Message for PacketTooBig<E> {
/// `PacketTooBig` message.
///
/// [minimum IPv6 MTU]: IPV6_MIN_MTU
/// [`checksum`]: Icmpv6::checksum
/// [`checksum`]: Icmpv6Packet::checksum
#[inline]
fn reconcile(&mut self) {
let _ = self.envelope_mut().truncate(IPV6_MIN_MTU);
Expand Down