stackforge_core/layer/icmp/
extensions.rs1use crate::layer::icmp::checksum::icmp_checksum;
14use std::net::{Ipv4Addr, Ipv6Addr};
15
16pub mod class_nums {
18 pub const MPLS: u8 = 1;
19 pub const INTERFACE_INFORMATION: u8 = 2;
20 pub const INTERFACE_IDENTIFICATION: u8 = 3;
21}
22
23#[must_use]
25pub fn class_name(classnum: u8) -> &'static str {
26 match classnum {
27 class_nums::MPLS => "MPLS",
28 class_nums::INTERFACE_INFORMATION => "Interface Information",
29 class_nums::INTERFACE_IDENTIFICATION => "Interface Identification",
30 _ => "unknown",
31 }
32}
33
34pub mod header_offsets {
36 pub const VERSION_RESERVED: usize = 0; pub const CHECKSUM: usize = 2;
38}
39
40pub const EXTENSION_HEADER_LEN: usize = 4;
41
42pub mod object_offsets {
44 pub const LEN: usize = 0;
45 pub const CLASSNUM: usize = 2;
46 pub const CLASSTYPE: usize = 3;
47 pub const DATA: usize = 4;
48}
49
50pub const EXTENSION_OBJECT_MIN_LEN: usize = 4;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct InterfaceInfoFlags {
55 pub has_ifindex: bool,
56 pub has_ipaddr: bool,
57 pub has_ifname: bool,
58 pub has_mtu: bool,
59}
60
61impl InterfaceInfoFlags {
62 #[must_use]
64 pub fn from_byte(byte: u8) -> Self {
65 Self {
66 has_ifindex: (byte & 0b0001_0000) != 0,
67 has_ipaddr: (byte & 0b0000_1000) != 0,
68 has_ifname: (byte & 0b0000_0100) != 0,
69 has_mtu: (byte & 0b0000_0001) != 0,
70 }
71 }
72
73 #[must_use]
75 pub fn to_byte(&self) -> u8 {
76 let mut byte = 0u8;
77 if self.has_ifindex {
78 byte |= 0b0001_0000;
79 }
80 if self.has_ipaddr {
81 byte |= 0b0000_1000;
82 }
83 if self.has_ifname {
84 byte |= 0b0000_0100;
85 }
86 if self.has_mtu {
87 byte |= 0b0000_0001;
88 }
89 byte
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum Afi {
96 Ipv4 = 1,
97 Ipv6 = 2,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct InterfaceInformation {
103 pub flags: InterfaceInfoFlags,
104 pub ifindex: Option<u32>,
105 pub ip_addr: Option<IpAddr>,
106 pub ifname: Option<String>,
107 pub mtu: Option<u32>,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum IpAddr {
113 V4(Ipv4Addr),
114 V6(Ipv6Addr),
115}
116
117#[must_use]
123pub fn has_extensions(icmp_type: u8, payload_len: usize) -> bool {
124 use crate::layer::icmp::error;
125
126 if !error::is_error_type(icmp_type) {
128 return false;
129 }
130
131 if payload_len < error::ERROR_MIN_PAYLOAD + EXTENSION_HEADER_LEN {
134 return false;
135 }
136
137 payload_len >= 128 + EXTENSION_HEADER_LEN + EXTENSION_OBJECT_MIN_LEN
140}
141
142#[must_use]
146pub fn extension_offset(icmp_header_start: usize, icmp_payload: &[u8]) -> Option<usize> {
147 if icmp_payload.len() >= 128 + EXTENSION_HEADER_LEN {
150 Some(icmp_header_start + 8 + 128) } else {
152 None
153 }
154}
155
156#[must_use]
158pub fn parse_extension_header(buf: &[u8], offset: usize) -> Option<(u8, u16)> {
159 if offset + EXTENSION_HEADER_LEN > buf.len() {
160 return None;
161 }
162
163 let version_reserved = u16::from_be_bytes([buf[offset], buf[offset + 1]]);
164 let version = ((version_reserved >> 12) & 0x0F) as u8;
165 let checksum = u16::from_be_bytes([buf[offset + 2], buf[offset + 3]]);
166
167 if version != 2 {
169 return None;
170 }
171
172 Some((version, checksum))
173}
174
175#[must_use]
177pub fn verify_extension_checksum(buf: &[u8], ext_start: usize, ext_len: usize) -> bool {
178 if ext_start + ext_len > buf.len() || ext_len < EXTENSION_HEADER_LEN {
179 return false;
180 }
181
182 let mut data = vec![0u8; ext_len];
184 data.copy_from_slice(&buf[ext_start..ext_start + ext_len]);
185 data[2] = 0;
186 data[3] = 0;
187
188 let computed = icmp_checksum(&data);
189 let stored = u16::from_be_bytes([buf[ext_start + 2], buf[ext_start + 3]]);
190
191 computed == stored
192}
193
194#[must_use]
196pub fn parse_extension_object(buf: &[u8], offset: usize) -> Option<(u16, u8, u8, usize)> {
197 if offset + EXTENSION_OBJECT_MIN_LEN > buf.len() {
198 return None;
199 }
200
201 let len = u16::from_be_bytes([buf[offset], buf[offset + 1]]);
202 let classnum = buf[offset + 2];
203 let classtype = buf[offset + 3];
204
205 if len < 4 || !len.is_multiple_of(4) {
207 return None;
208 }
209
210 let data_offset = offset + EXTENSION_OBJECT_MIN_LEN;
211
212 Some((len, classnum, classtype, data_offset))
213}
214
215#[must_use]
217pub fn parse_interface_information(
218 buf: &[u8],
219 data_offset: usize,
220 data_len: usize,
221) -> Option<InterfaceInformation> {
222 if data_len == 0 {
223 return None;
224 }
225
226 let flags_byte = buf[data_offset];
228 let flags = InterfaceInfoFlags::from_byte(flags_byte);
229
230 let mut offset = data_offset + 1; let mut result = InterfaceInformation {
232 flags,
233 ifindex: None,
234 ip_addr: None,
235 ifname: None,
236 mtu: None,
237 };
238
239 if flags.has_ifindex {
241 if offset + 4 > buf.len() {
242 return None;
243 }
244 result.ifindex = Some(u32::from_be_bytes([
245 buf[offset],
246 buf[offset + 1],
247 buf[offset + 2],
248 buf[offset + 3],
249 ]));
250 offset += 4;
251 }
252
253 if flags.has_ipaddr {
254 if offset + 4 > buf.len() {
255 return None;
256 }
257 let afi = u16::from_be_bytes([buf[offset], buf[offset + 1]]);
258 offset += 4; match afi {
261 1 => {
262 if offset + 4 > buf.len() {
264 return None;
265 }
266 let ip = Ipv4Addr::new(
267 buf[offset],
268 buf[offset + 1],
269 buf[offset + 2],
270 buf[offset + 3],
271 );
272 result.ip_addr = Some(IpAddr::V4(ip));
273 offset += 4;
274 },
275 2 => {
276 if offset + 16 > buf.len() {
278 return None;
279 }
280 let mut octets = [0u8; 16];
281 octets.copy_from_slice(&buf[offset..offset + 16]);
282 result.ip_addr = Some(IpAddr::V6(Ipv6Addr::from(octets)));
283 offset += 16;
284 },
285 _ => return None, }
287 }
288
289 if flags.has_ifname {
290 if offset + 1 > buf.len() {
291 return None;
292 }
293 let name_len = buf[offset] as usize;
294 offset += 1;
295
296 if offset + name_len > buf.len() {
297 return None;
298 }
299 let name_bytes = &buf[offset..offset + name_len];
300 if let Ok(name) = String::from_utf8(name_bytes.to_vec()) {
301 result.ifname = Some(name);
302 }
303 offset += name_len;
304 }
305
306 if flags.has_mtu {
307 if offset + 4 > buf.len() {
308 return None;
309 }
310 result.mtu = Some(u32::from_be_bytes([
311 buf[offset],
312 buf[offset + 1],
313 buf[offset + 2],
314 buf[offset + 3],
315 ]));
316 }
317
318 Some(result)
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn test_interface_info_flags() {
327 let flags = InterfaceInfoFlags {
328 has_ifindex: true,
329 has_ipaddr: true,
330 has_ifname: false,
331 has_mtu: true,
332 };
333
334 let byte = flags.to_byte();
335 assert_eq!(byte, 0b0001_1001);
336
337 let parsed = InterfaceInfoFlags::from_byte(byte);
338 assert_eq!(parsed, flags);
339 }
340
341 #[test]
342 fn test_parse_extension_header() {
343 let buf = [0x20, 0x00, 0x12, 0x34];
345 let result = parse_extension_header(&buf, 0);
346 assert_eq!(result, Some((2, 0x1234)));
347 }
348
349 #[test]
350 fn test_parse_extension_object() {
351 let buf = [0x00, 0x08, 0x02, 0x19, 0x00, 0x00, 0x00, 0x00];
353 let result = parse_extension_object(&buf, 0);
354 assert_eq!(result, Some((8, 2, 0x19, 4)));
355 }
356
357 #[test]
358 fn test_class_name() {
359 assert_eq!(class_name(class_nums::MPLS), "MPLS");
360 assert_eq!(
361 class_name(class_nums::INTERFACE_INFORMATION),
362 "Interface Information"
363 );
364 assert_eq!(class_name(99), "unknown");
365 }
366
367 #[test]
368 fn test_has_extensions() {
369 use crate::layer::icmp::types::types;
370
371 assert!(has_extensions(types::TIME_EXCEEDED, 200));
373
374 assert!(!has_extensions(types::ECHO_REQUEST, 200));
376
377 assert!(!has_extensions(types::TIME_EXCEEDED, 30));
379 }
380}