Skip to main content

sniffnet_packet_parser/
packet.rs

1use std::net::IpAddr;
2
3use etherparse::{ArpHardwareId, EtherType, LinkHeader, NetHeaders, TransportHeader, VlanHeader};
4
5use crate::arp_type::ArpType;
6use crate::headers::{LinkInfo, NetInfo, TransportInfo, get_sniffable_headers};
7use crate::icmp_type::{IcmpTypeV4, IcmpTypeV6};
8use crate::igmp_type::IgmpType;
9use crate::link_type::LinkType;
10use crate::protocol::Protocol;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13/// Parsed network packet with info extracted from its headers.
14pub struct ParsedPacket {
15    /// Info extracted from the data link layer header.
16    pub link_info: LinkInfo,
17    /// Info extracted from the network layer header.
18    pub net_info: NetInfo,
19    /// Info extracted from the transport layer header.
20    pub transport_info: TransportInfo,
21}
22
23impl ParsedPacket {
24    #[must_use]
25    /// Parse one raw network packet from its link type and wire bytes.
26    pub fn from_bytes(bytes: &[u8], link_type: LinkType) -> Option<ParsedPacket> {
27        let headers = get_sniffable_headers(bytes, link_type)?;
28
29        let vlan_header = headers.vlan();
30        let link_info = analyze_link_header(headers.link, vlan_header);
31
32        let is_arp = matches!(&headers.net, Some(NetHeaders::Arp(_)));
33
34        let net_info = analyze_net_header(headers.net)?;
35
36        let transport_info = if is_arp {
37            Some(TransportInfo {
38                src_port: None,
39                dst_port: None,
40                protocol: Protocol::Arp,
41                icmp_type: None,
42                igmp_type: None,
43            })
44        } else {
45            analyze_transport_header(headers.transport)
46        }?;
47
48        Some(ParsedPacket {
49            link_info,
50            net_info,
51            transport_info,
52        })
53    }
54
55    #[must_use]
56    /// Returns the total number of bytes in the packet.
57    pub fn bytes_count(&self) -> usize {
58        self.link_info.bytes.saturating_add(self.net_info.bytes)
59    }
60}
61
62/// This function extracts info from the data link layer header passed as parameter.
63fn analyze_link_header(
64    link_header: Option<LinkHeader>,
65    vlan_header: Option<VlanHeader>,
66) -> LinkInfo {
67    let (vlan_id, vlan_bytes) = match vlan_header {
68        Some(VlanHeader::Single(single)) => (Some(single.vlan_id.value()), 4),
69        Some(VlanHeader::Double(double)) => (Some(double.outer.vlan_id.value()), 8),
70        None => (None, 0),
71    };
72
73    match link_header {
74        Some(LinkHeader::Ethernet2(header)) => {
75            let src_mac = Some(header.source);
76            let dst_mac = Some(header.destination);
77            let bytes = vlan_bytes + 14;
78            LinkInfo {
79                src_mac,
80                dst_mac,
81                vlan_id,
82                bytes,
83            }
84        }
85        Some(LinkHeader::LinuxSll(header)) => {
86            let src_mac: Option<[u8; 6]> = if header.sender_address_valid_length == 6
87                && header.arp_hrd_type == ArpHardwareId::ETHERNET
88                && let Ok(sender) = header.sender_address[0..6].try_into()
89            {
90                Some(sender)
91            } else {
92                None
93            };
94            let bytes = vlan_bytes + 16;
95            LinkInfo {
96                src_mac,
97                dst_mac: None,
98                vlan_id,
99                bytes,
100            }
101        }
102        None => LinkInfo {
103            src_mac: None,
104            dst_mac: None,
105            vlan_id,
106            bytes: vlan_bytes,
107        },
108    }
109}
110
111/// This function extracts info from the network layer header passed as parameter.
112/// Returns `None` if packet has to be skipped.
113fn analyze_net_header(network_header: Option<NetHeaders>) -> Option<NetInfo> {
114    match network_header {
115        Some(NetHeaders::Ipv4(ipv4header, _)) => {
116            let src_ip = IpAddr::from(ipv4header.source);
117            let dst_ip = IpAddr::from(ipv4header.destination);
118            let bytes = usize::from(ipv4header.total_len);
119            Some(NetInfo {
120                src_ip,
121                dst_ip,
122                arp_type: None,
123                bytes,
124            })
125        }
126        Some(NetHeaders::Ipv6(ipv6header, _)) => {
127            let src_ip = IpAddr::from(ipv6header.source);
128            let dst_ip = IpAddr::from(ipv6header.destination);
129            let bytes = usize::from(ipv6header.payload_length) + 40;
130            Some(NetInfo {
131                src_ip,
132                dst_ip,
133                arp_type: None,
134                bytes,
135            })
136        }
137        Some(NetHeaders::Arp(arp_packet)) => {
138            let (src_ip, dst_ip) = match arp_packet.proto_addr_type {
139                EtherType::IPV4 => {
140                    let src_ip =
141                        match TryInto::<[u8; 4]>::try_into(arp_packet.sender_protocol_addr()) {
142                            Ok(source) => IpAddr::from(source),
143                            Err(_) => return None,
144                        };
145                    let dst_ip =
146                        match TryInto::<[u8; 4]>::try_into(arp_packet.target_protocol_addr()) {
147                            Ok(destination) => IpAddr::from(destination),
148                            Err(_) => return None,
149                        };
150                    (src_ip, dst_ip)
151                }
152                EtherType::IPV6 => {
153                    let src_ip =
154                        match TryInto::<[u8; 16]>::try_into(arp_packet.sender_protocol_addr()) {
155                            Ok(source) => IpAddr::from(source),
156                            Err(_) => return None,
157                        };
158                    let dst_ip =
159                        match TryInto::<[u8; 16]>::try_into(arp_packet.target_protocol_addr()) {
160                            Ok(destination) => IpAddr::from(destination),
161                            Err(_) => return None,
162                        };
163                    (src_ip, dst_ip)
164                }
165                _ => return None,
166            };
167            let bytes = arp_packet.packet_len();
168            let arp_type = Some(ArpType::from_etherparse(arp_packet.operation));
169            Some(NetInfo {
170                src_ip,
171                dst_ip,
172                arp_type,
173                bytes,
174            })
175        }
176        None => None,
177    }
178}
179
180/// This function extracts info from the transport layer header passed as parameter.
181/// Returns `None` if packet has to be skipped.
182fn analyze_transport_header(transport_header: Option<TransportHeader>) -> Option<TransportInfo> {
183    match transport_header {
184        Some(TransportHeader::Udp(udp_header)) => {
185            let src_port = Some(udp_header.source_port);
186            let dst_port = Some(udp_header.destination_port);
187            let protocol = Protocol::Udp;
188            Some(TransportInfo {
189                src_port,
190                dst_port,
191                protocol,
192                icmp_type: None,
193                igmp_type: None,
194            })
195        }
196        Some(TransportHeader::Tcp(tcp_header)) => {
197            let src_port = Some(tcp_header.source_port);
198            let dst_port = Some(tcp_header.destination_port);
199            let protocol = Protocol::Tcp;
200            Some(TransportInfo {
201                src_port,
202                dst_port,
203                protocol,
204                icmp_type: None,
205                igmp_type: None,
206            })
207        }
208        Some(TransportHeader::Icmpv4(icmpv4_header)) => {
209            let protocol = Protocol::Icmpv4;
210            let icmp_type = Some(IcmpTypeV4::from_etherparse(&icmpv4_header.icmp_type));
211            Some(TransportInfo {
212                src_port: None,
213                dst_port: None,
214                protocol,
215                icmp_type,
216                igmp_type: None,
217            })
218        }
219        Some(TransportHeader::Icmpv6(icmpv6_header)) => {
220            let protocol = Protocol::Icmpv6;
221            let icmp_type = Some(IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type));
222            Some(TransportInfo {
223                src_port: None,
224                dst_port: None,
225                protocol,
226                icmp_type,
227                igmp_type: None,
228            })
229        }
230        Some(TransportHeader::Igmp(igmp_header)) => {
231            let protocol = Protocol::Igmp;
232            let igmp_type = Some(IgmpType::from_etherparse(&igmp_header.igmp_type));
233            Some(TransportInfo {
234                src_port: None,
235                dst_port: None,
236                protocol,
237                icmp_type: None,
238                igmp_type,
239            })
240        }
241        None => None,
242    }
243}