From 6fea7ce527b8a8ec518f823e451300c48f85cdf0 Mon Sep 17 00:00:00 2001 From: drunkirishcoder Date: Sat, 8 Jan 2022 17:17:06 -0500 Subject: [PATCH 1/3] add ipip tunnel --- core/src/packets/icmp/v6/ndp/mod.rs | 6 +- core/src/packets/ip/mod.rs | 16 ++- core/src/packets/ip/tunnels/ipip.rs | 179 ++++++++++++++++++++++++++++ core/src/packets/ip/tunnels/mod.rs | 23 ++++ core/src/packets/mod.rs | 57 +++++++++ core/src/testils/byte_arrays.rs | 37 ++++++ 6 files changed, 310 insertions(+), 8 deletions(-) create mode 100644 core/src/packets/ip/tunnels/ipip.rs create mode 100644 core/src/packets/ip/tunnels/mod.rs diff --git a/core/src/packets/icmp/v6/ndp/mod.rs b/core/src/packets/icmp/v6/ndp/mod.rs index 2be9f43..644c6c8 100644 --- a/core/src/packets/icmp/v6/ndp/mod.rs +++ b/core/src/packets/icmp/v6/ndp/mod.rs @@ -814,11 +814,11 @@ mod tests { /// ICMPv6 packet with invalid MTU-option length. #[rustfmt::skip] const INVALID_OPTION_LENGTH: [u8;78] = [ - // ** ethernet Header + // ethernet Header 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x86, 0xDD, - // ** IPv6 Header + // IPv6 Header 0x60, 0x00, 0x00, 0x00, // payload length 0x00, 0x18, @@ -826,7 +826,7 @@ mod tests { 0xff, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0xf0, 0x45, 0xff, 0xfe, 0x0c, 0x66, 0x4b, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - // ** ICMPv6 Header + // ICMPv6 Header // type 0x86, // code diff --git a/core/src/packets/ip/mod.rs b/core/src/packets/ip/mod.rs index 5c466d1..08e60e8 100644 --- a/core/src/packets/ip/mod.rs +++ b/core/src/packets/ip/mod.rs @@ -18,6 +18,7 @@ //! Internet Protocol v4 and v6. +pub mod tunnels; pub mod v4; pub mod v6; @@ -61,17 +62,20 @@ pub mod ProtocolNumbers { /// User Datagram Protocol. pub const Udp: ProtocolNumber = ProtocolNumber(0x11); + /// IPv4 encapsulation. + pub const Ipv4: ProtocolNumber = ProtocolNumber(0x04); + /// Routing Header for IPv6. pub const Ipv6Route: ProtocolNumber = ProtocolNumber(0x2B); /// Fragment Header for IPv6. pub const Ipv6Frag: ProtocolNumber = ProtocolNumber(0x2C); - /// Internet Control Message Protocol for IPv6. - pub const Icmpv6: ProtocolNumber = ProtocolNumber(0x3A); - /// Internet Control Message Protocol for IPv4. pub const Icmpv4: ProtocolNumber = ProtocolNumber(0x01); + + /// Internet Control Message Protocol for IPv6. + pub const Icmpv6: ProtocolNumber = ProtocolNumber(0x3A); } impl fmt::Display for ProtocolNumber { @@ -82,10 +86,11 @@ impl fmt::Display for ProtocolNumber { match *self { ProtocolNumbers::Tcp => "TCP".to_string(), ProtocolNumbers::Udp => "UDP".to_string(), + ProtocolNumbers::Ipv4 => "IPv4".to_string(), ProtocolNumbers::Ipv6Route => "IPv6 Route".to_string(), ProtocolNumbers::Ipv6Frag => "IPv6 Frag".to_string(), - ProtocolNumbers::Icmpv6 => "ICMPv6".to_string(), ProtocolNumbers::Icmpv4 => "ICMPv4".to_string(), + ProtocolNumbers::Icmpv6 => "ICMPv6".to_string(), _ => format!("0x{:02x}", self.0), } ) @@ -272,10 +277,11 @@ mod tests { fn protocol_number_to_string() { assert_eq!("TCP", ProtocolNumbers::Tcp.to_string()); assert_eq!("UDP", ProtocolNumbers::Udp.to_string()); + assert_eq!("IPv4", ProtocolNumbers::Ipv4.to_string()); assert_eq!("IPv6 Route", ProtocolNumbers::Ipv6Route.to_string()); assert_eq!("IPv6 Frag", ProtocolNumbers::Ipv6Frag.to_string()); - assert_eq!("ICMPv6", ProtocolNumbers::Icmpv6.to_string()); assert_eq!("ICMPv4", ProtocolNumbers::Icmpv4.to_string()); + assert_eq!("ICMPv6", ProtocolNumbers::Icmpv6.to_string()); assert_eq!("0x00", ProtocolNumber::new(0).to_string()); } } diff --git a/core/src/packets/ip/tunnels/ipip.rs b/core/src/packets/ip/tunnels/ipip.rs new file mode 100644 index 0000000..a4c58cf --- /dev/null +++ b/core/src/packets/ip/tunnels/ipip.rs @@ -0,0 +1,179 @@ +/* +* 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 +*/ + +//! IPv4 in IPv4 tunnel. + +use crate::ensure; +use crate::packets::ethernet::Ethernet; +use crate::packets::ip::v4::Ipv4; +use crate::packets::ip::ProtocolNumbers; +use crate::packets::{Datalink, Packet, Tunnel}; +use anyhow::{anyhow, Result}; +use std::marker::PhantomData; + +/// [IPv4] encapsulation within IPv4 based on [IETF RFC 2003]. +/// +/// IP in IP tunnel connects two separate IPv4 networks. an outer IP header +/// is inserted before the datagram's existing IP header, as follows: +/// +/// +---------------------------+ +/// | | +/// | Outer IP Header | +/// | | +/// +---------------------------+ +---------------------------+ +/// | | | | +/// | IP Header | | IP Header | +/// | | | | +/// +---------------------------+ ====> +---------------------------+ +/// | | | | +/// | | | | +/// | IP Payload | | IP Payload | +/// | | | | +/// | | | | +/// +---------------------------+ +---------------------------+ +/// +/// This tunnel only supports unicast packets. +/// +/// [IPv4]: Ipv4 +/// [IETF RFC 2003]: https://datatracker.ietf.org/doc/html/rfc2003 +#[derive(Debug)] +pub struct IpIp { + _phantom: PhantomData, +} + +impl Tunnel for IpIp { + type Payload = Ipv4; + type Delivery = Ipv4; + + /// Encapsulates the existing IPv4 packet by prepending an outer IPv4 + /// packet. + /// + /// The DSCP and ECN options are copied from existing header to the new + /// outer header. + /// + /// # Remarks + /// + /// If the 'don't fragment' flag is set to true on the outer header, it + /// must not be unset. Otherwise, it may be set after tunnel encapsulation. + /// + /// # Errors + /// + /// Returns an error if the payload's time-to-live is `0`. The packet + /// should be discarded. + fn encap(payload: Self::Payload) -> Result { + ensure!(payload.ttl() != 0, anyhow!("payload's TTL is 0.")); + + let dscp = payload.dscp(); + let ecn = payload.ecn(); + let dont_fragment = payload.dont_fragment(); + + let envelope = payload.deparse(); + let mut delivery = envelope.push::()?; + delivery.set_dscp(dscp); + delivery.set_ecn(ecn); + if dont_fragment { + delivery.set_dont_fragment(); + } + delivery.set_protocol(ProtocolNumbers::Ipv4); + delivery.reconcile_all(); + + Ok(delivery) + } + + /// Decapsulates the outer IPv4 packet and returns the original payload + /// IPv4 packet. + /// + /// # Errors + /// + /// Returns an error if the protocol is not set to `ProtocolNumbers::Ipv4`, + /// indicating the packet is not from an IpIp tunnel. + fn decap(delivery: Self::Delivery) -> Result { + ensure!( + delivery.protocol() == ProtocolNumbers::Ipv4, + anyhow!("not an IPIP tunnel.") + ); + + let envelope = delivery.remove()?; + envelope.parse::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::ethernet::Ethernet; + use crate::packets::icmp::v4::EchoRequest; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::IPIP_PACKET; + + #[capsule::test] + fn encap_ipip_payload() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut ip4 = ip4.push::().unwrap().deparse(); + ip4.set_dscp(5); + ip4.set_ecn(1); + ip4.set_dont_fragment(); + ip4.reconcile(); + let payload_len = ip4.len(); + + let delivery = ip4.encap::().unwrap(); + assert_eq!(5, delivery.dscp()); + assert_eq!(1, delivery.ecn()); + assert!(delivery.dont_fragment()); + assert_eq!(payload_len + 20, delivery.len()); + } + + #[capsule::test] + fn encap_0ttl_payload() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let mut ip4 = ip4.push::().unwrap().deparse(); + ip4.set_ttl(0); + ip4.reconcile(); + + assert!(ip4.encap::().is_err()); + } + + #[capsule::test] + fn decap_ipip_delivery() { + let packet = Mbuf::from_bytes(&IPIP_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let delivery = ethernet.parse::().unwrap(); + let payload = delivery.decap::().unwrap(); + + assert_eq!("1.1.1.1", payload.src().to_string()); + assert_eq!("2.2.2.2", payload.dst().to_string()); + + // parse the payload's payload to verify packet integrity + assert!(payload.parse::().is_ok()); + } + + #[capsule::test] + fn decap_not_ipip() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip4 = ethernet.push::().unwrap(); + let notipip = ip4.push::().unwrap().deparse(); + + // the protocol is icmpv4 not ipv4, not an ipip tunnel + assert!(notipip.decap::().is_err()); + } +} diff --git a/core/src/packets/ip/tunnels/mod.rs b/core/src/packets/ip/tunnels/mod.rs new file mode 100644 index 0000000..018698f --- /dev/null +++ b/core/src/packets/ip/tunnels/mod.rs @@ -0,0 +1,23 @@ +/* +* 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 +*/ + +//! Various IP in IP tunnels. + +mod ipip; + +pub use self::ipip::*; diff --git a/core/src/packets/mod.rs b/core/src/packets/mod.rs index 86ea0fb..f93d061 100644 --- a/core/src/packets/mod.rs +++ b/core/src/packets/mod.rs @@ -300,6 +300,30 @@ pub trait Packet { self.reconcile(); self.envelope_mut().reconcile_all(); } + + /// Encapsulates the packet in a tunnel. + /// + /// Returns the delivery packet added by the tunnel. Once encapsulated, + /// the current packet and the current payload must not be modified. + /// The delivery packet treats the current packet as its opaque payload. + #[inline] + fn encap>(self) -> Result + where + Self: Sized, + { + T::encap(self) + } + + /// Decapsulates the tunnel by removing the delivery packet. + /// + /// Returns the payload packet encapsulated in the tunnel. + #[inline] + fn decap>(self) -> Result + where + Self: Sized, + { + T::decap(self) + } } /// A trait datalink layer protocol can implement. @@ -325,6 +349,39 @@ pub trait Datalink: Packet { fn set_protocol_type(&mut self, ether_type: EtherType); } +/// A trait all tunnel protocols must implement. +/// +/// This trait defines how the entry point should encapsulate the payload +/// packet, and how the exit point should decapsulate the delivery packet. +pub trait Tunnel { + /// The original packet type before entering the tunnel. + type Payload: Packet; + + /// The packet type tunnel uses to deliver the datagram to the tunnel + /// exit point. + type Delivery: Packet; + + /// Encapsulates the original packet and returns the new delivery packet. + /// + /// Once encapsulated, the original packet and its payload becomes the + /// opaque payload of the newly prepended delivery packet. The original + /// packet cannot be parsed and manipulated until it's decapsulated. The + /// encapsulator must construct the payload packet in its entirety before + /// invoking this. + /// + /// A tunnel protocol, for example GRE, may add multiple delivery packet + /// headers to the datagram. The return type should be the first + /// prepended packet type in the protocol stack. + fn encap(payload: Self::Payload) -> Result; + + /// Decapsulates the delivery packet and returns the original packet. + /// + /// Once decapsulated, the content of the delivery packet(s) are lost. + /// The decapsulator is responsible for caching the information before + /// invoking this. + fn decap(delivery: Self::Delivery) -> Result; +} + /// Immutable smart pointer to a struct. /// /// A smart pointer that prevents the struct from being modified. The main diff --git a/core/src/testils/byte_arrays.rs b/core/src/testils/byte_arrays.rs index f35818d..47998b3 100644 --- a/core/src/testils/byte_arrays.rs +++ b/core/src/testils/byte_arrays.rs @@ -398,3 +398,40 @@ pub const ROUTER_SOLICIT_PACKET: [u8; 70] = [ // source link-layer address option 0x01, 0x01, 0x70, 0x3a, 0xcb, 0x1b, 0xf9, 0x7a ]; + +/// IPv4 in IPv4 tunnel packet. +#[rustfmt::skip] +pub const IPIP_PACKET: [u8; 134] = [ +// ethernet header + 0xc2, 0x01, 0x57, 0x75, 0x00, 0x00, + 0xc2, 0x00, 0x57, 0x75, 0x00, 0x00, + 0x08, 0x00, +// delivery IPv4 header + 0x45, 0x00, + 0x00, 0x78, + 0x00, 0x14, 0x00, 0x00, + 0xff, 0x04, 0xa7, 0x6b, + 0x0a, 0x00, 0x00, 0x01, + 0x0a, 0x00, 0x00, 0x02, +// payload IPv4 header + 0x45, 0x00, + 0x00, 0x64, + 0x00, 0x14, 0x00, 0x00, + 0xff, 0x01, 0xb5, 0x7f, + 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, +// payload ICMPv4 header + 0x08, 0x00, + 0x43, 0x05, + // echo request payload data + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x09, 0x3b, 0x38, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd +]; From 0626f2266440df8085dc0054d326e0433f96f834 Mon Sep 17 00:00:00 2001 From: drunkirishcoder Date: Sat, 8 Jan 2022 22:14:29 -0500 Subject: [PATCH 2/3] add ip6in4 tunnel --- core/src/packets/ip/mod.rs | 5 + core/src/packets/ip/tunnels/ip6in4.rs | 172 ++++++++++++++++++++++++++ core/src/packets/ip/tunnels/ipip.rs | 15 ++- core/src/packets/ip/tunnels/mod.rs | 2 + core/src/testils/byte_arrays.rs | 46 +++++-- 5 files changed, 222 insertions(+), 18 deletions(-) create mode 100644 core/src/packets/ip/tunnels/ip6in4.rs diff --git a/core/src/packets/ip/mod.rs b/core/src/packets/ip/mod.rs index 08e60e8..e710ac2 100644 --- a/core/src/packets/ip/mod.rs +++ b/core/src/packets/ip/mod.rs @@ -65,6 +65,9 @@ pub mod ProtocolNumbers { /// IPv4 encapsulation. pub const Ipv4: ProtocolNumber = ProtocolNumber(0x04); + /// IPv6 encapsulation. + pub const Ipv6: ProtocolNumber = ProtocolNumber(0x29); + /// Routing Header for IPv6. pub const Ipv6Route: ProtocolNumber = ProtocolNumber(0x2B); @@ -87,6 +90,7 @@ impl fmt::Display for ProtocolNumber { ProtocolNumbers::Tcp => "TCP".to_string(), ProtocolNumbers::Udp => "UDP".to_string(), ProtocolNumbers::Ipv4 => "IPv4".to_string(), + ProtocolNumbers::Ipv6 => "IPv6".to_string(), ProtocolNumbers::Ipv6Route => "IPv6 Route".to_string(), ProtocolNumbers::Ipv6Frag => "IPv6 Frag".to_string(), ProtocolNumbers::Icmpv4 => "ICMPv4".to_string(), @@ -278,6 +282,7 @@ mod tests { assert_eq!("TCP", ProtocolNumbers::Tcp.to_string()); assert_eq!("UDP", ProtocolNumbers::Udp.to_string()); assert_eq!("IPv4", ProtocolNumbers::Ipv4.to_string()); + assert_eq!("IPv6", ProtocolNumbers::Ipv6.to_string()); assert_eq!("IPv6 Route", ProtocolNumbers::Ipv6Route.to_string()); assert_eq!("IPv6 Frag", ProtocolNumbers::Ipv6Frag.to_string()); assert_eq!("ICMPv4", ProtocolNumbers::Icmpv4.to_string()); diff --git a/core/src/packets/ip/tunnels/ip6in4.rs b/core/src/packets/ip/tunnels/ip6in4.rs new file mode 100644 index 0000000..bb108b8 --- /dev/null +++ b/core/src/packets/ip/tunnels/ip6in4.rs @@ -0,0 +1,172 @@ +/* +* 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 +*/ + +//! IPv6 in IPv4 tunnel. + +use crate::ensure; +use crate::packets::ethernet::{EtherTypes, Ethernet}; +use crate::packets::ip::v4::Ipv4; +use crate::packets::ip::v6::Ipv6; +use crate::packets::ip::ProtocolNumbers; +use crate::packets::{Datalink, Packet, Tunnel}; +use anyhow::{anyhow, Result}; +use std::marker::PhantomData; + +/// [IPv6] encapsulation within [IPv4] based on [IETF RFC 4213]. +/// +/// An IPv4 compatibility mechanisms designed to be employed by IPv6 hosts +/// and routers that need to interoperate with IPv4 hosts and utilize IPv4 +/// routing infrastructures. +/// +/// +-------------+ +/// | IPv4 | +/// | Header | +/// +-------------+ +-------------+ +/// | IPv6 | | IPv6 | +/// | Header | | Header | +/// +-------------+ +-------------+ +/// | Transport | | Transport | +/// | Layer | ===> | Layer | +/// | Header | | Header | +/// +-------------+ +-------------+ +/// | | | | +/// ~ Data ~ ~ Data ~ +/// | | | | +/// +-------------+ +-------------+ +/// +/// [IPv6]: Ipv6 +/// [IPv4]: Ipv4 +/// [IETF RFC 4213]: https://datatracker.ietf.org/doc/html/rfc4213 +#[derive(Debug)] +pub struct Ip6in4 { + _phantom: PhantomData, +} + +impl Tunnel for Ip6in4 { + type Payload = Ipv6; + type Delivery = Ipv4; + + /// Encapsulates the existing IPv6 packet by prepending an outer IPv4 + /// packet. + /// + /// The DSCP and ECN options are copied from existing header to the new + /// outer header. + /// + /// # Errors + /// + /// Returns an error if the payload's hop limit is `0`. The packet should + /// be discarded. + fn encap(payload: Self::Payload) -> Result { + ensure!( + payload.hop_limit() != 0, + anyhow!("payload's hop limit is 0.") + ); + + let dscp = payload.dscp(); + let ecn = payload.ecn(); + + let envelope = payload.deparse(); + let mut delivery = envelope.push::()?; + delivery.set_dscp(dscp); + delivery.set_ecn(ecn); + delivery.set_protocol(ProtocolNumbers::Ipv6); + delivery.reconcile_all(); + + Ok(delivery) + } + + /// Decapsulates the outer IPv4 packet and returns the original payload + /// IPv6 packet. + /// + /// # Errors + /// + /// Returns an error if the protocol is not set to `ProtocolNumbers::Ipv6`, + /// indicating the packet is not from an Ip6in4 tunnel. + fn decap(delivery: Self::Delivery) -> Result { + ensure!( + delivery.protocol() == ProtocolNumbers::Ipv6, + anyhow!("not an Ip6in4 tunnel.") + ); + + let mut envelope = delivery.remove()?; + envelope.set_protocol_type(EtherTypes::Ipv6); + envelope.parse::() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::ethernet::Ethernet; + use crate::packets::icmp::v6::EchoRequest; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{IP6IN4_PACKET, IPIP_PACKET}; + + #[capsule::test] + fn encap_ip6in4_payload() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut ip6 = ip6.push::().unwrap().deparse(); + ip6.set_dscp(5); + ip6.set_ecn(1); + ip6.reconcile(); + let payload_len = ip6.len(); + + let delivery = ip6.encap::().unwrap(); + assert_eq!(5, delivery.dscp()); + assert_eq!(1, delivery.ecn()); + assert_eq!(payload_len + 20, delivery.len()); + } + + #[capsule::test] + fn encap_0_hop_limit_payload() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut ip6 = ip6.push::().unwrap().deparse(); + ip6.set_hop_limit(0); + ip6.reconcile(); + + assert!(ip6.encap::().is_err()); + } + + #[capsule::test] + fn decap_ip6in4_delivery() { + let packet = Mbuf::from_bytes(&IP6IN4_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let delivery = ethernet.parse::().unwrap(); + let payload = delivery.decap::().unwrap(); + + assert_eq!("2001:db8:0:1::1", payload.src().to_string()); + assert_eq!("2001:db8:0:1::2", payload.dst().to_string()); + + // parse the payload's payload to verify packet integrity + assert!(payload.parse::().is_ok()); + } + + #[capsule::test] + fn decap_not_ip6in4() { + let packet = Mbuf::from_bytes(&IPIP_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let not6in4 = ethernet.parse::().unwrap(); + + // not an ip6in4 tunnel + assert!(not6in4.decap::().is_err()); + } +} diff --git a/core/src/packets/ip/tunnels/ipip.rs b/core/src/packets/ip/tunnels/ipip.rs index a4c58cf..8766742 100644 --- a/core/src/packets/ip/tunnels/ipip.rs +++ b/core/src/packets/ip/tunnels/ipip.rs @@ -105,7 +105,7 @@ impl Tunnel for IpIp { fn decap(delivery: Self::Delivery) -> Result { ensure!( delivery.protocol() == ProtocolNumbers::Ipv4, - anyhow!("not an IPIP tunnel.") + anyhow!("not an IpIp tunnel.") ); let envelope = delivery.remove()?; @@ -119,7 +119,7 @@ mod tests { use crate::packets::ethernet::Ethernet; use crate::packets::icmp::v4::EchoRequest; use crate::packets::Mbuf; - use crate::testils::byte_arrays::IPIP_PACKET; + use crate::testils::byte_arrays::{IP6IN4_PACKET, IPIP_PACKET}; #[capsule::test] fn encap_ipip_payload() { @@ -141,7 +141,7 @@ mod tests { } #[capsule::test] - fn encap_0ttl_payload() { + fn encap_0_ttl_payload() { let packet = Mbuf::new().unwrap(); let ethernet = packet.push::().unwrap(); let ip4 = ethernet.push::().unwrap(); @@ -168,12 +168,11 @@ mod tests { #[capsule::test] fn decap_not_ipip() { - let packet = Mbuf::new().unwrap(); - let ethernet = packet.push::().unwrap(); - let ip4 = ethernet.push::().unwrap(); - let notipip = ip4.push::().unwrap().deparse(); + let packet = Mbuf::from_bytes(&IP6IN4_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let notipip = ethernet.parse::().unwrap(); - // the protocol is icmpv4 not ipv4, not an ipip tunnel + // not an ipip tunnel assert!(notipip.decap::().is_err()); } } diff --git a/core/src/packets/ip/tunnels/mod.rs b/core/src/packets/ip/tunnels/mod.rs index 018698f..63746a0 100644 --- a/core/src/packets/ip/tunnels/mod.rs +++ b/core/src/packets/ip/tunnels/mod.rs @@ -18,6 +18,8 @@ //! Various IP in IP tunnels. +mod ip6in4; mod ipip; +pub use self::ip6in4::*; pub use self::ipip::*; diff --git a/core/src/testils/byte_arrays.rs b/core/src/testils/byte_arrays.rs index 47998b3..4b3b09b 100644 --- a/core/src/testils/byte_arrays.rs +++ b/core/src/testils/byte_arrays.rs @@ -424,14 +424,40 @@ pub const IPIP_PACKET: [u8; 134] = [ 0x08, 0x00, 0x43, 0x05, // echo request payload data - 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x09, 0x3b, 0x38, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, - 0xab, 0xcd, 0xab, 0xcd + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x3b, 0x38, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd +]; + +/// IPv6 in IPv4 tunnel packet. +#[rustfmt::skip] +pub const IP6IN4_PACKET: [u8; 134] = [ +// ethernet header + 0xc2, 0x01, 0x42, 0x02, 0x00, 0x00, + 0xc2, 0x00, 0x42, 0x02, 0x00, 0x00, + 0x08, 0x00, +// delivery IPv4 header + 0x45, 0x00, + 0x00, 0x78, + 0x00, 0x09, 0x00, 0x00, + 0xff, 0x29, 0xa7, 0x51, + 0x0a, 0x00, 0x00, 0x01, + 0x0a, 0x00, 0x00, 0x02, +// payload IPv6 header + 0x60, 0x00, 0x00, 0x00, + 0x00, 0x3c, + 0x3a, + 0x40, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, +// payload ICMPv6 header + 0x80, 0x00, + 0x7e, 0x8f, + // echo request payload data + 0x18, 0xdc, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 ]; From 6ab3d830f9f03ba6ce8c973d3d2fbd79181a0505 Mon Sep 17 00:00:00 2001 From: drunkirishcoder Date: Sun, 9 Jan 2022 17:59:25 -0500 Subject: [PATCH 3/3] add ip4gre tunnel --- core/src/packets/checksum.rs | 4 +- core/src/packets/ethernet.rs | 2 +- core/src/packets/gre/ip4gre.rs | 136 ++++++ core/src/packets/gre/mod.rs | 623 ++++++++++++++++++++++++++ core/src/packets/icmp/v4/mod.rs | 2 +- core/src/packets/icmp/v6/mod.rs | 2 +- core/src/packets/ip/mod.rs | 5 + core/src/packets/ip/tunnels/ip6in4.rs | 16 +- core/src/packets/ip/tunnels/ipip.rs | 16 +- core/src/packets/ip/v4.rs | 2 +- core/src/packets/mod.rs | 1 + core/src/packets/size_of.rs | 13 + core/src/packets/tcp.rs | 2 +- core/src/packets/udp.rs | 2 +- core/src/testils/byte_arrays.rs | 34 ++ 15 files changed, 840 insertions(+), 20 deletions(-) create mode 100644 core/src/packets/gre/ip4gre.rs create mode 100644 core/src/packets/gre/mod.rs diff --git a/core/src/packets/checksum.rs b/core/src/packets/checksum.rs index 3984088..4526c80 100644 --- a/core/src/packets/checksum.rs +++ b/core/src/packets/checksum.rs @@ -142,10 +142,10 @@ fn v6_csum(src: Ipv6Addr, dst: Ipv6Addr, packet_len: u16, protocol: ProtocolNumb /// /// [IETF RFC 1071]: https://tools.ietf.org/html/rfc1071 #[allow(clippy::cast_ptr_alignment)] -pub fn compute(pseudo_header_sum: u16, payload: &[u8]) -> u16 { +pub fn ones_complement(base: u16, payload: &[u8]) -> u16 { let len = payload.len(); let mut data = payload; - let mut checksum = u32::from(pseudo_header_sum); + let mut checksum = u32::from(base); // odd # of bytes, we add the last byte with padding separately if len % 2 > 0 { diff --git a/core/src/packets/ethernet.rs b/core/src/packets/ethernet.rs index 67a06b3..b707066 100644 --- a/core/src/packets/ethernet.rs +++ b/core/src/packets/ethernet.rs @@ -315,7 +315,7 @@ impl Packet for Ethernet { // the buffer will cause data corruption because it will write // past the 14 bytes we extended the buffer by. mbuf.extend(offset, ETH_HEADER_SIZE)?; - let _ = mbuf.write_data_slice(offset, &[0; ETH_HEADER_SIZE])?; + let _ = mbuf.write_data_slice(offset, &[0u8; ETH_HEADER_SIZE])?; let header = mbuf.read_data(offset)?; Ok(Ethernet { diff --git a/core/src/packets/gre/ip4gre.rs b/core/src/packets/gre/ip4gre.rs new file mode 100644 index 0000000..010df15 --- /dev/null +++ b/core/src/packets/gre/ip4gre.rs @@ -0,0 +1,136 @@ +/* +* 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::ensure; +use crate::packets::ethernet::Ethernet; +use crate::packets::gre::{Gre, GreTunnelPacket}; +use crate::packets::ip::v4::Ipv4; +use crate::packets::ip::{IpPacket, ProtocolNumbers}; +use crate::packets::{Datalink, Packet, Tunnel}; +use anyhow::{anyhow, Result}; +use std::marker::PhantomData; + +/// Arbitrary layer 3 protocol encapsulated in IPv4 with GRE based on +/// [IETF RFC 2784]. +/// +/// The payload is first encapsulated in a GRE packet. The resulting +/// GRE packet is then encapsulated in IPv4 and then forwarded. +/// +/// [IETF RFC 2784]: https://datatracker.ietf.org/doc/html/rfc2784 +/// [IPv4]: crate::packets::ip::v4::Ipv4 +#[derive(Debug)] +pub struct Ip4Gre, D: Datalink = Ethernet> { + _phantom1: PhantomData

, + _phantom2: PhantomData, +} + +impl, D: Datalink> Tunnel for Ip4Gre { + type Payload = P; + type Delivery = Ipv4; + + /// Encapsulates the existing layer-3 packet by prepending an outer IPv4 + /// and a GRE packets. + fn encap(payload: Self::Payload) -> Result { + let envelope = payload.deparse(); + let protocol_type = envelope.protocol_type(); + let ip4 = envelope.push::()?; + let mut gre = ip4.push::>()?; + gre.set_protocol_type(protocol_type); + gre.reconcile_all(); + Ok(gre.deparse()) + } + + /// Decapsulates the outer IPv4 and GRE packets and returns the original + /// layer-3 packet. + fn decap(delivery: Self::Delivery) -> Result { + ensure!(delivery.gre_payload(), anyhow!("not an Ip4Gre tunnel.")); + + let gre = delivery.parse::>()?; + let protocol_type = gre.protocol_type(); + + let mut envelope = gre.remove()?.remove()?; + envelope.set_protocol_type(protocol_type); + envelope.parse::() + } +} + +/// Generic impl for all IpPacket. +impl GreTunnelPacket for T { + fn gre_payload(&self) -> bool { + self.next_protocol() == ProtocolNumbers::Gre + } + + fn mark_gre_payload(&mut self) { + self.set_next_protocol(ProtocolNumbers::Gre) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::ethernet::EtherTypes; + use crate::packets::icmp::v4::EchoRequest; + use crate::packets::icmp::v6::EchoReply; + use crate::packets::ip::v6::Ipv6; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{IP4GRE_PACKET, IPIP_PACKET}; + + #[capsule::test] + fn encap_ip4gre_payload() { + let packet = Mbuf::new().unwrap(); + let ethernet = packet.push::().unwrap(); + let ip6 = ethernet.push::().unwrap(); + let mut ip6 = ip6.push::().unwrap().deparse(); + ip6.reconcile(); + let payload_len = ip6.len(); + + let delivery = ip6.encap::>().unwrap(); + assert_eq!(EtherTypes::Ipv4, delivery.envelope().protocol_type()); + assert_eq!(ProtocolNumbers::Gre, delivery.protocol()); + + let gre = delivery.parse::>().unwrap(); + assert_eq!(EtherTypes::Ipv6, gre.protocol_type()); + + // check payload matches original packet length + assert_eq!(payload_len, gre.payload_len()); + } + + #[capsule::test] + fn decap_ip4gre_delivery() { + let packet = Mbuf::from_bytes(&IP4GRE_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let delivery = ethernet.parse::().unwrap(); + let payload = delivery.decap::>().unwrap(); + + assert_eq!("1.1.1.1", payload.src().to_string()); + assert_eq!("2.2.2.2", payload.dst().to_string()); + + // parse the payload's payload to verify packet integrity + assert!(payload.parse::().is_ok()); + } + + #[capsule::test] + fn decap_not_ip4gre() { + let packet = Mbuf::from_bytes(&IPIP_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let notip4gre = ethernet.parse::().unwrap(); + + // not an ip4gre tunnel + assert!(notip4gre.decap::>().is_err()); + } +} diff --git a/core/src/packets/gre/mod.rs b/core/src/packets/gre/mod.rs new file mode 100644 index 0000000..d72eef9 --- /dev/null +++ b/core/src/packets/gre/mod.rs @@ -0,0 +1,623 @@ +/* +* 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 +*/ + +//! Generic Routing Encapsulation + +mod ip4gre; + +pub use self::ip4gre::*; + +use crate::ensure; +use crate::packets::ethernet::EtherType; +use crate::packets::types::{u16be, u32be}; +use crate::packets::{checksum, Internal, Packet, SizeOf}; +use anyhow::{anyhow, Result}; +use std::fmt; +use std::ptr::NonNull; + +/// Option bit flags +const C: u8 = 0b1000_0000; +const K: u8 = 0b0010_0000; +const S: u8 = 0b0001_0000; +const FLAGS: u8 = C | K | S; + +/// Generic Routing Encapsulation based on [IETF RFC 2784]. +/// +/// ``` +/// 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 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// |C| |K|S| Reserved0 | Ver | Protocol Type | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Checksum (optional) | Reserved1 (Optional) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Key (optional) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Sequence Number (Optional) | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +/// +/// - *Checksum Present*: (1 bit) +/// If the Checksum Present bit is set to one, then the Checksum and the +/// Reserved1 fields are present and the Checksum field contains valid +/// information. +/// +/// - *Key Present*: (1 bit) +/// If the Key Present bit is set to 1, then it indicates that the Key +/// field is present in the GRE header. Defined in [IETF RFC 2890]. +/// +/// - *Sequence Number Present*: (1 bit) +/// If the Sequence Number Present bit is set to 1, then it indicates +/// that the Sequence Number field is present. Defined in [IETF RFC 2890]. +/// +/// - *Reserved0*: (9 bits) +/// reserved for future use. These bits MUST be sent as zero and MUST be +/// ignored on receipt. +/// +/// - *Version Number*: (3 bits) +/// The Version Number field MUST contain the value zero. +/// +/// - *Protocol Type*: (16 bits) +/// The Protocol Type field contains the protocol type of the payload +/// packet. These Protocol Types are defined as "ETHER TYPES". +/// +/// - *Checksum*: (16 bits) +/// The Checksum field contains the IP (one's complement) checksum sum of +/// the all the 16 bit words in the GRE header and the payload packet. +/// +/// - *Reserved1*: (16 bits) +/// The Reserved1 field is reserved for future use, and if present, MUST +/// be transmitted as zero. +/// +/// - *Key Field*: (32 bits) +/// The Key field is intended to be used for identifying an individual +/// traffic flow within a tunnel. Defined in [IETF RFC 2890]. +/// +/// - *Sequence Number*: (32 bits) +/// The Sequence Number MUST be used by the receiver to establish the +/// order in which packets have been transmitted from the encapsulator to +/// the receiver. The intended use of the Sequence Field is to provide +/// unreliable but in-order delivery. +/// +/// A GRE encapsulated packet has the form +/// +/// ``` +/// --------------------------------- +/// | | +/// | Delivery Header | +/// | | +/// --------------------------------- +/// | | +/// | GRE Header | +/// | | +/// --------------------------------- +/// | | +/// | Payload packet | +/// | | +/// --------------------------------- +/// ``` +/// +/// # Remarks +/// +/// The implementation of GRE treats the payload as opaque. It cannot be +/// parsed or manipulated with type safety. Work with the typed payload +/// packet either before encapsulation or after decapsulation like other +/// tunnel protocols. +/// +/// [IETF RFC 2784]: https://datatracker.ietf.org/doc/html/rfc2784 +/// [IETF RFC 2890]: https://datatracker.ietf.org/doc/html/rfc2890 +pub struct Gre { + envelope: E, + header: NonNull, + checksum: Option>, + key: Option>, + seqno: Option>, + offset: usize, +} + +/// A trait for packets that can be a GRE envelope. +pub trait GreTunnelPacket: Packet { + /// Returns whether the current payload is GRE. + fn gre_payload(&self) -> bool; + + /// Marks the envelope to be containing a GRE payload. + fn mark_gre_payload(&mut self); +} + +impl Gre { + #[inline] + fn header(&self) -> &GreHeader { + unsafe { self.header.as_ref() } + } + + #[inline] + fn header_mut(&mut self) -> &mut GreHeader { + unsafe { self.header.as_mut() } + } + + /// Returns whether the checksum is present. + /// + /// If the bit is set, `reconcile` will calculate the checksum. + #[inline] + pub fn checksum_present(&self) -> bool { + (self.header().flags_to_res0 & C) != 0 + } + + /// offset of the checksum field if it were present. + #[inline] + fn checksum_offset(&self) -> usize { + self.offset() + 4 + } + + /// Sets the checksum present flag to true. + #[inline] + pub fn set_checksum_present(&mut self) -> Result<()> { + if !self.checksum_present() { + // extend the buffer to add the field + let offset = self.checksum_offset(); + self.mbuf_mut().extend(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 |= C; + self.sync_optionals()?; + } + + Ok(()) + } + + /// Sets the checksum present flag to false and removes the field. + #[inline] + pub fn unset_checksum_present(&mut self) -> Result<()> { + if self.checksum_present() { + // shrink the buffer to remove the field + let offset = self.checksum_offset(); + self.mbuf_mut().shrink(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 &= !C; + self.sync_optionals()?; + } + + Ok(()) + } + + /// Returns the packet checksum or `None` if the option is not set. + #[inline] + pub fn checksum(&self) -> Option { + self.checksum.map(|ptr| unsafe { *ptr.as_ref() }.into()) + } + + #[inline] + fn set_checksum(&mut self, checksum: u16) { + // no op if the checksum present bit not set. + if let Some(ref mut ptr) = self.checksum { + unsafe { *ptr.as_mut() = checksum.into() }; + } + } + + #[inline] + fn compute_checksum(&mut self) { + self.set_checksum(0); + + if let Ok(data) = self.mbuf().read_data_slice(self.offset, self.len()) { + let data = unsafe { data.as_ref() }; + let checksum = checksum::ones_complement(0, data); + self.set_checksum(checksum); + } else { + // we are reading the entire packet, should never run out + unreachable!() + } + } + + /// Returns whether the key is present. + #[inline] + pub fn key_present(&self) -> bool { + (self.header().flags_to_res0 & K) != 0 + } + + /// offset of the key field if it were present. + #[inline] + fn key_offset(&self) -> usize { + self.offset() + 4 + if self.checksum_present() { 4 } else { 0 } + } + + /// Sets the key present flag to false and removes the field. + #[inline] + pub fn unset_key_present(&mut self) -> Result<()> { + if self.key_present() { + // shrink the buffer to remove the field + let offset = self.key_offset(); + self.mbuf_mut().shrink(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 &= !K; + self.sync_optionals()?; + } + + Ok(()) + } + + /// Returns the key field or `None` if the option is not set. + #[inline] + pub fn key(&self) -> Option { + self.key.map(|ptr| unsafe { *ptr.as_ref() }.into()) + } + + /// Sets the key field and key present flag to true. + #[inline] + pub fn set_key(&mut self, key: u32) -> Result<()> { + if !self.key_present() { + // extend the buffer to add the field + let offset = self.key_offset(); + self.mbuf_mut().extend(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 |= K; + self.sync_optionals()?; + } + + // should always match + if let Some(ref mut ptr) = self.key { + unsafe { *ptr.as_mut() = key.into() }; + } + + Ok(()) + } + + /// Returns whether the sequence number is present. + #[inline] + pub fn seqno_present(&self) -> bool { + (self.header().flags_to_res0 & S) != 0 + } + + /// offset of the sequence number field if it were present. + #[inline] + fn seqno_offset(&self) -> usize { + self.offset() + + 4 + + if self.checksum_present() { 4 } else { 0 } + + if self.key_present() { 4 } else { 0 } + } + + /// Sets the sequence number present flag to false and removes the field. + #[inline] + pub fn unset_seqno_present(&mut self) -> Result<()> { + if self.seqno_present() { + // shrink the buffer to remove the field + let offset = self.seqno_offset(); + self.mbuf_mut().shrink(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 &= !S; + self.sync_optionals()?; + } + + Ok(()) + } + + /// Returns the sequence number or `None` if the option is not set. + pub fn seqno(&self) -> Option { + self.seqno.map(|ptr| unsafe { *ptr.as_ref() }.into()) + } + + /// Sets the sequence number field and sequence number present flag to true. + #[inline] + pub fn set_seqno(&mut self, seqno: u32) -> Result<()> { + if !self.seqno_present() { + // extend the buffer to add the field + let offset = self.seqno_offset(); + self.mbuf_mut().extend(offset, u32be::size_of())?; + self.header_mut().flags_to_res0 |= S; + self.sync_optionals()?; + } + + // should always match + if let Some(ref mut ptr) = self.seqno { + unsafe { *ptr.as_mut() = seqno.into() }; + } + + Ok(()) + } + + /// Returns the version number. + /// + /// # Remarks + /// + /// Version must always be 0. + #[inline] + pub fn version(&self) -> u8 { + self.header().res0_to_ver & 0b111 + } + + /// Returns the protocol type of the payload packet. + #[inline] + pub fn protocol_type(&self) -> EtherType { + EtherType::new(self.header().protocol_type.into()) + } + + /// Sets the protocol type of the payload packet. + #[inline] + pub fn set_protocol_type(&mut self, protocol_type: EtherType) { + self.header_mut().protocol_type = protocol_type.0.into() + } + + /// Syncs the pointers to optional fields after one of the bits was + /// changed. The underlying buffer should be adjusted accordingly before + /// sync or it will corrupt the packet. + #[inline] + fn sync_optionals(&mut self) -> Result<()> { + let mut offset = self.offset + 4; + + self.checksum = None; + if self.checksum_present() { + let ptr = self.mbuf().read_data::(offset)?; + self.checksum = Some(ptr); + offset += 4; + } + + self.key = None; + if self.key_present() { + let ptr = self.mbuf().read_data::(offset)?; + self.key = Some(ptr); + offset += 4; + } + + self.seqno = None; + if self.seqno_present() { + let ptr = self.mbuf().read_data::(offset)?; + self.seqno = Some(ptr); + } + + Ok(()) + } +} + +impl fmt::Debug for Gre { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("gre") + .field("checksum_present", &self.checksum_present()) + .field("key_present", &self.key_present()) + .field("seqno_present", &self.seqno_present()) + .field("version", &self.version()) + .field("protocol_type", &self.protocol_type()) + .field( + "checksum", + &self + .checksum() + .map(|checksum| format!("0x{:04x}", checksum)) + .unwrap_or_else(|| "[none]".to_string()), + ) + .field( + "key", + &self + .key() + .map(|key| format!("{}", key)) + .unwrap_or_else(|| "[none]".to_string()), + ) + .field( + "seqno", + &self + .seqno() + .map(|seq| format!("{}", seq)) + .unwrap_or_else(|| "[none]".to_string()), + ) + .field("$offset", &self.offset()) + .field("$len", &self.len()) + .field("$header_len", &self.header_len()) + .finish() + } +} + +impl Packet for Gre { + type Envelope = E; + + #[inline] + fn envelope(&self) -> &Self::Envelope { + &self.envelope + } + + #[inline] + fn envelope_mut(&mut self) -> &mut Self::Envelope { + &mut self.envelope + } + + #[inline] + fn offset(&self) -> usize { + self.offset + } + + /// GRE header is dynamically sized based on the number of set optional + /// fields. + #[inline] + fn header_len(&self) -> usize { + let set_options = (self.header().flags_to_res0 & FLAGS).count_ones(); + (set_options as usize + 1) * 4 + } + + #[inline] + unsafe fn clone(&self, internal: Internal) -> Self { + Gre:: { + envelope: self.envelope.clone(internal), + header: self.header, + checksum: self.checksum, + key: self.key, + seqno: self.seqno, + offset: self.offset, + } + } + + /// Parses the envelope's payload as a GRE packet. + /// + /// # Errors + /// + /// Returns an error if the envelope's payload is not GRE. + #[inline] + fn try_parse(envelope: Self::Envelope, _internal: Internal) -> Result { + ensure!(envelope.gre_payload(), anyhow!("not a GRE packet.")); + + let mbuf = envelope.mbuf(); + let offset = envelope.payload_offset(); + let header = mbuf.read_data(offset)?; + + let mut packet = Gre { + envelope, + header, + checksum: None, + key: None, + seqno: None, + offset, + }; + packet.sync_optionals()?; + + Ok(packet) + } + + /// Prepends a GRE packet to the beginning of the envelope's payload. + /// + /// # Errors + /// + /// Returns an error if the buffer does not have enough free space. + #[inline] + fn try_push(mut envelope: Self::Envelope, _internal: Internal) -> Result { + let offset = envelope.payload_offset(); + let mbuf = envelope.mbuf_mut(); + + mbuf.extend(offset, GreHeader::size_of())?; + let header = mbuf.write_data(offset, &GreHeader::default())?; + + let mut packet = Gre { + envelope, + header, + checksum: None, + key: None, + seqno: None, + offset, + }; + packet.envelope_mut().mark_gre_payload(); + + Ok(packet) + } + + #[inline] + fn deparse(self) -> Self::Envelope { + self.envelope + } + + /// Reconciles the derivable header fields against the changes made to + /// the packet. + /// + /// * [`checksum`] is computed based on payload. + #[inline] + fn reconcile(&mut self) { + if self.checksum_present() { + self.compute_checksum() + } + } +} + +/// GRE header. +#[derive(Clone, Copy, Debug, Default, SizeOf)] +#[repr(C, packed)] +struct GreHeader { + flags_to_res0: u8, + res0_to_ver: u8, + protocol_type: u16be, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::packets::ethernet::{EtherTypes, Ethernet}; + use crate::packets::ip::v4::Ipv4; + use crate::packets::Mbuf; + use crate::testils::byte_arrays::{IP4GRE_PACKET, TCP4_PACKET}; + + #[test] + fn size_of_gre_header() { + assert_eq!(4, GreHeader::size_of()); + } + + #[capsule::test] + fn parse_gre_packet() { + let packet = Mbuf::from_bytes(&IP4GRE_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let gre = ip4.parse::>().unwrap(); + + assert!(!gre.checksum_present()); + assert!(!gre.key_present()); + assert!(!gre.seqno_present()); + assert_eq!(0, gre.version()); + assert_eq!(EtherTypes::Ipv4, gre.protocol_type()); + assert_eq!(None, gre.checksum()); + assert_eq!(None, gre.key()); + assert_eq!(None, gre.seqno()); + + // without options, the header length is 4. + assert_eq!(4, gre.header_len()); + } + + #[capsule::test] + fn parse_gre_setter_checks() { + let packet = Mbuf::from_bytes(&IP4GRE_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + let mut gre = ip4.parse::>().unwrap(); + + gre.set_checksum_present().unwrap(); + gre.set_key(1).unwrap(); + gre.set_seqno(2).unwrap(); + gre.reconcile(); + + assert!(gre.checksum_present()); + assert!(gre.key_present()); + assert!(gre.seqno_present()); + assert!(gre.checksum() != Some(0)); + assert_eq!(Some(1), gre.key()); + assert_eq!(Some(2), gre.seqno()); + assert_eq!(16, gre.header_len()); + + gre.unset_checksum_present().unwrap(); + + assert!(!gre.checksum_present()); + assert!(gre.key_present()); + assert!(gre.seqno_present()); + assert_eq!(None, gre.checksum()); + assert_eq!(Some(1), gre.key()); + assert_eq!(Some(2), gre.seqno()); + assert_eq!(12, gre.header_len()); + + gre.unset_key_present().unwrap(); + + assert!(!gre.checksum_present()); + assert!(!gre.key_present()); + assert!(gre.seqno_present()); + assert_eq!(None, gre.checksum()); + assert_eq!(None, gre.key()); + assert_eq!(Some(2), gre.seqno()); + assert_eq!(8, gre.header_len()); + + gre.unset_seqno_present().unwrap(); + + assert!(!gre.checksum_present()); + assert!(!gre.key_present()); + assert!(!gre.seqno_present()); + assert_eq!(None, gre.checksum()); + assert_eq!(None, gre.key()); + assert_eq!(None, gre.seqno()); + assert_eq!(4, gre.header_len()); + } + + #[capsule::test] + fn parse_non_gre_packet() { + let packet = Mbuf::from_bytes(&TCP4_PACKET).unwrap(); + let ethernet = packet.parse::().unwrap(); + let ip4 = ethernet.parse::().unwrap(); + + assert!(ip4.parse::>().is_err()); + } +} diff --git a/core/src/packets/icmp/v4/mod.rs b/core/src/packets/icmp/v4/mod.rs index ab725cc..21fc851 100644 --- a/core/src/packets/icmp/v4/mod.rs +++ b/core/src/packets/icmp/v4/mod.rs @@ -120,7 +120,7 @@ impl Icmpv4 { if let Ok(data) = self.mbuf().read_data_slice(self.offset(), self.len()) { let data = unsafe { data.as_ref() }; - let checksum = checksum::compute(0, data); + let checksum = checksum::ones_complement(0, data); self.header_mut().checksum = checksum.into(); } else { // we are reading till the end of buffer, should never run out diff --git a/core/src/packets/icmp/v6/mod.rs b/core/src/packets/icmp/v6/mod.rs index a2b7e96..9b07ec7 100644 --- a/core/src/packets/icmp/v6/mod.rs +++ b/core/src/packets/icmp/v6/mod.rs @@ -129,7 +129,7 @@ impl Icmpv6 { .envelope() .pseudo_header(data.len() as u16, ProtocolNumbers::Icmpv6) .sum(); - let checksum = checksum::compute(pseudo_header_sum, data); + let checksum = checksum::ones_complement(pseudo_header_sum, data); self.header_mut().checksum = checksum.into(); } else { // we are reading till the end of buffer, should never run out diff --git a/core/src/packets/ip/mod.rs b/core/src/packets/ip/mod.rs index e710ac2..1d67878 100644 --- a/core/src/packets/ip/mod.rs +++ b/core/src/packets/ip/mod.rs @@ -79,6 +79,9 @@ pub mod ProtocolNumbers { /// Internet Control Message Protocol for IPv6. pub const Icmpv6: ProtocolNumber = ProtocolNumber(0x3A); + + /// Generic Routing Encapsulation. + pub const Gre: ProtocolNumber = ProtocolNumber(0x2F); } impl fmt::Display for ProtocolNumber { @@ -95,6 +98,7 @@ impl fmt::Display for ProtocolNumber { ProtocolNumbers::Ipv6Frag => "IPv6 Frag".to_string(), ProtocolNumbers::Icmpv4 => "ICMPv4".to_string(), ProtocolNumbers::Icmpv6 => "ICMPv6".to_string(), + ProtocolNumbers::Gre => "GRE".to_string(), _ => format!("0x{:02x}", self.0), } ) @@ -287,6 +291,7 @@ mod tests { assert_eq!("IPv6 Frag", ProtocolNumbers::Ipv6Frag.to_string()); assert_eq!("ICMPv4", ProtocolNumbers::Icmpv4.to_string()); assert_eq!("ICMPv6", ProtocolNumbers::Icmpv6.to_string()); + assert_eq!("GRE", ProtocolNumbers::Gre.to_string()); assert_eq!("0x00", ProtocolNumber::new(0).to_string()); } } diff --git a/core/src/packets/ip/tunnels/ip6in4.rs b/core/src/packets/ip/tunnels/ip6in4.rs index bb108b8..69926f5 100644 --- a/core/src/packets/ip/tunnels/ip6in4.rs +++ b/core/src/packets/ip/tunnels/ip6in4.rs @@ -33,6 +33,7 @@ use std::marker::PhantomData; /// and routers that need to interoperate with IPv4 hosts and utilize IPv4 /// routing infrastructures. /// +/// ``` /// +-------------+ /// | IPv4 | /// | Header | @@ -48,18 +49,19 @@ use std::marker::PhantomData; /// ~ Data ~ ~ Data ~ /// | | | | /// +-------------+ +-------------+ +/// ``` /// /// [IPv6]: Ipv6 /// [IPv4]: Ipv4 /// [IETF RFC 4213]: https://datatracker.ietf.org/doc/html/rfc4213 #[derive(Debug)] -pub struct Ip6in4 { - _phantom: PhantomData, +pub struct Ip6in4 { + _phantom: PhantomData, } -impl Tunnel for Ip6in4 { - type Payload = Ipv6; - type Delivery = Ipv4; +impl Tunnel for Ip6in4 { + type Payload = Ipv6; + type Delivery = Ipv4; /// Encapsulates the existing IPv6 packet by prepending an outer IPv4 /// packet. @@ -131,7 +133,9 @@ mod tests { let delivery = ip6.encap::().unwrap(); assert_eq!(5, delivery.dscp()); assert_eq!(1, delivery.ecn()); - assert_eq!(payload_len + 20, delivery.len()); + + // check payload matches original packet length + assert_eq!(payload_len, delivery.payload_len()); } #[capsule::test] diff --git a/core/src/packets/ip/tunnels/ipip.rs b/core/src/packets/ip/tunnels/ipip.rs index 8766742..ce19ea7 100644 --- a/core/src/packets/ip/tunnels/ipip.rs +++ b/core/src/packets/ip/tunnels/ipip.rs @@ -31,6 +31,7 @@ use std::marker::PhantomData; /// IP in IP tunnel connects two separate IPv4 networks. an outer IP header /// is inserted before the datagram's existing IP header, as follows: /// +/// ``` /// +---------------------------+ /// | | /// | Outer IP Header | @@ -46,19 +47,20 @@ use std::marker::PhantomData; /// | | | | /// | | | | /// +---------------------------+ +---------------------------+ +/// ``` /// /// This tunnel only supports unicast packets. /// /// [IPv4]: Ipv4 /// [IETF RFC 2003]: https://datatracker.ietf.org/doc/html/rfc2003 #[derive(Debug)] -pub struct IpIp { - _phantom: PhantomData, +pub struct IpIp { + _phantom: PhantomData, } -impl Tunnel for IpIp { - type Payload = Ipv4; - type Delivery = Ipv4; +impl Tunnel for IpIp { + type Payload = Ipv4; + type Delivery = Ipv4; /// Encapsulates the existing IPv4 packet by prepending an outer IPv4 /// packet. @@ -137,7 +139,9 @@ mod tests { assert_eq!(5, delivery.dscp()); assert_eq!(1, delivery.ecn()); assert!(delivery.dont_fragment()); - assert_eq!(payload_len + 20, delivery.len()); + + // check payload matches original packet length + assert_eq!(payload_len, delivery.payload_len()); } #[capsule::test] diff --git a/core/src/packets/ip/v4.rs b/core/src/packets/ip/v4.rs index 95f897a..ce678d0 100644 --- a/core/src/packets/ip/v4.rs +++ b/core/src/packets/ip/v4.rs @@ -325,7 +325,7 @@ impl Ipv4 { if let Ok(data) = self.mbuf().read_data_slice(self.offset, self.header_len()) { let data = unsafe { data.as_ref() }; - let checksum = checksum::compute(0, data); + let checksum = checksum::ones_complement(0, data); self.set_checksum(checksum); } else { // we are reading the entire header, should never run out diff --git a/core/src/packets/mod.rs b/core/src/packets/mod.rs index f93d061..78c061e 100644 --- a/core/src/packets/mod.rs +++ b/core/src/packets/mod.rs @@ -21,6 +21,7 @@ pub mod arp; pub mod checksum; pub mod ethernet; +pub mod gre; pub mod icmp; pub mod ip; mod mbuf; diff --git a/core/src/packets/size_of.rs b/core/src/packets/size_of.rs index f7a6a30..989621d 100644 --- a/core/src/packets/size_of.rs +++ b/core/src/packets/size_of.rs @@ -17,6 +17,7 @@ */ use crate::net::MacAddr; +use crate::packets::types::{u16be, u32be}; use std::mem; use std::net::{Ipv4Addr, Ipv6Addr}; @@ -54,6 +55,18 @@ impl SizeOf for u8 { } } +impl SizeOf for u16be { + fn size_of() -> usize { + mem::size_of::() + } +} + +impl SizeOf for u32be { + fn size_of() -> usize { + mem::size_of::() + } +} + impl SizeOf for [u8; 2] { fn size_of() -> usize { mem::size_of::<[u8; 2]>() diff --git a/core/src/packets/tcp.rs b/core/src/packets/tcp.rs index 7391e56..2a6c51b 100644 --- a/core/src/packets/tcp.rs +++ b/core/src/packets/tcp.rs @@ -470,7 +470,7 @@ impl Tcp { .envelope() .pseudo_header(data.len() as u16, ProtocolNumbers::Tcp) .sum(); - let checksum = checksum::compute(pseudo_header_sum, data); + let checksum = checksum::ones_complement(pseudo_header_sum, data); self.set_checksum(checksum); } else { // we are reading till the end of buffer, should never run out diff --git a/core/src/packets/udp.rs b/core/src/packets/udp.rs index 42512e8..cc6f1fd 100644 --- a/core/src/packets/udp.rs +++ b/core/src/packets/udp.rs @@ -225,7 +225,7 @@ impl Udp { .envelope() .pseudo_header(data.len() as u16, ProtocolNumbers::Udp) .sum(); - let checksum = checksum::compute(pseudo_header_sum, data); + let checksum = checksum::ones_complement(pseudo_header_sum, data); self.set_checksum(checksum); } else { // we are reading till the end of buffer, should never run out diff --git a/core/src/testils/byte_arrays.rs b/core/src/testils/byte_arrays.rs index 4b3b09b..629a5e3 100644 --- a/core/src/testils/byte_arrays.rs +++ b/core/src/testils/byte_arrays.rs @@ -461,3 +461,37 @@ pub const IP6IN4_PACKET: [u8; 134] = [ 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 ]; + +/// GRE packet. +#[rustfmt::skip] +pub const IP4GRE_PACKET: [u8; 138] = [ +// ethernet header + 0xc2, 0x01, 0x57, 0x75, 0x00, 0x00, + 0xc2, 0x00, 0x57, 0x75, 0x00, 0x00, + 0x08, 0x00, +// delivery IPv4 header + 0x45, 0x00, + 0x00, 0x7c, + 0x00, 0x0a, 0x00, 0x00, + 0xff, 0x2f, 0xa7, 0x46, + 0x0a, 0x00, 0x00, 0x01, + 0x0a, 0x00, 0x00, 0x02, +// delivery GRE header + 0x00, 0x00, + 0x08, 0x00, +// payload IPv4 header + 0x45, 0x00, + 0x00, 0x64, + 0x00, 0x0a, 0x00, 0x00, + 0xff, 0x01, 0xb5, 0x89, + 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, +// payload ICMPv4 header + 0x08, 0x00, + 0xbf, 0xd4, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbe, 0x70, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, + 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd, 0xab, 0xcd +];