Skip to content

Commit

Permalink
Merge pull request #8 from 0xphen/feat/deep-parser
Browse files Browse the repository at this point in the history
Feat/deep parser
  • Loading branch information
0xphen authored Oct 29, 2023
2 parents c2050e5 + c7acf30 commit 5b611cc
Show file tree
Hide file tree
Showing 19 changed files with 831 additions and 557 deletions.
3 changes: 0 additions & 3 deletions src/aggregator/mod.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
pub mod aggregator;
pub mod parsers;

// #[cfg(test)]
Expand Down
65 changes: 65 additions & 0 deletions src/parsers/definitions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use super::{
errors::ParserError, ethernet_frame::EthernetFrame, icmp::IcmpPacket, ipv4::Ipv4Packet,
ipv6::Ipv6Packet, tcp::TcpSegment, udp::UdpDatagram,
};

#[derive(Debug, PartialEq)]
pub enum IPType {
TCP,
UDP,
ICMP,
Other(u8),
}

impl From<u8> for IPType {
fn from(byte: u8) -> IPType {
match byte {
1 => IPType::ICMP,
6 => IPType::TCP,
17 => IPType::UDP,
_ => IPType::Other(byte),
}
}
}

#[derive(Debug, PartialEq)]
pub enum EtherType {
IPv4,
IPv6,
ARP,
Other(u16),
}

impl From<u16> for EtherType {
fn from(raw: u16) -> Self {
match raw {
0x0800 => Self::IPv4,
0x86DD => Self::IPv6,
0x0806 => Self::ARP,
other => Self::Other(other),
}
}
}

/// `DeepParser` is a trait intended for objects representing network packets.
/// It provides a method to parse the encapsulated data and return it in a structured format.
pub trait DeepParser {
/// Parses the packet data to extract further encapsulated layers.
///
/// # Returns
/// * `Ok(LayeredData)` if the parsing is successful and the data is encapsulated within.
/// * `Err(ParserError)` if there is an issue with parsing.
fn parse_next_layer(self) -> Result<LayeredData, ParserError>;
}

#[derive(Debug, PartialEq)]
pub enum LayeredData {
Payload(Vec<u8>),
IcmpData(IcmpPacket),
UdpData(UdpDatagram),
TcpData(TcpSegment),
Ipv4Data(Ipv4Packet),
Ipv6Data(Ipv6Packet),
EthernetFrameData(EthernetFrame),
Empty,
}
9 changes: 9 additions & 0 deletions src/parsers/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,13 @@ pub enum ParserError {

#[error("Invalid packet/segment length")]
InvalidLength,

#[error("Expected payload data")]
InvalidPayload,

#[error("Unknown IP type `{0}`")]
UnknownIPType(u8),

#[error("Unknown ether type type")]
UnSupportedEtherType,
}
167 changes: 70 additions & 97 deletions src/parsers/ethernet_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

use super::{
constants,
definitions::{DeepParser, EtherType, LayeredData},
errors::{ErrorSource, ParserError},
utils::read_arbitrary_length,
ipv4::Ipv4Packet,
ipv6::Ipv6Packet,
utils::{read_arbitrary_length, read_u16, read_u32},
};

use std::fmt;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::io::{Cursor, Seek, SeekFrom};

const MAC_ADDRESS_BYTES: usize = 6;

Expand Down Expand Up @@ -59,25 +62,6 @@ impl fmt::Display for MacAddress {
}
}

#[derive(Debug, PartialEq)]
pub enum EtherType {
IPv4,
IPv6,
ARP,
Other(u16),
}

impl From<u16> for EtherType {
fn from(raw: u16) -> Self {
match raw {
0x0800 => Self::IPv4,
0x86DD => Self::IPv6,
0x0806 => Self::ARP,
other => Self::Other(other),
}
}
}

// Constants representing various parameters and offsets within an Ethernet frame.
// These are used for parsing the frame correctly.
const TPID_VLAN: u32 = 33024; // [0x81, 0x00];
Expand All @@ -86,35 +70,44 @@ const BITMASK_Q_TAG: u32 = 0xFFFFFFFF;
const OFFSET_MAC_DEST: usize = 0;
const OFFSET_MAC_SRC: usize = 6;

