1use compact_str::CompactString;
7use smallvec::SmallVec;
8
9use super::netlink::family as netlink_family;
10use super::{FieldValue, ParseContext, ParseResult, Protocol};
11use crate::schema::{DataKind, FieldDescriptor};
12
13pub mod msg_type {
15 pub const RTM_NEWLINK: u16 = 16;
16 pub const RTM_DELLINK: u16 = 17;
17 pub const RTM_GETLINK: u16 = 18;
18 pub const RTM_SETLINK: u16 = 19;
19 pub const RTM_NEWADDR: u16 = 20;
20 pub const RTM_DELADDR: u16 = 21;
21 pub const RTM_GETADDR: u16 = 22;
22 pub const RTM_NEWROUTE: u16 = 24;
23 pub const RTM_DELROUTE: u16 = 25;
24 pub const RTM_GETROUTE: u16 = 26;
25 pub const RTM_NEWNEIGH: u16 = 28;
26 pub const RTM_DELNEIGH: u16 = 29;
27 pub const RTM_GETNEIGH: u16 = 30;
28}
29
30fn msg_type_name(msg_type: u16) -> &'static str {
32 match msg_type {
33 msg_type::RTM_NEWLINK => "RTM_NEWLINK",
34 msg_type::RTM_DELLINK => "RTM_DELLINK",
35 msg_type::RTM_GETLINK => "RTM_GETLINK",
36 msg_type::RTM_SETLINK => "RTM_SETLINK",
37 msg_type::RTM_NEWADDR => "RTM_NEWADDR",
38 msg_type::RTM_DELADDR => "RTM_DELADDR",
39 msg_type::RTM_GETADDR => "RTM_GETADDR",
40 msg_type::RTM_NEWROUTE => "RTM_NEWROUTE",
41 msg_type::RTM_DELROUTE => "RTM_DELROUTE",
42 msg_type::RTM_GETROUTE => "RTM_GETROUTE",
43 msg_type::RTM_NEWNEIGH => "RTM_NEWNEIGH",
44 msg_type::RTM_DELNEIGH => "RTM_DELNEIGH",
45 msg_type::RTM_GETNEIGH => "RTM_GETNEIGH",
46 _ => "UNKNOWN",
47 }
48}
49
50#[derive(Debug, Clone, Copy)]
54pub struct RtnetlinkProtocol;
55
56impl Protocol for RtnetlinkProtocol {
57 fn name(&self) -> &'static str {
58 "rtnetlink"
59 }
60
61 fn display_name(&self) -> &'static str {
62 "RTNetlink"
63 }
64
65 fn can_parse(&self, context: &ParseContext) -> Option<u32> {
66 if context.parent_protocol == Some("netlink") {
68 if let Some(family) = context.hint("netlink_family") {
69 if family == netlink_family::ROUTE as u64 {
70 return Some(100);
71 }
72 }
73 }
74 None
75 }
76
77 fn parse<'a>(&self, data: &'a [u8], context: &ParseContext) -> ParseResult<'a> {
78 let mut fields = SmallVec::new();
79
80 let nl_msg_type = context
82 .hint("netlink_msg_type")
83 .map(|t| t as u16)
84 .unwrap_or(0);
85
86 fields.push(("msg_type", FieldValue::UInt16(nl_msg_type)));
87 fields.push(("msg_type_name", FieldValue::Str(msg_type_name(nl_msg_type))));
88
89 match nl_msg_type {
91 16..=19 => {
93 parse_link_header(data, &mut fields);
94 }
95 20..=22 => {
97 parse_addr_header(data, &mut fields);
98 }
99 24..=26 => {
101 parse_route_header(data, &mut fields);
102 }
103 _ => {
104 }
106 }
107
108 ParseResult::success(fields, &[], SmallVec::new())
110 }
111
112 fn schema_fields(&self) -> Vec<FieldDescriptor> {
113 vec![
114 FieldDescriptor::new("rtnetlink.msg_type", DataKind::UInt16).set_nullable(true),
116 FieldDescriptor::new("rtnetlink.msg_type_name", DataKind::String).set_nullable(true),
117 FieldDescriptor::new("rtnetlink.link_index", DataKind::UInt32).set_nullable(true),
119 FieldDescriptor::new("rtnetlink.link_type", DataKind::UInt16).set_nullable(true),
120 FieldDescriptor::new("rtnetlink.link_flags", DataKind::UInt32).set_nullable(true),
121 FieldDescriptor::new("rtnetlink.if_name", DataKind::String).set_nullable(true),
122 FieldDescriptor::new("rtnetlink.mtu", DataKind::UInt32).set_nullable(true),
123 FieldDescriptor::mac_field("rtnetlink.hw_addr").set_nullable(true),
124 FieldDescriptor::new("rtnetlink.addr_family", DataKind::UInt8).set_nullable(true),
126 FieldDescriptor::new("rtnetlink.addr_family_name", DataKind::String).set_nullable(true),
127 FieldDescriptor::new("rtnetlink.prefix_len", DataKind::UInt8).set_nullable(true),
128 FieldDescriptor::new("rtnetlink.addr_index", DataKind::UInt32).set_nullable(true),
129 FieldDescriptor::new("rtnetlink.address", DataKind::String).set_nullable(true),
130 FieldDescriptor::new("rtnetlink.local_addr", DataKind::String).set_nullable(true),
131 FieldDescriptor::new("rtnetlink.route_family", DataKind::UInt8).set_nullable(true),
133 FieldDescriptor::new("rtnetlink.dst_prefix_len", DataKind::UInt8).set_nullable(true),
134 FieldDescriptor::new("rtnetlink.src_prefix_len", DataKind::UInt8).set_nullable(true),
135 FieldDescriptor::new("rtnetlink.route_table", DataKind::UInt8).set_nullable(true),
136 FieldDescriptor::new("rtnetlink.route_protocol", DataKind::UInt8).set_nullable(true),
137 FieldDescriptor::new("rtnetlink.route_scope", DataKind::UInt8).set_nullable(true),
138 FieldDescriptor::new("rtnetlink.route_type", DataKind::UInt8).set_nullable(true),
139 FieldDescriptor::new("rtnetlink.destination", DataKind::String).set_nullable(true),
140 FieldDescriptor::new("rtnetlink.gateway", DataKind::String).set_nullable(true),
141 FieldDescriptor::new("rtnetlink.oif_index", DataKind::UInt32).set_nullable(true),
142 ]
143 }
144
145 fn dependencies(&self) -> &'static [&'static str] {
146 &["netlink"]
147 }
148}
149
150#[allow(dead_code)]
152mod link_header {
153 pub const FAMILY_OFFSET: usize = 0;
154 pub const TYPE_OFFSET: usize = 2;
155 pub const INDEX_OFFSET: usize = 4;
156 pub const FLAGS_OFFSET: usize = 8;
157 pub const CHANGE_OFFSET: usize = 12;
158 pub const MIN_LEN: usize = 16;
159}
160
161#[allow(dead_code)]
163mod addr_header {
164 pub const FAMILY_OFFSET: usize = 0;
165 pub const PREFIXLEN_OFFSET: usize = 1;
166 pub const FLAGS_OFFSET: usize = 2;
167 pub const SCOPE_OFFSET: usize = 3;
168 pub const INDEX_OFFSET: usize = 4;
169 pub const MIN_LEN: usize = 8;
170}
171
172#[allow(dead_code)]
174mod route_header {
175 pub const FAMILY_OFFSET: usize = 0;
176 pub const DST_LEN_OFFSET: usize = 1;
177 pub const SRC_LEN_OFFSET: usize = 2;
178 pub const TOS_OFFSET: usize = 3;
179 pub const TABLE_OFFSET: usize = 4;
180 pub const PROTOCOL_OFFSET: usize = 5;
181 pub const SCOPE_OFFSET: usize = 6;
182 pub const TYPE_OFFSET: usize = 7;
183 pub const MIN_LEN: usize = 12;
184}
185
186fn parse_link_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
188 if data.len() < link_header::MIN_LEN {
189 return;
190 }
191
192 let link_type = u16::from_le_bytes([
193 data[link_header::TYPE_OFFSET],
194 data[link_header::TYPE_OFFSET + 1],
195 ]);
196 let index = u32::from_le_bytes([
197 data[link_header::INDEX_OFFSET],
198 data[link_header::INDEX_OFFSET + 1],
199 data[link_header::INDEX_OFFSET + 2],
200 data[link_header::INDEX_OFFSET + 3],
201 ]);
202 let flags = u32::from_le_bytes([
203 data[link_header::FLAGS_OFFSET],
204 data[link_header::FLAGS_OFFSET + 1],
205 data[link_header::FLAGS_OFFSET + 2],
206 data[link_header::FLAGS_OFFSET + 3],
207 ]);
208
209 fields.push(("link_index", FieldValue::UInt32(index)));
210 fields.push(("link_type", FieldValue::UInt16(link_type)));
211 fields.push(("link_flags", FieldValue::UInt32(flags)));
212
213 parse_link_attributes(&data[link_header::MIN_LEN..], fields);
215}
216
217fn parse_link_attributes(mut data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
219 while data.len() >= 4 {
221 let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
222 let attr_type = u16::from_le_bytes([data[2], data[3]]);
223
224 if attr_len < 4 || attr_len > data.len() {
225 break;
226 }
227
228 let value = &data[4..attr_len];
229
230 match attr_type {
231 3 => {
233 if let Ok(name) = std::str::from_utf8(value.strip_suffix(&[0]).unwrap_or(value)) {
234 fields.push(("if_name", FieldValue::OwnedString(CompactString::new(name))));
235 }
236 }
237 4 if value.len() >= 4 => {
239 let mtu = u32::from_le_bytes([value[0], value[1], value[2], value[3]]);
240 fields.push(("mtu", FieldValue::UInt32(mtu)));
241 }
242 1 if value.len() == 6 => {
244 let mut mac = [0u8; 6];
245 mac.copy_from_slice(value);
246 fields.push(("hw_addr", FieldValue::MacAddr(mac)));
247 }
248 _ => {}
249 }
250
251 let padded_len = (attr_len + 3) & !3;
253 if padded_len > data.len() {
254 break;
255 }
256 data = &data[padded_len..];
257 }
258}
259
260fn parse_addr_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
262 if data.len() < addr_header::MIN_LEN {
263 return;
264 }
265
266 let family = data[addr_header::FAMILY_OFFSET];
267 let prefix_len = data[addr_header::PREFIXLEN_OFFSET];
268 let index = u32::from_le_bytes([
269 data[addr_header::INDEX_OFFSET],
270 data[addr_header::INDEX_OFFSET + 1],
271 data[addr_header::INDEX_OFFSET + 2],
272 data[addr_header::INDEX_OFFSET + 3],
273 ]);
274
275 fields.push(("addr_family", FieldValue::UInt8(family)));
276 fields.push((
277 "addr_family_name",
278 FieldValue::Str(match family {
279 2 => "IPv4", 10 => "IPv6", _ => "Unknown",
282 }),
283 ));
284 fields.push(("prefix_len", FieldValue::UInt8(prefix_len)));
285 fields.push(("addr_index", FieldValue::UInt32(index)));
286
287 parse_addr_attributes(&data[addr_header::MIN_LEN..], family, fields);
289}
290
291fn parse_addr_attributes(
293 mut data: &[u8],
294 family: u8,
295 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
296) {
297 while data.len() >= 4 {
298 let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
299 let attr_type = u16::from_le_bytes([data[2], data[3]]);
300
301 if attr_len < 4 || attr_len > data.len() {
302 break;
303 }
304
305 let value = &data[4..attr_len];
306
307 match attr_type {
308 1 => {
310 if let Some(addr_str) = format_ip_address(value, family) {
311 fields.push((
312 "address",
313 FieldValue::OwnedString(CompactString::new(&addr_str)),
314 ));
315 }
316 }
317 2 => {
319 if let Some(addr_str) = format_ip_address(value, family) {
320 fields.push((
321 "local_addr",
322 FieldValue::OwnedString(CompactString::new(&addr_str)),
323 ));
324 }
325 }
326 _ => {}
327 }
328
329 let padded_len = (attr_len + 3) & !3;
330 if padded_len > data.len() {
331 break;
332 }
333 data = &data[padded_len..];
334 }
335}
336
337fn format_ip_address(value: &[u8], family: u8) -> Option<String> {
339 match family {
340 2 if value.len() == 4 => {
341 Some(format!(
343 "{}.{}.{}.{}",
344 value[0], value[1], value[2], value[3]
345 ))
346 }
347 10 if value.len() == 16 => {
348 use std::net::Ipv6Addr;
350 let addr = Ipv6Addr::new(
351 u16::from_be_bytes([value[0], value[1]]),
352 u16::from_be_bytes([value[2], value[3]]),
353 u16::from_be_bytes([value[4], value[5]]),
354 u16::from_be_bytes([value[6], value[7]]),
355 u16::from_be_bytes([value[8], value[9]]),
356 u16::from_be_bytes([value[10], value[11]]),
357 u16::from_be_bytes([value[12], value[13]]),
358 u16::from_be_bytes([value[14], value[15]]),
359 );
360 Some(addr.to_string())
361 }
362 _ => None,
363 }
364}
365
366fn parse_route_header(data: &[u8], fields: &mut SmallVec<[(&'static str, FieldValue); 16]>) {
368 if data.len() < route_header::MIN_LEN {
369 return;
370 }
371
372 let family = data[route_header::FAMILY_OFFSET];
373 let dst_len = data[route_header::DST_LEN_OFFSET];
374 let src_len = data[route_header::SRC_LEN_OFFSET];
375 let table = data[route_header::TABLE_OFFSET];
376 let protocol = data[route_header::PROTOCOL_OFFSET];
377 let scope = data[route_header::SCOPE_OFFSET];
378 let route_type = data[route_header::TYPE_OFFSET];
379
380 fields.push(("route_family", FieldValue::UInt8(family)));
381 fields.push(("dst_prefix_len", FieldValue::UInt8(dst_len)));
382 fields.push(("src_prefix_len", FieldValue::UInt8(src_len)));
383 fields.push(("route_table", FieldValue::UInt8(table)));
384 fields.push(("route_protocol", FieldValue::UInt8(protocol)));
385 fields.push(("route_scope", FieldValue::UInt8(scope)));
386 fields.push(("route_type", FieldValue::UInt8(route_type)));
387
388 parse_route_attributes(&data[route_header::MIN_LEN..], family, fields);
390}
391
392fn parse_route_attributes(
394 mut data: &[u8],
395 family: u8,
396 fields: &mut SmallVec<[(&'static str, FieldValue); 16]>,
397) {
398 while data.len() >= 4 {
399 let attr_len = u16::from_le_bytes([data[0], data[1]]) as usize;
400 let attr_type = u16::from_le_bytes([data[2], data[3]]);
401
402 if attr_len < 4 || attr_len > data.len() {
403 break;
404 }
405
406 let value = &data[4..attr_len];
407
408 match attr_type {
409 1 => {
411 if let Some(addr_str) = format_ip_address(value, family) {
412 fields.push((
413 "destination",
414 FieldValue::OwnedString(CompactString::new(&addr_str)),
415 ));
416 }
417 }
418 5 => {
420 if let Some(addr_str) = format_ip_address(value, family) {
421 fields.push((
422 "gateway",
423 FieldValue::OwnedString(CompactString::new(&addr_str)),
424 ));
425 }
426 }
427 4 if value.len() >= 4 => {
429 let oif = u32::from_le_bytes([value[0], value[1], value[2], value[3]]);
430 fields.push(("oif_index", FieldValue::UInt32(oif)));
431 }
432 _ => {}
433 }
434
435 let padded_len = (attr_len + 3) & !3;
436 if padded_len > data.len() {
437 break;
438 }
439 data = &data[padded_len..];
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::protocol::netlink::LINKTYPE_NETLINK;
447
448 fn make_rtnetlink_context(msg_type: u16) -> ParseContext {
450 let mut ctx = ParseContext::new(LINKTYPE_NETLINK);
451 ctx.parent_protocol = Some("netlink");
452 ctx.insert_hint("netlink_family", netlink_family::ROUTE as u64);
453 ctx.insert_hint("netlink_msg_type", msg_type as u64);
454 ctx
455 }
456
457 #[test]
461 fn test_can_parse_rtnetlink() {
462 let parser = RtnetlinkProtocol;
463 let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
464
465 assert!(parser.can_parse(&ctx).is_some());
466 assert_eq!(parser.can_parse(&ctx), Some(100));
467 }
468
469 #[test]
473 fn test_cannot_parse_without_family_hint() {
474 let parser = RtnetlinkProtocol;
475 let ctx = ParseContext::new(LINKTYPE_NETLINK);
476
477 assert!(parser.can_parse(&ctx).is_none());
478 }
479
480 #[test]
484 fn test_cannot_parse_wrong_family() {
485 let parser = RtnetlinkProtocol;
486 let mut ctx = ParseContext::new(LINKTYPE_NETLINK);
487 ctx.parent_protocol = Some("netlink");
488 ctx.insert_hint("netlink_family", netlink_family::NETFILTER as u64);
489
490 assert!(parser.can_parse(&ctx).is_none());
491 }
492
493 #[test]
497 fn test_parse_rtm_newlink_type() {
498 let parser = RtnetlinkProtocol;
499 let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
500
501 let result = parser.parse(&[], &ctx);
503
504 assert!(result.is_ok());
505 assert_eq!(
506 result.get("msg_type"),
507 Some(&FieldValue::UInt16(msg_type::RTM_NEWLINK))
508 );
509 assert_eq!(
510 result.get("msg_type_name"),
511 Some(&FieldValue::Str("RTM_NEWLINK"))
512 );
513 }
514
515 #[test]
519 fn test_parse_rtm_newaddr_type() {
520 let parser = RtnetlinkProtocol;
521 let ctx = make_rtnetlink_context(msg_type::RTM_NEWADDR);
522
523 let result = parser.parse(&[], &ctx);
524
525 assert!(result.is_ok());
526 assert_eq!(
527 result.get("msg_type_name"),
528 Some(&FieldValue::Str("RTM_NEWADDR"))
529 );
530 }
531
532 #[test]
536 fn test_parse_rtm_newroute_type() {
537 let parser = RtnetlinkProtocol;
538 let ctx = make_rtnetlink_context(msg_type::RTM_NEWROUTE);
539
540 let result = parser.parse(&[], &ctx);
541
542 assert!(result.is_ok());
543 assert_eq!(
544 result.get("msg_type_name"),
545 Some(&FieldValue::Str("RTM_NEWROUTE"))
546 );
547 }
548
549 #[test]
553 fn test_rtnetlink_schema_fields() {
554 let parser = RtnetlinkProtocol;
555 let fields = parser.schema_fields();
556
557 let field_names: Vec<&str> = fields.iter().map(|f| f.name).collect();
558
559 assert!(field_names.contains(&"rtnetlink.msg_type"));
561 assert!(field_names.contains(&"rtnetlink.msg_type_name"));
562
563 assert!(field_names.contains(&"rtnetlink.link_index"));
565 assert!(field_names.contains(&"rtnetlink.link_type"));
566 assert!(field_names.contains(&"rtnetlink.link_flags"));
567 assert!(field_names.contains(&"rtnetlink.if_name"));
568 assert!(field_names.contains(&"rtnetlink.mtu"));
569 assert!(field_names.contains(&"rtnetlink.hw_addr"));
570
571 assert!(field_names.contains(&"rtnetlink.addr_family"));
573 assert!(field_names.contains(&"rtnetlink.prefix_len"));
574 assert!(field_names.contains(&"rtnetlink.address"));
575
576 assert!(field_names.contains(&"rtnetlink.route_family"));
578 assert!(field_names.contains(&"rtnetlink.dst_prefix_len"));
579 assert!(field_names.contains(&"rtnetlink.destination"));
580 assert!(field_names.contains(&"rtnetlink.gateway"));
581 assert!(field_names.contains(&"rtnetlink.oif_index"));
582 }
583
584 #[test]
588 fn test_rtnetlink_dependencies() {
589 let parser = RtnetlinkProtocol;
590 let deps = parser.dependencies();
591
592 assert!(deps.contains(&"netlink"));
593 }
594
595 #[test]
599 fn test_unknown_message_type() {
600 let parser = RtnetlinkProtocol;
601 let ctx = make_rtnetlink_context(999);
602
603 let result = parser.parse(&[], &ctx);
604
605 assert!(result.is_ok());
606 assert_eq!(
607 result.get("msg_type_name"),
608 Some(&FieldValue::Str("UNKNOWN"))
609 );
610 }
611
612 #[test]
616 fn test_rtnetlink_is_terminal() {
617 let parser = RtnetlinkProtocol;
618 let ctx = make_rtnetlink_context(msg_type::RTM_NEWLINK);
619
620 let result = parser.parse(&[0u8; 32], &ctx);
621
622 assert!(result.is_ok());
623 assert!(result.remaining.is_empty());
624 assert!(result.child_hints.is_empty());
625 }
626}