Skip to main content

packet_parser/parse/data_link/
mod.rs

1// Copyright (c) 2024 Cyprien Avico avicocyprien@yahoo.com
2//
3// Licensed under the MIT License <LICENSE-MIT or http://opensource.org/licenses/MIT>.
4// This file may not be copied, modified, or distributed except according to those terms.
5
6// parsed_packet/data_link/mod.rs
7
8//! The `DataLink` module provides functionality to parse and analyze data link layer packets,
9//! specifically Ethernet frames. It extracts MAC addresses, Ethertype, and the payload from
10//! a raw byte slice.
11//!
12//! # Overview
13//!
14//! The `DataLink` structure represents an Ethernet frame with the following fields:
15//! - `destination_mac`: The destination MAC address of the packet as a string.
16//! - `source_mac`: The source MAC address of the packet as a string.
17//! - `ethertype`: The Ethertype value, which indicates the protocol used in the payload.
18//! - `payload`: The remaining packet data after the Ethernet header.
19//!
20//! This module includes:
21//! - A `TryFrom<&[u8]>` implementation to parse an Ethernet frame from a raw byte slice.
22//! - A validation step to ensure the packet length is sufficient before parsing.
23//!
24//! # Example
25//!
26//! ```rust
27//! use packet_parser::parse::data_link::DataLink;
28//!
29//! let raw_packet: [u8; 18] = [
30//!     0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Destination MAC
31//!     0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Source MAC
32//!     0x08, 0x00, // Ethertype (IPv4)
33//!     0x45, 0x00, 0x00, 0x54, // Payload (IPv4 Header fragment)
34//! ];
35//!
36//! let datalink = DataLink::try_from(raw_packet.as_ref()).expect("Failed to parse valid packet");
37//! println!("{:?}", datalink);
38//! ```
39//!
40//! # Errors
41//!
42//! The `TryFrom<&[u8]>` implementation can return a `DataLinkError` if:
43//! - The packet is too short to contain a valid Ethernet frame.
44//! - The MAC addresses or Ethertype are invalid.
45//!
46//! # See Also
47//! - [`MacAddress`]
48//! - [`Ethertype`]
49
50pub mod mac_addres;
51use mac_addres::MacAddress;
52use serde::Serialize;
53
54pub mod ethertype;
55pub mod vlan_tag;
56
57use crate::{
58    checks::data_link::validate_data_link_length, errors::data_link::DataLinkError,
59    parse::data_link::vlan_tag::VlanTag,
60};
61
62use ethertype::Ethertype;
63
64/// Represents a parsed Ethernet frame, containing source and destination MAC addresses,
65/// an Ethertype, and the payload.
66#[derive(Debug, Clone, Serialize, Eq)]
67pub struct DataLink<'a> {
68    /// The destination MAC address as a string.
69    pub destination_mac: String,
70    /// The source MAC address as a string.
71    pub source_mac: String,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub vlan: Option<VlanTag>,
74    /// The Ethertype of the packet, indicating the protocol in the payload.
75    pub ethertype: String,
76    /// The payload of the Ethernet frame.
77    #[serde(skip_serializing)]
78    pub payload: &'a [u8],
79}
80
81impl<'a> TryFrom<&'a [u8]> for DataLink<'a> {
82    type Error = DataLinkError;
83
84    fn try_from(packets: &'a [u8]) -> Result<Self, Self::Error> {
85        validate_data_link_length(packets)?;
86
87        let destination_mac = MacAddress::try_from(&packets[0..6])?;
88        let source_mac = MacAddress::try_from(&packets[6..12])?;
89
90        // EtherType brut (peut être 0x8100 pour VLAN)
91        let raw_ethertype = u16::from_be_bytes([packets[12], packets[13]]);
92
93        let mut vlan: Option<VlanTag> = None;
94        let ethertype: String;
95        let payload: &'a [u8];
96
97        if raw_ethertype == 0x8100 {
98            // On a un tag 802.1Q.
99            // Il nous faut au minimum 18 octets : header Ethernet + TCI + inner EtherType
100            if packets.len() < 18 {
101                return Err(DataLinkError::DataLinkTooShort(packets.len() as u8));
102            }
103
104            // TCI + inner EtherType : [14..18]
105            let vlan_tag = VlanTag::try_from(&packets[14..18])?;
106
107            ethertype = vlan_tag.inner_ethertype_name().to_string();
108            payload = &packets[18..];
109            vlan = Some(vlan_tag);
110        } else {
111            // Pas de VLAN
112            ethertype = Ethertype::from(raw_ethertype).name().to_string();
113            payload = &packets[14..];
114        }
115
116        Ok(DataLink {
117            destination_mac: destination_mac.display_with_oui(),
118            source_mac: source_mac.display_with_oui(),
119            vlan,
120            ethertype,
121            payload,
122        })
123    }
124}
125
126impl<'a> PartialEq for DataLink<'a> {
127    fn eq(&self, other: &Self) -> bool {
128        self.destination_mac == other.destination_mac
129            && self.source_mac == other.source_mac
130            && self.vlan == other.vlan
131            && self.ethertype == other.ethertype
132    }
133}
134
135use std::hash::{Hash, Hasher};
136
137impl<'a> Hash for DataLink<'a> {
138    fn hash<H: Hasher>(&self, state: &mut H) {
139        self.destination_mac.hash(state);
140        self.source_mac.hash(state);
141        self.vlan.hash(state);
142        self.ethertype.hash(state);
143    }
144}
145
146#[cfg(test)]
147mod tests {
148
149    use crate::errors::data_link::DataLinkError;
150    use crate::parse::data_link::DataLink;
151    use crate::parse::data_link::mac_addres::MacAddress;
152
153    #[test]
154    fn test_datalink_try_from_valid_packet() {
155        let raw_packet: [u8; 18] = [
156            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Destination MAC
157            0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Source MAC
158            0x08, 0x00, // Ethertype (IPv4)
159            0x45, 0x00, 0x00, 0x54, // Payload (IPv4 Header fragment)
160        ];
161
162        let datalink =
163            DataLink::try_from(raw_packet.as_ref()).expect("Failed to parse valid packet");
164
165        assert_eq!(
166            datalink.destination_mac,
167            MacAddress::try_from(&raw_packet[0..6])
168                .unwrap()
169                .display_with_oui()
170        );
171        assert_eq!(
172            datalink.source_mac,
173            MacAddress::try_from(&raw_packet[6..12])
174                .unwrap()
175                .display_with_oui()
176        );
177        assert_eq!(datalink.ethertype, "IPv4"); // IPv4 Ethertype
178    }
179
180    #[test]
181    fn test_datalink_try_from_invalid_length() {
182        let short_packet: [u8; 10] = [0x00; 10];
183
184        let result = DataLink::try_from(short_packet.as_ref());
185        assert!(matches!(result, Err(DataLinkError::DataLinkTooShort(_))));
186    }
187
188    #[test]
189    fn test_datalink_try_from_ethertype_parsing() {
190        let raw_packet: [u8; 18] = [
191            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Destination MAC
192            0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Source MAC
193            0x86, 0xDD, // Ethertype (IPv6)
194            0x60, 0x00, 0x00, 0x00, // IPv6 Header fragment
195        ];
196
197        let datalink = DataLink::try_from(raw_packet.as_ref()).unwrap();
198        assert_eq!(datalink.ethertype, "IPv6"); // IPv6 Ethertype
199    }
200
201    #[test]
202    fn test_datalink_try_from_ethertype_unknown() {
203        let raw_packet: [u8; 18] = [
204            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Destination MAC
205            0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Source MAC
206            0xAB, 0xCD, // Inconnu Ethertype
207            0x12, 0x34, 0x56, 0x78, // Payload quelconque
208        ];
209
210        let datalink = DataLink::try_from(raw_packet.as_ref()).unwrap();
211        assert_eq!(datalink.ethertype, "Unknown (0xABCD)"); // Ethertype inconnu, mais accepté
212    }
213    #[test]
214    fn test_datalink_try_from_empty_payload() {
215        let raw_packet: [u8; 14] = [
216            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Destination MAC
217            0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Source MAC
218            0xAB, 0xCD, // Inconnu Ethertype
219        ];
220
221        let datalink = DataLink::try_from(raw_packet.as_ref()).unwrap();
222        assert_eq!(datalink.ethertype, "Unknown (0xABCD)"); // Ethertype inconnu, mais accepté
223    }
224
225    #[test]
226    fn test_datalink_try_from_vlan_tagged() {
227        // Dest MAC, Src MAC, TPID=0x8100, TCI (PCP=0, DEI=0, VLAN 10), inner EtherType=0x0800 (IPv4)
228        let raw_packet: [u8; 22] = [
229            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // Dest
230            0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, // Src
231            0x81, 0x00, // TPID 802.1Q
232            0x00, 0x0A, // TCI : VLAN 10
233            0x08, 0x00, // Inner EtherType : IPv4
234            0x45, 0x00, 0x00, 0x54, // Début header IPv4
235        ];
236
237        let datalink = DataLink::try_from(raw_packet.as_ref()).unwrap();
238
239        assert_eq!(datalink.ethertype, "IPv4");
240        assert!(datalink.vlan.is_some());
241        let vlan = datalink.vlan.unwrap();
242        assert_eq!(vlan.id, 10);
243        assert_eq!(datalink.payload, &raw_packet[18..]);
244    }
245}