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)]
13pub struct ParsedPacket {
15 pub link_info: LinkInfo,
17 pub net_info: NetInfo,
19 pub transport_info: TransportInfo,
21}
22
23impl ParsedPacket {
24 #[must_use]
25 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 pub fn bytes_count(&self) -> usize {
58 self.link_info.bytes.saturating_add(self.net_info.bytes)
59 }
60}
61
62fn 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
111fn 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
186fn 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}