|
61 | 61 | #![allow(non_camel_case_types)] |
62 | 62 |
|
63 | 63 | use std::fmt; |
64 | | -use std::net::IpAddr; |
| 64 | +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
65 | 65 |
|
66 | 66 | pub use error::TryFromSliceError; |
67 | 67 | use serde::{Deserialize, Serialize}; |
@@ -276,6 +276,156 @@ pub fn dump_bv(x: &BitVec<u8, Msb0>) -> String { |
276 | 276 | } |
277 | 277 | } |
278 | 278 |
|
| 279 | +pub fn dump_ip4(x: &BitVec<u8, Msb0>) -> String { |
| 280 | + if x.len() != 32 { |
| 281 | + return dump_bv(x); |
| 282 | + } |
| 283 | + let v: u32 = x.load_le(); |
| 284 | + Ipv4Addr::from(v).to_string() |
| 285 | +} |
| 286 | + |
| 287 | +pub fn dump_ip6(x: &BitVec<u8, Msb0>) -> String { |
| 288 | + if x.len() != 128 { |
| 289 | + return dump_bv(x); |
| 290 | + } |
| 291 | + let v: u128 = x.load_le(); |
| 292 | + Ipv6Addr::from(v).to_string() |
| 293 | +} |
| 294 | + |
| 295 | +pub fn dump_mac(x: &BitVec<u8, Msb0>) -> String { |
| 296 | + if x.len() != 48 { |
| 297 | + return dump_bv(x); |
| 298 | + } |
| 299 | + let v: u128 = x.load_le(); |
| 300 | + format!( |
| 301 | + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", |
| 302 | + (v >> 40) & 0xff, |
| 303 | + (v >> 32) & 0xff, |
| 304 | + (v >> 24) & 0xff, |
| 305 | + (v >> 16) & 0xff, |
| 306 | + (v >> 8) & 0xff, |
| 307 | + v & 0xff, |
| 308 | + ) |
| 309 | +} |
| 310 | + |
| 311 | +pub fn dump_ether_type(x: &BitVec<u8, Msb0>) -> String { |
| 312 | + if x.len() != 16 { |
| 313 | + return dump_bv(x); |
| 314 | + } |
| 315 | + let v: u16 = x.load_le(); |
| 316 | + let name = match v { |
| 317 | + 0x0800 => Some("IPv4"), |
| 318 | + 0x0806 => Some("ARP"), |
| 319 | + 0x0901 => Some("Sidecar"), |
| 320 | + 0x8100 => Some("VLAN"), |
| 321 | + 0x86dd => Some("IPv6"), |
| 322 | + 0x8847 => Some("MPLS unicast"), |
| 323 | + 0x8848 => Some("MPLS multicast"), |
| 324 | + 0x88a8 => Some("QinQ"), |
| 325 | + _ => None, |
| 326 | + }; |
| 327 | + match name { |
| 328 | + Some(n) => format!("0x{:04x} ({})", v, n), |
| 329 | + None => format!("0x{:04x}", v), |
| 330 | + } |
| 331 | +} |
| 332 | + |
| 333 | +pub fn dump_ip_proto(x: &BitVec<u8, Msb0>) -> String { |
| 334 | + if x.len() != 8 { |
| 335 | + return dump_bv(x); |
| 336 | + } |
| 337 | + let v: u8 = x.load_le(); |
| 338 | + let name = match v { |
| 339 | + 1 => Some("ICMPv4"), |
| 340 | + 6 => Some("TCP"), |
| 341 | + 17 => Some("UDP"), |
| 342 | + 41 => Some("IPv6-in-IPv4"), |
| 343 | + 43 => Some("IPv6-Route"), |
| 344 | + 44 => Some("IPv6-Frag"), |
| 345 | + 58 => Some("ICMPv6"), |
| 346 | + 59 => Some("IPv6-NoNxt"), |
| 347 | + 60 => Some("IPv6-Opts"), |
| 348 | + 132 => Some("SCTP"), |
| 349 | + _ => None, |
| 350 | + }; |
| 351 | + match name { |
| 352 | + Some(n) => format!("0x{:02x} ({})", v, n), |
| 353 | + None => format!("0x{:02x}", v), |
| 354 | + } |
| 355 | +} |
| 356 | + |
| 357 | +pub fn dump_tcp_flags(x: &BitVec<u8, Msb0>) -> String { |
| 358 | + if x.len() != 8 { |
| 359 | + return dump_bv(x); |
| 360 | + } |
| 361 | + let v: u8 = x.load_le(); |
| 362 | + if v == 0 { |
| 363 | + return "none".into(); |
| 364 | + } |
| 365 | + let mut flags = Vec::new(); |
| 366 | + if v & 0x01 != 0 { flags.push("FIN"); } |
| 367 | + if v & 0x02 != 0 { flags.push("SYN"); } |
| 368 | + if v & 0x04 != 0 { flags.push("RST"); } |
| 369 | + if v & 0x08 != 0 { flags.push("PSH"); } |
| 370 | + if v & 0x10 != 0 { flags.push("ACK"); } |
| 371 | + if v & 0x20 != 0 { flags.push("URG"); } |
| 372 | + if v & 0x40 != 0 { flags.push("ECE"); } |
| 373 | + if v & 0x80 != 0 { flags.push("CWR"); } |
| 374 | + flags.join("|") |
| 375 | +} |
| 376 | + |
| 377 | +pub fn dump_arp_op(x: &BitVec<u8, Msb0>) -> String { |
| 378 | + if x.len() != 16 { |
| 379 | + return dump_bv(x); |
| 380 | + } |
| 381 | + let v: u16 = x.load_le(); |
| 382 | + let name = match v { |
| 383 | + 1 => Some("Request"), |
| 384 | + 2 => Some("Reply"), |
| 385 | + 3 => Some("RARP Request"), |
| 386 | + 4 => Some("RARP Reply"), |
| 387 | + _ => None, |
| 388 | + }; |
| 389 | + match name { |
| 390 | + Some(n) => format!("0x{:04x} ({})", v, n), |
| 391 | + None => format!("0x{:04x}", v), |
| 392 | + } |
| 393 | +} |
| 394 | + |
| 395 | +pub fn dump_icmp_type(x: &BitVec<u8, Msb0>) -> String { |
| 396 | + if x.len() != 8 { |
| 397 | + return dump_bv(x); |
| 398 | + } |
| 399 | + let v: u8 = x.load_le(); |
| 400 | + // Types 0-127 use ICMPv4 semantics; types 128-255 are exclusively ICMPv6. |
| 401 | + let name = match v { |
| 402 | + // ICMPv4 |
| 403 | + 0 => Some("Echo Reply"), |
| 404 | + 3 => Some("Dest Unreachable"), |
| 405 | + 5 => Some("Redirect"), |
| 406 | + 8 => Some("Echo Request"), |
| 407 | + 11 => Some("Time Exceeded"), |
| 408 | + 12 => Some("Parameter Problem"), |
| 409 | + // ICMPv6 (128+ are unambiguously ICMPv6) |
| 410 | + 128 => Some("Echo Request"), |
| 411 | + 129 => Some("Echo Reply"), |
| 412 | + 130 => Some("MLD Query"), |
| 413 | + 131 => Some("MLD Report"), |
| 414 | + 132 => Some("MLD Done"), |
| 415 | + 133 => Some("RS"), |
| 416 | + 134 => Some("RA"), |
| 417 | + 135 => Some("NS"), |
| 418 | + 136 => Some("NA"), |
| 419 | + 137 => Some("Redirect"), |
| 420 | + 143 => Some("MLDv2 Report"), |
| 421 | + _ => None, |
| 422 | + }; |
| 423 | + match name { |
| 424 | + Some(n) => format!("0x{:02x} ({})", v, n), |
| 425 | + None => format!("0x{:02x}", v), |
| 426 | + } |
| 427 | +} |
| 428 | + |
279 | 429 | pub fn extract_exact_key( |
280 | 430 | keyset_data: &[u8], |
281 | 431 | offset: usize, |
@@ -357,6 +507,99 @@ pub fn extract_lpm_key( |
357 | 507 | table::Key::Lpm(table::Prefix { addr, len }) |
358 | 508 | } |
359 | 509 |
|
| 510 | +#[cfg(test)] |
| 511 | +mod tests { |
| 512 | + use super::*; |
| 513 | + |
| 514 | + // Construct a bitvec as it would appear after header::set() — bytes are |
| 515 | + // reversed for multi-byte fields (>8 bits), unchanged for single-byte fields. |
| 516 | + fn net_bv(bytes: &[u8]) -> BitVec<u8, Msb0> { |
| 517 | + let mut v = bytes.to_vec(); |
| 518 | + if v.len() > 1 { |
| 519 | + v.reverse(); |
| 520 | + } |
| 521 | + BitVec::<u8, Msb0>::from_vec(v) |
| 522 | + } |
| 523 | + |
| 524 | + #[test] |
| 525 | + fn test_dump_ip4() { |
| 526 | + assert_eq!(dump_ip4(&net_bv(&[192, 168, 1, 1])), "192.168.1.1"); |
| 527 | + assert_eq!(dump_ip4(&net_bv(&[10, 0, 0, 1])), "10.0.0.1"); |
| 528 | + assert_eq!(dump_ip4(&net_bv(&[0, 0, 0, 0])), "0.0.0.0"); |
| 529 | + assert_eq!(dump_ip4(&net_bv(&[255, 255, 255, 255])), "255.255.255.255"); |
| 530 | + } |
| 531 | + |
| 532 | + #[test] |
| 533 | + fn test_dump_ip6() { |
| 534 | + let mut fd00_1 = [0u8; 16]; |
| 535 | + fd00_1[0] = 0xfd; |
| 536 | + fd00_1[15] = 0x01; |
| 537 | + assert_eq!(dump_ip6(&net_bv(&fd00_1)), "fd00::1"); |
| 538 | + |
| 539 | + let mut loopback = [0u8; 16]; |
| 540 | + loopback[15] = 1; |
| 541 | + assert_eq!(dump_ip6(&net_bv(&loopback)), "::1"); |
| 542 | + } |
| 543 | + |
| 544 | + #[test] |
| 545 | + fn test_dump_mac() { |
| 546 | + assert_eq!( |
| 547 | + dump_mac(&net_bv(&[0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff])), |
| 548 | + "aa:bb:cc:dd:ee:ff" |
| 549 | + ); |
| 550 | + assert_eq!( |
| 551 | + dump_mac(&net_bv(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00])), |
| 552 | + "00:00:00:00:00:00" |
| 553 | + ); |
| 554 | + } |
| 555 | + |
| 556 | + #[test] |
| 557 | + fn test_dump_ether_type() { |
| 558 | + assert_eq!(dump_ether_type(&net_bv(&[0x08, 0x00])), "0x0800 (IPv4)"); |
| 559 | + assert_eq!(dump_ether_type(&net_bv(&[0x08, 0x06])), "0x0806 (ARP)"); |
| 560 | + assert_eq!(dump_ether_type(&net_bv(&[0x86, 0xdd])), "0x86dd (IPv6)"); |
| 561 | + assert_eq!(dump_ether_type(&net_bv(&[0x09, 0x01])), "0x0901 (Sidecar)"); |
| 562 | + assert_eq!(dump_ether_type(&net_bv(&[0x12, 0x34])), "0x1234"); |
| 563 | + } |
| 564 | + |
| 565 | + #[test] |
| 566 | + fn test_dump_ip_proto() { |
| 567 | + assert_eq!(dump_ip_proto(&net_bv(&[1])), "0x01 (ICMPv4)"); |
| 568 | + assert_eq!(dump_ip_proto(&net_bv(&[6])), "0x06 (TCP)"); |
| 569 | + assert_eq!(dump_ip_proto(&net_bv(&[17])), "0x11 (UDP)"); |
| 570 | + assert_eq!(dump_ip_proto(&net_bv(&[58])), "0x3a (ICMPv6)"); |
| 571 | + assert_eq!(dump_ip_proto(&net_bv(&[99])), "0x63"); |
| 572 | + } |
| 573 | + |
| 574 | + #[test] |
| 575 | + fn test_dump_tcp_flags() { |
| 576 | + assert_eq!(dump_tcp_flags(&net_bv(&[0x00])), "none"); |
| 577 | + assert_eq!(dump_tcp_flags(&net_bv(&[0x02])), "SYN"); |
| 578 | + assert_eq!(dump_tcp_flags(&net_bv(&[0x12])), "SYN|ACK"); |
| 579 | + assert_eq!(dump_tcp_flags(&net_bv(&[0x01])), "FIN"); |
| 580 | + assert_eq!(dump_tcp_flags(&net_bv(&[0x04])), "RST"); |
| 581 | + assert_eq!(dump_tcp_flags(&net_bv(&[0xff])), "FIN|SYN|RST|PSH|ACK|URG|ECE|CWR"); |
| 582 | + } |
| 583 | + |
| 584 | + #[test] |
| 585 | + fn test_dump_arp_op() { |
| 586 | + assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x01])), "0x0001 (Request)"); |
| 587 | + assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x02])), "0x0002 (Reply)"); |
| 588 | + assert_eq!(dump_arp_op(&net_bv(&[0x00, 0x05])), "0x0005"); |
| 589 | + } |
| 590 | + |
| 591 | + #[test] |
| 592 | + fn test_dump_icmp_type() { |
| 593 | + assert_eq!(dump_icmp_type(&net_bv(&[0])), "0x00 (Echo Reply)"); |
| 594 | + assert_eq!(dump_icmp_type(&net_bv(&[8])), "0x08 (Echo Request)"); |
| 595 | + assert_eq!(dump_icmp_type(&net_bv(&[133])), "0x85 (RS)"); |
| 596 | + assert_eq!(dump_icmp_type(&net_bv(&[134])), "0x86 (RA)"); |
| 597 | + assert_eq!(dump_icmp_type(&net_bv(&[135])), "0x87 (NS)"); |
| 598 | + assert_eq!(dump_icmp_type(&net_bv(&[136])), "0x88 (NA)"); |
| 599 | + assert_eq!(dump_icmp_type(&net_bv(&[50])), "0x32"); |
| 600 | + } |
| 601 | +} |
| 602 | + |
360 | 603 | pub fn extract_bool_action_parameter( |
361 | 604 | parameter_data: &[u8], |
362 | 605 | offset: usize, |
|
0 commit comments