Skip to main content

sniffnet_packet_parser/
packet.rs

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