/// An Ethernet frame representation.
///
/// The `EthernetFrame` struct models the structure of an Ethernet II frame,
/// which is the most commonly used Ethernet frame type. The Ethernet frame
/// carries data internally for protocols such as IP and ARP.
///
/// An Ethernet frame comprises several fields including destination and
/// source MAC addresses, an optional VLAN tag (Q-tag), an EtherType field
/// indicating the upper-layer protocol, the payload, and a Frame Check Sequence (FCS).
#[derive(Debug, PartialEq)]
/// Represents the header of an Ethernet frame.
///
/// # Fields
/// Ethernet frames begin with a header that contains the essential fields
/// for network communication. This struct captures the key components of
/// that header, specifically catering to Ethernet II framing.
pub struct EthernetFrameHeader {
/// The MAC (Media Access Control) address of the intended recipient of the packet.
pub mac_destination: MacAddress,

/// The MAC address of the sender of the packet.
pub mac_source: MacAddress,

/// An optional 802.1Q tag specifying VLAN membership and priority information.
/// It's present in VLAN-tagged frames, otherwise `None`.
pub q_tag: Option<u32>,

/// The EtherType field indicating the protocol encapsulated in the payload of the frame.
/// Common values indicate IPv4, IPv6, ARP, etc.
pub ether_type: EtherType,
}

/// Represents a complete Ethernet frame.
///
/// * `mac_destination`: The MAC address of the receiving device.
/// * `mac_source`: The MAC address of the sending device.
/// * `q_tag`: An optional Q-tag field used in VLANs. If the frame is VLAN-tagged,
/// this field will include the Tag Protocol Identifier (TPID) and the Tag Control Information (TCI).
/// * `ether_type`: A field that indicates the protocol of the encapsulated payload.
/// For example, `0x0800` indicates IPv4.
/// * `payload`: The encapsulated data within the Ethernet frame. Its content and interpretation
/// are determined by the `ether_type`.
/// Note: The Frame Check Sequence (FCS) is not represented here as
/// it's used only for the frame's integrity check.
/// This structure encompasses the entire Ethernet frame, providing access to
/// both the header and the payload of the frame. It is fundamental for
/// handling network data at a low level, allowing for the parsing, creation,
/// and manipulation of Ethernet frames for various networking operations.
#[derive(Debug, PartialEq)]
pub struct EthernetFrame {
pub mac_destination: MacAddress, // Destination MAC address
pub mac_source: MacAddress, // Source MAC address
pub q_tag: Option<u32>, // Optional Q-tag for VLAN-tagged frames
pub ether_type: EtherType, // EtherType indicating the upper-layer protocol
pub payload: Vec<u8>, // Frame's payload/data
/// The header of the Ethernet frame, containing all the relevant
/// information for routing and type of content.
pub header: EthernetFrameHeader,

/// The actual payload of the Ethernet frame encapsulated as `LayeredData`.
/// This can represent various forms of data as structured in different
/// network layers, depending on the EtherType specified in the header.
pub data: Box<LayeredData>,
}

impl EthernetFrame {
Expand Down Expand Up @@ -168,21 +161,23 @@ impl EthernetFrame {
source: e,
})?;

let q_tag_ether_bytes = Self::read_u32(&mut cursor, "QTAG_&_ETHERTYPE")?;
let q_tag_ether_bytes = read_u32(&mut cursor, "QTAG_&_ETHERTYPE")?;

let (q_tag, ether_type) =
Self::parse_vlan_tag_and_ether_type(&mut cursor, q_tag_ether_bytes)?;

let fcs_offset = frame.len() - 4;
let payload_size = fcs_offset as u64 - cursor.position();
let payload = read_arbitrary_length(&mut cursor, payload_size as usize, "Payload")?;
let data_size = fcs_offset as u64 - cursor.position();
let data = read_arbitrary_length(&mut cursor, data_size as usize, "Payload")?;

Ok(EthernetFrame {
mac_destination: MacAddress::from_bytes(mac_destination_bytes),
mac_source: MacAddress::from_bytes(mac_source_bytes),
q_tag,
ether_type: EtherType::from(ether_type),
payload,
header: EthernetFrameHeader {
mac_destination: MacAddress::from_bytes(mac_destination_bytes),
mac_source: MacAddress::from_bytes(mac_source_bytes),
q_tag,
ether_type: EtherType::from(ether_type),
},
data: Box::new(LayeredData::Payload(data)),
})
}

Expand Down Expand Up @@ -215,7 +210,7 @@ impl EthernetFrame {
) -> Result<(Option<u32>, u16), ParserError> {
let (q_tag, ether_type) = match q_tag_ether_bytes >> 16 {
TPID_VLAN => {
let e_t = Self::read_u16(cursor, "Ether Type")?;
let e_t = read_u16(cursor, "Ether Type")?;
(Some(q_tag_ether_bytes & BITMASK_Q_TAG), e_t)
}
_ => {
Expand Down Expand Up @@ -266,50 +261,28 @@ impl EthernetFrame {
string: "Src/Dest MAC Address".to_string(),
})
}
}

