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            let ether_type = EtherType::IPV4.0;
120            Some(NetInfo {
121                src_ip,
122                dst_ip,
123                arp_type: None,
124                ether_type,
125                bytes,
126            })
127        }
128        Some(NetHeaders::Ipv6(ipv6header, _)) => {
129            let src_ip = IpAddr::from(ipv6header.source);
130            let dst_ip = IpAddr::from(ipv6header.destination);
131            let bytes = usize::from(ipv6header.payload_length) + 40;
132            let ether_type = EtherType::IPV6.0;
133            Some(NetInfo {
134                src_ip,
135                dst_ip,
136                arp_type: None,
137                ether_type,
138                bytes,
139            })
140        }
141        Some(NetHeaders::Arp(arp_packet)) => {
142            let (src_ip, dst_ip) = match arp_packet.proto_addr_type {
143                EtherType::IPV4 => {
144                    let src_ip =
145                        match TryInto::<[u8; 4]>::try_into(arp_packet.sender_protocol_addr()) {
146                            Ok(source) => IpAddr::from(source),
147                            Err(_) => return None,
148                        };
149                    let dst_ip =
150                        match TryInto::<[u8; 4]>::try_into(arp_packet.target_protocol_addr()) {
151                            Ok(destination) => IpAddr::from(destination),
152                            Err(_) => return None,
153                        };
154                    (src_ip, dst_ip)
155                }
156                EtherType::IPV6 => {
157                    let src_ip =
158                        match TryInto::<[u8; 16]>::try_into(arp_packet.sender_protocol_addr()) {
159                            Ok(source) => IpAddr::from(source),
160                            Err(_) => return None,
161                        };
162                    let dst_ip =
163                        match TryInto::<[u8; 16]>::try_into(arp_packet.target_protocol_addr()) {
164                            Ok(destination) => IpAddr::from(destination),
165                            Err(_) => return None,
166                        };
167                    (src_ip, dst_ip)
168                }
169                _ => return None,
170            };
171            let bytes = arp_packet.packet_len();
172            let arp_type = Some(ArpType::from_etherparse(arp_packet.operation));
173            let ether_type = EtherType::ARP.0;
174            Some(NetInfo {
175                src_ip,
176                dst_ip,
177                arp_type,
178                ether_type,
179                bytes,
180            })
181        }
182        None => None,
183    }
184}
185
186/// This function extracts info from the transport layer header passed as parameter.
187/// Returns `None` if packet has to be skipped.
188fn analyze_transport_header(transport_header: Option<TransportHeader>) -> Option<TransportInfo> {
189    match transport_header {
190        Some(TransportHeader::Udp(udp_header)) => {
191            let src_port = Some(udp_header.source_port);
192            let dst_port = Some(udp_header.destination_port);
193            let protocol = Protocol::Udp;
194            Some(TransportInfo {
195                src_port,
196                dst_port,
197                protocol,
198                icmp_type: None,
199                igmp_type: None,
200            })
201        }
202        Some(TransportHeader::Tcp(tcp_header)) => {
203            let src_port = Some(tcp_header.source_port);
204            let dst_port = Some(tcp_header.destination_port);
205            let protocol = Protocol::Tcp;
206            Some(TransportInfo {
207                src_port,
208                dst_port,
209                protocol,
210                icmp_type: None,
211                igmp_type: None,
212            })
213        }
214        Some(TransportHeader::Icmpv4(icmpv4_header)) => {
215            let protocol = Protocol::Icmpv4;
216            let icmp_type = Some(IcmpTypeV4::from_etherparse(&icmpv4_header.icmp_type));
217            Some(TransportInfo {
218                src_port: None,
219                dst_port: None,
220                protocol,
221                icmp_type,
222                igmp_type: None,
223            })
224        }
225        Some(TransportHeader::Icmpv6(icmpv6_header)) => {
226            let protocol = Protocol::Icmpv6;
227            let icmp_type = Some(IcmpTypeV6::from_etherparse(&icmpv6_header.icmp_type));
228            Some(TransportInfo {
229                src_port: None,
230                dst_port: None,
231                protocol,
232                icmp_type,
233                igmp_type: None,
234            })
235        }
236        Some(TransportHeader::Igmp(igmp_header)) => {
237            let protocol = Protocol::Igmp;
238            let igmp_type = Some(IgmpType::from_etherparse(&igmp_header.igmp_type));
239            Some(TransportInfo {
240                src_port: None,
241                dst_port: None,
242                protocol,
243                icmp_type: None,
244                igmp_type,
245            })
246        }
247        None => None,
248    }
249}