/// Reads a 128-bit unsigned integer from the current position of the cursor in big-endian format.
///
/// # Arguments
///
/// * `cursor` - A cursor over the byte slice from which the data will be read.
/// * `field` - A description of the data field, used for error messages.
///
/// # Returns
///
/// Returns a `Result` containing the `u128` value if successful, otherwise a `ParserError::ExtractionError`.
fn read_u32(cursor: &mut Cursor<&[u8]>, field: &str) -> Result<u32, ParserError> {
let mut buffer: [u8; 4] = Default::default();

cursor
.read_exact(&mut buffer)
.map_err(|e| ParserError::ExtractionError {
string: field.to_string(),
source: ErrorSource::Io(e),
})?;

Ok(u32::from_be_bytes(buffer))
}

/// Reads a 16-bit unsigned integer from the cursor's current position in big-endian format.
///
/// # Arguments
///
/// * `cursor` - A reference to the cursor in the byte slice being read.
/// * `field` - A descriptor for the data field, used in error messaging.
///
/// # Returns
///
/// A `Result` containing the `u16` value if successful, or a `ParserError::ExtractionError` on failure.
fn read_u16(cursor: &mut Cursor<&[u8]>, field: &str) -> Result<u16, ParserError> {
let mut buffer: [u8; 2] = Default::default();
impl DeepParser for EthernetFrame {
fn parse_next_layer(mut self) -> Result<LayeredData, ParserError> {
let data = match &*self.data {
LayeredData::Payload(data) => data,
_ => return Err(ParserError::InvalidPayload),
};

cursor
.read_exact(&mut buffer)
.map_err(|e| ParserError::ExtractionError {
string: field.to_string(),
source: ErrorSource::Io(e),
})?;
let layered_data = match self.header.ether_type {
EtherType::IPv4 => {
let ipv4_packet = Ipv4Packet::from_bytes(data)?;
ipv4_packet.parse_next_layer()?
}
EtherType::IPv6 => {
let ipv6_packet = Ipv6Packet::from_bytes(data)?;
ipv6_packet.parse_next_layer()?
}
_ => return Err(ParserError::UnSupportedEtherType),
};

Ok(u16::from_be_bytes(buffer))
*self.data = layered_data;
Ok(LayeredData::EthernetFrameData(self))
}
}
28 changes: 21 additions & 7 deletions src/parsers/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

use super::{
definitions::{DeepParser, LayeredData},
errors::ParserError,
utils::{read_arbitrary_length, read_u64},
};
Expand All @@ -21,12 +22,17 @@ use std::io::Cursor;
const DATA_OFFSET_OR_MIN_SIZE: usize = 8;

#[derive(Debug, PartialEq)]
pub struct IcmpPacket {
pub struct IcmpPacketHeader {
pub icmp_type: u8, // Type of ICMP message.
pub icmp_code: u8, // Subtype to further specify the message.
pub checksum: u16, // Error-checking data calculated from the ICMP message.
pub rest_of_header: u32, // Remaining data in the header (depends on type and code).
pub data: Vec<u8>, // Payload or message associated with the ICMP packet.
}

#[derive(Debug, PartialEq)]
pub struct IcmpPacket {
pub header: IcmpPacketHeader,
pub data: Box<LayeredData>,
}

impl IcmpPacket {
Expand Down Expand Up @@ -56,11 +62,13 @@ impl IcmpPacket {
read_arbitrary_length(&mut cursor, packets.len() - DATA_OFFSET_OR_MIN_SIZE, "Data")?;

Ok(IcmpPacket {
icmp_type,
icmp_code,
checksum,
rest_of_header,
data,
header: IcmpPacketHeader {
icmp_type,
icmp_code,
checksum,
rest_of_header,
},
data: Box::new(LayeredData::Payload(data)),
})
}

Expand Down Expand Up @@ -93,3 +101,9 @@ impl IcmpPacket {
Ok((icmp_type, icmp_code, checksum, rest_of_header))
}
}

impl DeepParser for IcmpPacket {
fn parse_next_layer(self) -> Result<LayeredData, ParserError> {
Ok(LayeredData::IcmpData(self))
}
}
Loading

0 comments on commit 5b611cc

Please sign in to comment.