diff --git a/README.md b/README.md index 44baa427..f4cdb456 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Currently supported are: * UDP * TCP * ICMP & ICMPv6 (not all message types are supported) +* IGMP (v1, v2 & v3) Reconstruction of fragmented IP packets is also supported, but requires allocations. @@ -114,6 +115,7 @@ It is also possible to only slice one packet layer: * [`TcpSlice::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.TcpSlice.html#method.from_slice) * [`Icmpv4Slice::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv4Slice.html#method.from_slice) * [`Icmpv6Slice::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv6Slice.html#method.from_slice) +* [`IgmpSlice::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.IgmpSlice.html#method.from_slice) The resulting data types allow access to both the header(s) and the payload of the layer and will automatically limit the length of payload if the layer has a length field limiting the @@ -158,6 +160,7 @@ And for deserialization into the corresponding header structs have a look at: * [`TcpHeader::read`](https://docs.rs/etherparse/0.20.3/etherparse/struct.TcpHeader.html#method.read) & [`TcpHeader::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.TcpHeader.html#method.from_slice) * [`Icmpv4Header::read`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv4Header.html#method.read) & [`Icmpv4Header::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv4Header.html#method.from_slice) * [`Icmpv6Header::read`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv6Header.html#method.read) & [`Icmpv6Header::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv6Header.html#method.from_slice) +* [`IgmpHeader::from_slice`](https://docs.rs/etherparse/0.20.3/etherparse/struct.IgmpHeader.html#method.from_slice) ## How to generate fake packet data? @@ -216,6 +219,7 @@ Read the documentations of the different methods for a more details: * [`TcpHeader::to_bytes`](https://docs.rs/etherparse/0.20.3/etherparse/struct.TcpHeader.html#method.to_bytes) & [`TcpHeader::write`](https://docs.rs/etherparse/0.20.3/etherparse/struct.TcpHeader.html#method.write) * [`Icmpv4Header::to_bytes`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv4Header.html#method.to_bytes) & [`Icmpv4Header::write`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv4Header.html#method.write) * [`Icmpv6Header::to_bytes`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv6Header.html#method.to_bytes) & [`Icmpv6Header::write`](https://docs.rs/etherparse/0.20.3/etherparse/struct.Icmpv6Header.html#method.write) +* [`IgmpHeader::to_bytes`](https://docs.rs/etherparse/0.20.3/etherparse/struct.IgmpHeader.html#method.to_bytes) & [`IgmpHeader::write`](https://docs.rs/etherparse/0.20.3/etherparse/struct.IgmpHeader.html#method.write) ## References * Darpa Internet Program Protocol Specification [RFC 791](https://tools.ietf.org/html/rfc791) diff --git a/changelog.md b/changelog.md index fefe29d1..3c25886c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog: +## Unreleased + +* Added zero-copy IGMP support (IGMPv1, IGMPv2 & IGMPv3): + * `IgmpSlice`, an enum with one zero-copy variant per message type (`MembershipQuery`, `MembershipQueryWithSources`, `MembershipReportV1`, `MembershipReportV2`, `MembershipReportV3`, `LeaveGroup` & `Unknown`), plus common accessors (`header`, `payload`, `slice`, `checksum`, `is_checksum_valid`). + * Per-variant slice types (`MembershipQuerySlice`, `MembershipQueryWithSourcesSlice`, `MembershipReportV1Slice`, `MembershipReportV2Slice`, `MembershipReportV3Slice`, `LeaveGroupSlice` & `IgmpUnknownSlice`) with typed field accessors. Variable-length data is exposed where it exists: `MembershipReportV3Slice::group_records` and `MembershipQueryWithSourcesSlice::source_addresses` / `source_addrs_bytes` (no longer `Option`-returning). + * `ReportGroupRecordV3Slice` & `ReportGroupRecordV3SliceIter` for iterating IGMPv3 group records (incl. typed `source_addresses`). + * Integrated IGMP into the transport layer parsing & building: + * New enum variants `TransportSlice::Igmp`, `TransportHeader::Igmp`, `PayloadSlice::Igmp` & `LaxPayloadSlice::Igmp`. + * `SlicedPacket`, `LaxSlicedPacket`, `PacketHeaders` & `LaxPacketHeaders` now decode `ip_number::IGMP` payloads. + * `PacketBuilder` gained `igmp` & `igmp_header` setters and `IgmpHeader::write`. + ## 0.15.0 * Added Linux SLL Support (thanks to @RabadanDotDev) diff --git a/etherparse/examples/read_by_slicing.rs b/etherparse/examples/read_by_slicing.rs index 3422502e..f3e256e6 100644 --- a/etherparse/examples/read_by_slicing.rs +++ b/etherparse/examples/read_by_slicing.rs @@ -96,6 +96,7 @@ fn main() { match value.transport { Some(Icmpv4(value)) => println!(" Icmpv4 {:?}", value), Some(Icmpv6(value)) => println!(" Icmpv6 {:?}", value), + Some(Igmp(value)) => println!(" Igmp {:?}", value), Some(Udp(value)) => println!( " UDP {:?} -> {:?}", value.source_port(), diff --git a/etherparse/proptest-regressions/compositions_tests.txt b/etherparse/proptest-regressions/compositions_tests.txt index 5cb5f36c..7811a425 100644 --- a/etherparse/proptest-regressions/compositions_tests.txt +++ b/etherparse/proptest-regressions/compositions_tests.txt @@ -8,3 +8,4 @@ cc 93464c2fb682bf96a32f9800d3932df8611a278bf6c993dc3ad6301d17795715 # shrinks to cc 19938c0e61de8fbe9f8df17d1325091a1825e2b209a4adb8b21dcd28a0e0f558 # shrinks to ref eth = Ethernet2Header { source: [0, 0, 0, 0, 0, 0], destination: [0, 0, 0, 0, 0, 0], ether_type: 0 }, ref vlan_outer = SingleVlanHeader { priority_code_point: 0, drop_eligible_indicator: false, vlan_identifier: 0, ether_type: 0 }, ref vlan_inner = SingleVlanHeader { priority_code_point: 0, drop_eligible_indicator: false, vlan_identifier: 0, ether_type: 0 }, ref ipv4 = Ipv4Header { ihl: 8, differentiated_services_code_point: 0, explicit_congestion_notification: 0, payload_len: 34240, identification: 0, dont_fragment: false, more_fragments: false, fragments_offset: 0, time_to_live: 0, protocol: 95, header_checksum: 2458, source: [0, 0, 0, 0], destination: [0, 0, 0, 0], options: [80, 229, 92, 224, 82, 126, 48, 60, 105, 201, 96, 77] }, ref ipv4_exts = Ipv4Extensions { auth: None }, ref ipv6 = Ipv6Header { traffic_class: 129, flow_label: 787898, payload_length: 54827, next_header: 33, hop_limit: 254, source: [109, 7, 4, 79, 149, 61, 253, 73, 214, 117, 64, 10, 168, 230, 137, 73], destination: [44, 199, 106, 47, 71, 14, 18, 94, 107, 95, 41, 238, 83, 187, 218, 132] }, ref ipv6_exts = Ipv6Extensions { hop_by_hop_options: Some(Ipv6RawExtensionHeader { next_header: 60, payload: [112, 231, 1, 88, 255, 168, 119, 95, 144, 149, 61, 29, 235, 11, 182, 192, 83, 15, 201, 180, 189, 232, 85, 231, 220, 116, 192, 132, 43, 162, 23, 161, 129, 246, 28, 236, 164, 174, 67, 235, 121, 212, 9, 73, 30, 98, 190, 173, 122, 133, 58, 154, 142, 6, 24, 203, 3, 230, 232, 50, 77, 203, 83, 151, 3, 157, 193, 242, 25, 246, 224, 4, 178, 173, 156, 5, 210, 3, 97, 27, 171, 152, 187, 16, 98, 73, 57, 176, 35, 25, 246, 71, 154, 32, 132, 227, 164, 29, 92, 159, 74, 247, 144, 68, 39, 254, 227, 156, 63, 140, 246, 246, 199, 111, 101, 173, 179, 116, 79, 114, 249, 162, 71, 113, 121, 224, 229, 237, 67, 3, 4, 162, 152, 120, 58, 132, 244, 196, 136, 196, 206, 160, 45, 83, 167, 218, 32, 206, 52, 246, 144, 220, 133, 150, 36, 91, 193, 118, 28, 33, 236, 64, 255, 72, 190, 70, 160, 38, 139, 134, 80, 153, 236, 93, 198, 211, 21, 19, 251, 131, 119, 219, 161, 19, 144, 96, 6, 188, 115, 43, 91, 216, 5, 135, 101, 166, 99, 11, 174, 169, 255, 248, 101, 23, 62, 55, 169, 40, 6, 186, 195, 235, 76, 41] }), destination_options: Some(Ipv6RawExtensionHeader { next_header: 43, payload: [238, 203, 236, 202, 32, 25, 193, 164, 167, 189, 30, 208, 207, 108, 114, 10, 12, 226, 180, 59, 207, 44, 143, 244, 221, 200, 232, 154, 140, 180, 167, 70, 197, 72, 31, 249, 141, 75, 7, 255, 201, 53, 76, 234, 201, 187, 214, 141, 249, 216, 232, 12, 45, 196, 208, 110, 78, 14, 60, 251, 17, 239, 13, 141, 216, 29, 230, 120, 102, 88, 104, 237, 17, 252, 108, 126, 203, 75] }), routing: Some(Ipv6RoutingExtensions { routing: Ipv6RawExtensionHeader { next_header: 44, payload: [254, 77, 166, 70, 182, 207, 149, 153, 212, 40, 122, 249, 15, 84, 41, 126, 254, 103, 2, 162, 52, 216, 226, 175, 148, 253, 5, 153, 50, 16, 32, 44, 139, 24, 73, 245, 17, 9, 50, 18, 176, 70, 177, 29, 220, 255, 253, 255, 94, 39, 69, 225, 93, 176, 139, 48, 98, 210, 151, 80, 3, 105, 114, 59, 232, 171, 163, 235, 40, 56, 9, 85, 180, 225, 71, 230, 216, 128, 194, 109, 150, 198, 175, 68, 186, 112, 223, 48, 61, 245, 191, 34, 3, 207, 250, 27, 110, 21, 229, 221, 166, 76, 220, 214, 215, 104, 137, 46, 134, 94, 106, 89, 129, 218, 113, 234, 119, 79, 84, 147, 98, 202, 148, 239, 67, 99, 223, 222, 139, 13, 237, 170, 164, 89, 15, 185, 202, 252, 2, 156, 33, 28, 194, 52, 180, 232, 239, 202, 23, 123, 215, 81, 236, 65, 80, 192, 136, 184, 237, 135, 205, 183, 104, 66, 253, 128, 176, 245, 213, 65, 120, 202, 15, 130, 202, 55, 28, 94, 189, 8, 11, 59, 112, 96, 196, 186, 15, 96, 32, 60, 193, 8, 95, 44, 110, 224, 32, 71, 96, 140, 69, 124, 69, 241, 153, 87, 65, 15, 171, 113, 248, 239, 156, 78, 174, 47, 99, 190, 159, 163, 29, 197, 75, 161, 4, 209, 213, 236, 86, 120, 74, 15, 147, 85, 135, 147, 242, 220, 144, 55, 202, 170, 71, 90, 107, 103, 170, 8, 231, 169, 231, 170, 153, 184, 158, 99, 127, 228, 243, 191, 139, 69, 75, 133, 185, 212, 104, 214, 233, 171, 0, 135, 73, 14, 31, 2, 90, 187, 82, 205, 161, 69, 251, 143, 243, 15, 56, 250, 98, 175, 82, 196, 216, 95, 249, 127, 84, 181, 211, 50, 81, 36, 26, 247, 224, 3, 92, 61, 120, 67, 163, 170, 185, 61, 254, 91, 248, 20, 150, 19, 49, 71, 52, 102, 152, 209, 105, 219, 65, 151, 19, 101, 102, 133, 216, 94, 237, 221, 232, 168, 51, 28, 214, 231, 179, 180, 235, 17, 36, 19, 33, 54, 232, 131, 150, 95, 96, 84, 13, 6, 20, 28, 160, 92, 193, 206, 231, 10, 238, 240, 6, 77, 44, 78, 6, 253, 142, 54, 72, 135, 39, 144, 95, 132, 194, 5, 25, 225, 46, 143, 153, 93, 213, 32, 114, 214, 230, 61, 21, 189, 86, 34, 12, 85, 75, 242, 112, 3, 251, 4, 129, 141, 153, 47, 228, 157, 65, 13, 82, 38, 80, 34, 7, 52, 172, 210, 141, 83, 27, 39, 100, 16, 0, 216, 114, 134, 195, 220, 156, 79, 174, 220, 88, 252, 193, 210, 93, 190, 229, 6, 16, 63, 190, 46, 5, 126, 28, 10, 51, 102, 19, 8, 153, 157, 142, 125, 6, 40, 100, 68, 139, 231, 69, 159, 46, 98, 36, 25, 200, 140, 107, 101, 15, 70, 25, 89, 211, 3, 17, 253, 9, 50, 39, 60, 47, 185, 135, 17, 218, 116, 65, 107, 110, 122, 227, 202, 155, 71, 164, 119, 189, 84, 128, 8, 180, 93, 177, 45, 15, 198, 16, 79, 179, 46, 103, 85, 91, 229, 254, 12, 152, 129, 160, 104, 16, 217, 157, 157, 61, 137, 189, 194, 132, 234, 243, 123, 91, 70, 132, 5, 222, 200, 134, 26, 129, 182, 254, 254, 151, 165, 184, 13, 85, 106, 44, 20, 79, 183, 130, 223, 209, 88, 35, 174, 160, 91, 199, 118, 168, 40, 189, 181, 59, 38, 74, 43, 24, 80, 25, 224, 73, 119, 241, 101, 41, 109, 115, 24, 35, 204, 181, 100, 33, 78, 109, 253, 192, 21, 137, 4, 203, 143, 243, 152, 96, 237, 209, 26, 217, 68, 239, 59, 1, 200, 219, 177, 22, 196, 180, 1, 102, 202, 126, 216, 32, 221, 143, 99, 223, 7, 129, 183, 252, 35, 59, 15, 204, 56, 18, 118, 229, 215, 81, 147, 172, 69, 116, 46, 51, 169, 157, 22, 69, 178, 97, 224, 190, 198, 11, 216, 188, 108, 161, 120, 196, 181, 172, 21, 41, 124, 197, 106, 58, 193, 102, 16, 67, 127, 109, 45, 135, 60, 110, 30, 155, 88, 173, 34, 14, 78, 117, 93, 158, 51, 117, 168, 226, 43, 44, 173, 185, 20, 111, 151, 32, 95, 226, 103, 101, 76, 229, 117, 14, 56, 187, 185, 131, 185, 50, 68, 20, 173, 69, 94, 131, 252, 114, 133, 98, 55, 143, 45, 12, 25, 226, 189, 170, 73, 70, 163, 98, 27, 195, 211, 38, 108, 243, 46, 5, 140, 56, 85, 136, 98, 154, 22, 112, 91, 192, 81, 51, 252, 190, 222, 16, 151, 178, 51, 209, 208, 15, 72, 17, 127, 219, 117, 10, 93, 193, 133, 55, 125, 98, 95, 35, 63, 115, 88, 44, 80, 120, 10, 224, 207, 98, 243, 227, 236, 149, 9, 163, 166, 250, 134, 32, 144, 182, 144, 212, 237, 231, 157, 18, 39, 46, 116, 226, 106, 195, 193, 129, 171, 121, 5, 135, 72, 160, 170, 139, 83, 138, 70, 124, 115, 12, 219, 197, 250, 209, 205, 250, 55, 107, 37, 26, 107, 141, 164, 107, 93, 45, 26, 7, 240, 168, 25, 169, 241, 21, 22, 142, 216, 164, 17, 50, 214, 204, 32, 31, 184, 179, 11, 134, 255, 229, 160, 130, 167, 149, 190, 141, 191, 64, 247, 35, 182, 183, 9, 119, 116, 199, 43, 91, 48, 101, 117, 52, 145, 248, 62, 25, 82, 129, 253, 53, 206, 51, 195, 80, 45, 83, 239, 194, 4, 108, 177, 156, 196, 42, 215, 45, 2, 2, 251, 9, 122, 230, 239, 39, 83, 129, 88, 192, 181, 57, 235, 22, 25, 122, 54, 9, 242, 32, 96, 178, 29, 2, 9, 212, 157, 250, 227, 114, 138, 238, 202, 121, 90, 101, 42, 137, 159, 27, 112, 225, 206, 201, 104, 201, 177, 177, 26, 103, 227, 100, 190, 231, 117, 136, 230, 180, 121, 54, 60, 113, 26, 49, 140, 66, 76, 150, 183, 116, 193, 170, 130, 166, 214, 204, 212, 125, 75, 19, 17, 79, 245, 198, 176, 15, 17, 43, 92, 169, 227, 25, 11, 194, 245, 93, 126, 247, 254, 74, 148, 187, 231, 153, 196, 193, 177, 125, 67, 183, 79, 219, 77, 89, 233, 42, 45, 38, 232, 164, 146, 228, 179, 204, 107, 191, 254, 232, 61, 172, 148, 144, 56, 60, 178, 90, 211, 72, 255, 93, 3, 25, 220, 180, 82, 70, 85, 209, 97, 92, 7, 232, 204, 201, 202, 235, 31, 75, 60, 157, 149, 147, 168, 175, 138, 116, 118, 127, 123, 98, 115, 205, 37, 81, 74, 136, 150, 89, 83, 204, 201, 105, 154, 27, 1, 104, 193, 102, 17, 247, 204, 236, 134, 110, 165, 141, 123, 21, 229, 56, 215, 184, 3, 251, 7, 181, 246, 50, 133, 74, 50, 36, 224, 12, 171, 200, 245, 193, 110, 42, 93, 115, 215, 182, 128, 107, 175, 64, 170, 131, 206, 74, 124, 194, 150, 191, 102, 85, 139, 127, 117, 35, 239, 137, 225, 68, 108, 118, 250, 127, 250, 128, 167, 149, 240, 21, 238, 117, 98, 181, 186, 162, 83, 152, 255, 80, 111, 235, 55, 133, 209, 43, 118, 151, 148, 140, 253, 249, 178, 148, 174, 254, 236, 250, 172, 27, 220, 189, 20, 26, 201, 253, 187, 109, 55, 51, 26, 243, 44, 65, 59, 131, 116, 15, 52, 222, 174, 63, 49, 150, 113, 71, 98, 228, 48, 27, 236, 183, 240, 184, 87, 21, 146, 248, 224, 54, 46, 81, 109, 129, 243, 104, 48, 239, 36, 8, 232, 9, 229, 82, 164, 3, 186, 86, 202, 128, 224, 218, 19, 161, 92, 187, 55, 41, 203, 143, 139, 54, 50, 120, 253, 62, 26, 232, 113, 97, 136, 6, 53, 89, 90, 200, 202, 246, 102, 193, 14, 244, 179, 226, 253, 205, 189, 236, 98, 51, 154, 217, 83, 254, 238, 229, 32, 197, 124, 71, 165, 235, 224, 67, 190, 207, 23, 232, 240, 34, 203, 137, 64, 93, 65, 240, 205, 71, 61, 36, 104, 99, 125, 94, 9, 255, 131, 204, 210, 17, 210, 205, 112, 188, 146, 246, 237, 76, 128, 24, 198, 43, 184, 72, 22, 77, 196, 8, 77, 138, 105, 155, 165, 215, 253, 162, 248, 172, 95, 79, 102, 199, 90, 251, 122, 74, 24, 69, 65, 112, 172, 227, 140, 202, 104, 235, 119, 220, 80, 78, 234, 21, 129, 138, 250, 188, 87, 131, 20, 185, 76, 24, 103, 231, 145, 48, 207, 167, 230, 18, 30, 80, 190, 139, 36, 22, 165, 21, 176, 240, 227, 82, 246, 112, 184, 21, 226, 116, 175, 147, 250, 109, 236, 83, 52, 112, 156, 180, 111, 220, 43, 77, 112, 98, 193, 125, 145, 31, 38, 115, 213, 67, 95, 62, 81, 208, 123, 8, 158, 157, 171, 133, 246, 210, 56, 169, 221, 27, 153, 121, 210, 134, 24, 202, 90, 183, 78, 229, 99, 153, 245, 135, 122, 55, 158, 129, 216, 147, 80, 150, 203, 182, 220, 9, 95, 65, 222, 120, 144, 133, 148, 45, 134, 7, 113, 74, 219, 238, 229, 1, 112, 173, 189, 232, 176, 219, 14, 143, 14, 134, 108, 209, 218, 59, 252, 192, 185, 255, 142, 96, 87, 1, 77, 243, 219, 46, 78, 253, 128, 249, 182, 149, 144, 174, 176, 198, 64, 3, 200, 129, 217, 102, 131, 119, 102, 74, 10, 212, 86, 143, 165, 108, 235, 36, 100, 18, 3, 241, 8, 113, 92, 201, 114, 216, 97, 120, 199, 196, 172, 29, 179, 205, 252, 163, 199, 187, 139, 42, 103, 99, 51, 51, 8, 205, 180, 149, 177, 245, 77, 111, 26, 246, 112, 174, 236, 221, 168, 72, 137, 38, 59, 10, 89, 6, 68, 66, 158, 17, 246, 149, 239, 165, 221, 28, 144, 252, 247, 102, 194, 215, 90, 15, 206, 93, 133, 197, 15, 81, 155, 143, 200, 201, 112, 105, 60, 84, 52, 179, 179, 18, 67, 178, 126, 113, 15, 45, 26, 159, 223, 161, 249, 141, 31, 179, 43, 94, 8, 125, 194, 219, 26, 65, 57, 166, 236, 185, 24, 63, 206, 215, 22, 85, 117, 41, 197, 182, 147, 46, 202, 167, 206, 154, 89, 200, 95, 238, 93, 125, 4, 101, 195, 253, 179, 29, 13, 234, 225, 171, 72, 82, 224, 60, 191, 74, 113, 217, 161, 10, 13, 202, 196, 144, 104, 46, 71, 49, 212, 22, 181, 250, 28, 27, 95, 151, 158, 25, 84, 226, 200] }, final_destination_options: None }), fragment: Some(Ipv6FragmentHeader { next_header: 109, fragment_offset: 2113, more_fragments: true, identification: 5944605 }), auth: None }, ref udp = UdpHeader { source_port: 27523, destination_port: 52161, length: 45869, checksum: 14910 }, ref tcp = TcpHeader { source_port: 17245, destination_port: 46697, sequence_number: 160328470, acknowledgment_number: 2631620014, data_offset: 10, ns: false, fin: false, syn: false, rst: true, psh: false, ack: true, urg: false, ece: true, cwr: false, window_size: 24158, checksum: 53442, urgent_pointer: 8968, options: [Err(UnknownId(173))] }, ref icmpv4 = Icmpv4Header { icmp_type: Unknown { type_u8: 234, code_u8: 221, bytes5to8: [200, 89, 56, 131] }, checksum: 16430 }, ref icmpv6 = Icmpv6Header { icmp_type: Unknown { type_u8: 30, code_u8: 106, bytes5to8: [52, 110, 228, 155] }, checksum: 38251 }, ref payload = [111, 188, 151, 183, 149, 185, 18, 245, 219, 34, 101, 100, 224, 105, 138, 24, 34, 92, 6, 75, 219, 201, 60, 187, 214, 136, 150, 248, 6, 50, 64, 136, 89, 13, 42, 46, 93, 80, 5, 22, 114, 77, 34, 58, 115, 121, 159, 158, 151, 132, 171, 188, 57, 49, 52, 166, 160, 191, 60, 116, 6, 117, 215, 53, 99, 85, 33, 16, 109, 90, 48, 192, 31, 77, 71, 43, 229, 66, 22, 199, 176, 216, 156, 180, 197, 105, 72, 60, 198, 61, 119, 201, 118, 240, 131, 5, 102, 75, 200, 84, 254, 216, 228, 209, 150, 251, 234, 232, 20, 243, 127, 121, 97, 68, 16, 43, 140, 15, 235, 75, 178, 41, 209, 114, 244, 16, 163, 224, 223, 132, 128, 56, 142, 160, 184, 140, 89, 35, 167, 84, 217, 209, 200, 3, 120, 124, 220, 113, 169, 39, 64, 82, 255, 81, 239, 172, 199, 48, 179, 102, 109, 53, 167, 253, 203, 114, 225, 103, 233, 1, 72, 29, 178, 90, 44, 246, 248, 43, 137, 46, 5, 250, 25, 94, 155, 183, 46, 229, 121, 120, 16, 105, 40, 15, 168, 29, 93, 71, 42, 36, 179, 253, 67, 132, 81, 196, 190, 165, 130, 54, 57, 212, 240, 76, 252, 175, 147, 200, 18, 179, 196, 82, 9, 135, 197, 217, 12, 60, 130, 144, 129, 206, 133, 122, 183, 87, 194, 149, 79, 206, 67, 178, 51, 38, 60, 143, 132, 9, 221, 193, 27, 31, 145, 245, 137, 134, 248, 231, 68, 211, 125, 22, 234, 78, 231, 119, 27, 241, 143, 43, 173, 231, 117, 180, 255, 230, 138, 68, 233, 225, 184, 16, 132, 168, 65, 84, 177, 210, 183, 55, 188, 216, 82, 7, 137, 1, 81, 69, 14, 104, 82, 239, 73, 218, 70, 196, 163, 59, 183, 151, 95, 197, 81, 49, 97, 162, 96, 9, 95, 254, 137, 252, 100, 190, 218, 124, 130, 82, 32, 154, 253, 44, 253, 58, 149, 116, 45, 82, 104, 103, 119, 42, 175, 208, 203, 25, 65, 154, 218, 222, 22, 148, 94, 5, 226, 217, 158, 148, 30, 84, 36, 142, 214, 166, 176, 62, 198, 178, 94, 205, 220, 155, 5, 86, 48, 167, 114, 108, 210, 127, 105, 247, 106, 30, 77, 100, 149, 109, 139, 60, 174, 121, 24, 203, 35, 163, 15, 212, 151, 206, 94, 134, 28, 253, 192, 66, 12, 167, 45, 146, 101] cc 8a9b0a970a9a2fe38d925b7ce0f4ff73ba262d7258e2d20e1ff7c9251885a396 # shrinks to ref eth = Ethernet2Header { source: [0, 0, 0, 0, 0, 0], destination: [0, 0, 0, 0, 0, 0], ether_type: 0x0000 }, ref vlan_outer = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref vlan_inner = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0806 (Address Resolution Protocol (ARP)) }, ref ipv4 = Ipv4Header { dscp: Ipv4Dscp(0), ecn: Ipv4Ecn(0), total_len: 20, identification: 0, dont_fragment: false, more_fragments: false, fragment_offset: IpFragOffset(0), time_to_live: 0, protocol: 29 (ISO-TP4 - ISO Transport Protocol Class 4), header_checksum: 0, source: [0, 0, 0, 0], destination: [0, 0, 0, 0], options: [] }, ref ipv4_exts = Ipv4Extensions { auth: Some(IpAuthHeader { next_header: 24 (TRUNK-2 - Trunk-2), spi: 2509063036, sequence_number: 3755394165, raw_icv: [92, 181, 195, 73, 130, 142, 140, 140, 223, 45, 197, 36, 9, 69, 55, 43, 149, 231, 9, 4, 120, 102, 160, 165, 107, 87, 64, 204, 231, 179, 197, 165, 115, 145, 144, 125, 94, 30, 168, 176, 19, 63, 236, 79, 115, 199, 15, 92, 162, 106, 139, 90, 248, 240, 127, 37, 144, 179, 147, 224, 57, 41, 152, 198, 117, 161, 199, 197, 98, 23, 220, 31, 79, 25, 99, 100, 171, 127, 4, 248, 29, 2, 189, 178, 206, 124, 247, 24, 51, 75, 240, 246, 123, 220, 42, 147, 91, 187, 218, 92, 90, 171, 47, 1, 54, 19, 58, 73, 82, 207, 143, 83, 100, 150, 44, 228, 214, 246, 111, 239, 107, 35, 187, 109, 244, 118, 33, 58, 192, 37, 195, 191, 74, 82, 149, 2, 25, 245, 127, 172, 34, 72, 200, 6, 37, 121, 147, 136, 76, 40, 209, 25, 202, 208, 118, 230, 122, 151, 38, 140, 238, 68, 101, 149, 46, 233, 83, 180, 105, 169, 213, 123, 189, 109, 9, 214, 192, 238, 95, 177, 223, 71, 103, 7, 243, 54, 156, 100, 138, 195, 151, 91, 193, 151, 177, 82, 165, 16, 106, 253, 90, 76, 0, 204, 20, 190, 229, 54, 147, 227, 190, 57, 233, 143, 98, 114, 82, 106, 157, 134, 187, 18, 66, 21, 220, 135, 175, 76, 33, 91, 242, 181, 47, 74, 206, 252, 84, 28, 248, 36, 94, 8, 124, 219, 150, 145, 213, 192, 233, 11, 56, 72, 201, 140, 48, 93, 198, 77, 26, 31, 208, 179, 191, 185, 195, 83, 39, 3, 110, 92, 146, 229, 222, 36, 53, 176, 178, 49, 44, 110, 75, 8, 104, 135, 52, 26, 50, 4, 183, 70, 84, 67, 8, 76, 25, 45, 106, 225, 33, 107, 142, 35, 90, 100, 141, 25, 202, 82, 41, 43, 49, 103, 33, 202, 244, 125, 123, 69, 118, 118, 118, 72, 244, 53, 71, 161, 133, 88, 131, 144, 109, 3, 60, 40, 166, 109, 206, 115, 18, 97, 172, 157, 50, 144, 179, 3, 7, 154, 121, 56, 138, 174, 242, 151, 95, 245, 204, 195, 241, 62, 200, 106, 28, 205, 27, 201, 190, 66, 12, 127, 70, 30, 227, 173, 225, 6, 142, 228, 215, 244, 138, 130, 171, 59, 180, 180, 127, 145, 1, 228, 40, 222, 36, 43, 141, 74, 226, 229, 46, 9, 101, 229, 99, 111, 102, 188, 153, 86, 118, 111, 1, 235, 176, 218, 83, 130, 14, 60, 142, 42, 26, 99, 245, 73, 213, 247, 185, 176, 118, 29, 139, 236, 218, 167, 13, 129, 124, 105, 194, 63, 95, 95, 197, 97, 65, 41, 221, 32, 122, 48, 218, 198, 95, 95, 124, 6, 2, 84, 160, 96, 128, 125, 172, 240, 53, 131, 49, 10, 254, 166, 16, 127, 66, 232, 206, 172, 31, 24, 115, 213, 172, 29, 96, 116, 88, 163, 135, 121, 233, 223, 63, 65, 45, 18, 119, 145, 192, 246, 94, 235, 25, 83, 174, 244, 215, 135, 229, 230, 216, 101, 146, 157, 118, 91, 43, 16, 196, 2, 167, 72, 139, 20, 105, 112, 167, 191, 83, 233, 43, 43, 254, 199, 49, 68, 69, 91, 192, 153, 244, 15, 59, 119, 80, 51, 129, 220, 154, 172, 193, 147, 59, 241, 215, 70, 224, 110, 21, 33, 34, 216, 177, 174, 227, 84, 38, 181, 249, 95, 87, 152, 165, 132, 95, 134, 37, 228] }) }, ref ipv6 = Ipv6Header { traffic_class: 15, flow_label: Ipv6FlowLabel(377208), payload_length: 39375, next_header: 11 (NVP-II - Network Voice Protocol), hop_limit: 164, source: [130, 247, 84, 229, 201, 228, 210, 155, 39, 71, 113, 56, 54, 232, 243, 221], destination: [42, 1, 131, 202, 153, 22, 76, 246, 14, 164, 20, 54, 68, 234, 189, 145] }, ref ipv6_exts = Ipv6Extensions { hop_by_hop_options: Some(Ipv6RawExtHeader { next_header: 60 (IPv6-Opts - Destination Options for IPv6), payload: [62, 47, 61, 126, 20, 254, 36, 110, 35, 19, 117, 163, 115, 86, 52, 46, 27, 154, 128, 33, 173, 71, 49, 222, 253, 62, 14, 57, 125, 78, 228, 70, 248, 103, 253, 195, 35, 6, 89, 220, 194, 161, 100, 221, 94, 202, 232, 176, 37, 104, 84, 204, 136, 200, 112, 93, 234, 1, 245, 194, 81, 94, 114, 17, 71, 224, 146, 87, 167, 234, 170, 48, 69, 136, 130, 154, 143, 122, 81, 104, 95, 50, 213, 82, 77, 182, 60, 31, 192, 201, 244, 231, 252, 125, 130, 59, 128, 141, 185, 41, 125, 190, 138, 143, 72, 81, 15, 3, 56, 216, 52, 209, 87, 197, 249, 92, 165, 226, 237, 205, 210, 3, 182, 13, 89, 244, 39, 149, 201, 75, 104, 25, 209, 124, 87, 125, 44, 51, 84, 70, 144, 225, 60, 8, 220, 2, 47, 131, 62, 183, 243, 165, 43, 20, 222, 245, 223, 171, 74, 184, 83, 63, 66, 200, 185, 58, 27, 172, 193, 98, 113, 56, 199, 151, 136, 20, 241, 232, 212, 165, 82, 187, 209, 172, 128, 153, 175, 140, 151, 178, 207, 210, 232, 57, 124, 187, 90, 140, 2, 203, 59, 81, 175, 81, 242, 125, 217, 19, 67, 109, 148, 37, 253, 127, 22, 118, 105, 86, 190, 137, 172, 53, 96, 87, 166, 105, 151, 59, 192, 91, 76, 58, 186, 14, 61, 133, 213, 36, 178, 48, 71, 226, 35, 225, 153, 252, 43, 154, 118, 6, 58, 243, 47, 180, 225, 73, 137, 234, 81, 56, 91, 175, 90, 151, 177, 210, 6, 73, 250, 9, 158, 217, 181, 141, 37, 184, 233, 148, 143, 13, 110, 161, 112, 191, 234, 14, 69, 54, 88, 227, 6, 43, 248, 36, 191, 157, 200, 209, 170, 189, 46, 203, 128, 37, 247, 62, 31, 116, 145, 11, 91, 150, 186, 252, 188, 28, 195, 202, 242, 240, 5, 204, 27, 102, 36, 94, 55, 244, 238, 9, 199, 126, 145, 192, 77, 88, 154, 197, 74, 186, 94, 81, 172, 19, 213, 130, 78, 98, 160, 53, 199, 70, 150, 17, 167, 36, 138, 239, 164, 89, 252, 210, 188, 230, 252, 180, 138, 105, 82, 71, 221, 160, 48, 40, 54, 168, 39, 153, 124, 210, 125, 202, 57, 145, 202, 186, 49, 218, 153, 18, 230, 234, 222, 250, 125, 160, 11, 228, 185, 111, 15, 183, 24, 54, 112, 220, 188, 185, 132, 199, 20, 9, 81, 53, 195, 100, 208, 40, 167, 241, 8, 215, 70, 112, 245, 242, 210, 225, 54, 113, 80, 45, 123, 44, 8, 54, 64, 21, 176, 55, 26, 47, 197, 145, 107, 144, 28, 110, 182, 27, 0, 1, 47, 166, 244, 61, 0, 10, 116, 36, 106, 85, 214, 49, 37, 215, 25, 113, 22, 34, 49, 194, 206, 54, 75, 250, 86, 72, 15, 180, 147, 202, 211, 42, 192, 83, 95, 133, 78, 13, 26, 51, 176, 183, 149, 132, 51, 13, 173, 190, 205, 236, 235, 28, 244, 110, 185, 136, 10, 52, 244, 24, 23, 202, 51, 101, 139, 150, 79, 118, 138, 55, 60, 90, 146, 20, 136, 15, 253, 103, 24, 159, 155, 229, 119, 121, 21, 60, 168, 62, 95, 95, 46, 230, 165, 224, 255, 68, 80, 233, 89, 185, 215, 238, 33, 186, 174, 233, 170, 65, 205, 66, 127, 30, 85, 119, 248, 136, 163, 213, 15, 159, 132, 160, 58, 4, 155, 47, 52, 166, 16, 195, 116, 156, 167, 25, 125, 160, 101, 29, 96, 11, 191, 192, 20, 96, 166, 203, 40, 233, 187, 197, 199, 27, 21, 152, 63, 26, 61, 148, 11, 101, 175, 137, 233, 155, 41, 178, 106, 177, 178, 138, 80, 93, 114, 47, 24, 166, 30, 109, 48, 54, 243, 44, 214, 250, 146, 62, 145, 212, 244, 227, 10, 68, 35, 137, 172, 242, 219, 175, 173, 66, 92, 87, 110, 244, 41, 10, 67, 161, 217, 164, 140, 199, 197, 195, 20, 165, 89, 133, 9, 128, 171, 67, 127, 142, 207, 60, 0, 69, 127, 52, 26, 222, 36, 254, 236, 223, 172, 209, 155, 199, 13, 241, 158, 249, 196, 185, 173, 37, 195, 94, 15, 183, 129, 51, 81, 28, 244, 179, 196, 200, 248, 33, 72, 202, 238, 176, 25, 197, 83, 42, 224, 214, 41, 11, 187, 211, 100, 57, 245, 80, 135, 213, 174, 58, 16, 1, 98, 13, 162, 178, 13, 61, 55, 165, 150, 209, 62, 115, 88, 178, 168, 2, 245, 21, 231, 221, 84, 131, 243, 0, 99, 193, 157, 227, 244, 41, 47, 127, 151, 23, 30, 83, 189, 104, 239, 97, 111, 79, 213, 165, 19, 75, 237, 148, 161, 132, 81, 29, 197, 193, 199, 119, 189, 102, 227, 122, 38, 110, 186, 178, 234, 41, 116, 127, 123, 65, 110, 128, 171, 65, 10, 156, 131, 186, 49, 112, 22, 189, 222, 27, 95, 65, 233, 51, 151, 52, 43, 185, 171, 194, 108, 200, 196, 8, 77, 238, 81, 37, 195, 19, 220, 248, 177, 227, 141, 76, 1, 150, 158, 187, 133, 49, 147, 64, 151, 48, 212, 97, 241, 153, 118, 8, 253, 119, 125, 8, 136, 228, 54, 174, 147, 63, 68, 219, 79, 5, 56, 43, 16, 144, 47, 79, 171, 180, 66, 142, 186, 132, 44, 50, 172, 44, 118, 96, 150, 161, 174, 218, 68, 217, 94, 113, 33, 49, 116, 13, 161, 46, 48, 206, 28, 53, 200, 208, 243, 104, 97, 204, 186, 126, 153, 70, 141, 149, 28, 189, 206, 100, 101, 179, 183, 106, 15, 243, 49, 90, 119, 183, 222, 49, 244, 50, 218, 16, 186, 19, 153, 141, 2, 210, 227, 45, 191, 60, 50, 226, 254, 160, 109, 3, 24, 189, 138, 220, 230, 73, 51, 61, 238, 228, 242, 183, 71, 166, 59, 183, 32, 223, 40, 233, 218, 48, 2, 215, 103, 222, 229, 8, 134, 145, 136, 143, 66, 139, 95, 212, 59, 173, 225, 105, 138, 189, 234, 113, 174, 7, 232, 207, 226, 74, 50, 247, 212, 76, 237, 177, 103, 145, 92, 3, 41, 13, 139, 149, 187, 128, 43, 202, 130, 94, 25, 82, 250, 220, 192, 164, 148, 221, 98, 94, 105, 178, 60, 20, 142, 231, 23, 76, 136, 12, 85, 180, 85, 75, 134, 195, 147, 194, 211, 59, 183, 85, 127, 123, 195, 153, 201, 197, 37, 212, 160, 39, 68, 229, 244, 71, 79, 248, 77, 84, 130, 71, 155, 4, 141, 158, 211, 173, 62, 87, 146, 15, 3, 122, 154, 175, 34, 158, 255, 137, 84, 218, 30, 59, 182, 59, 193, 92, 13, 174, 243, 178, 227, 194, 228, 41, 12, 121, 71, 170, 15, 201, 10, 155, 108, 48, 122, 69, 246, 158, 192, 155, 67, 100, 254, 16, 151, 85, 69, 24, 6, 254, 13, 94, 134, 144, 16, 5, 112, 80, 235, 159, 73, 15, 87, 114, 216, 36, 8, 168, 11, 215, 188, 210, 243, 85, 78, 96, 27, 242, 83, 32, 207, 27, 191, 176, 75, 76, 50, 200, 104, 154, 228, 203, 31, 232, 105, 79, 78, 89, 63, 146, 52, 108, 114, 8, 186, 201, 229, 162, 179, 214, 46, 177, 173, 146, 84, 73, 21, 79, 22, 137, 187, 240, 72, 47, 88, 165, 195, 244, 133, 27, 252, 218, 149, 50, 231, 92, 253, 198, 255, 51, 214, 53, 147, 189, 100, 71, 174, 39, 17, 247, 177, 185, 8, 181, 160, 139, 211, 223, 172, 33, 73, 116, 108, 182, 248, 82, 39, 215, 136, 26, 141, 159, 226, 129, 99, 232, 155, 174, 28, 115, 117, 119, 231, 30, 173, 235, 156, 13, 161, 1, 52, 174] }), destination_options: Some(Ipv6RawExtHeader { next_header: 44 (IPv6-Frag - Fragment Header for IPv6), payload: [32, 35, 195, 46, 88, 189, 22, 199, 230, 162, 123, 89, 66, 218, 24, 103, 194, 32, 107, 137, 196, 98, 188, 82, 148, 190, 188, 13, 7, 214, 106, 129, 158, 134, 232, 135, 109, 7, 40, 241, 74, 227, 139, 81, 143, 182, 224, 235, 137, 14, 81, 157, 200, 186, 195, 164, 166, 184, 143, 252, 54, 118, 10, 11, 253, 83, 239, 71, 139, 127, 25, 28, 91, 186, 238, 170, 141, 38, 47, 164, 229, 109, 243, 71, 182, 25, 136, 223, 183, 40, 91, 105, 9, 169, 64, 149, 69, 103, 60, 81, 192, 20, 161, 114, 111, 222, 211, 64, 255, 79, 20, 28, 205, 59, 6, 58, 243, 42, 228, 120, 180, 47, 106, 177, 89, 25, 53, 159, 172, 159, 96, 119, 139, 68, 27, 234, 249, 37, 174, 0, 78, 138, 48, 181, 7, 58, 62, 134, 209, 152, 63, 7, 167, 33, 48, 0, 204, 240, 31, 135, 59, 245, 179, 144, 36, 71, 18, 25, 132, 86, 133, 135, 186, 30, 198, 189, 95, 143, 189, 141, 108, 134, 202, 110, 171, 178, 92, 230, 238, 255, 155, 7, 233, 217, 134, 190, 33, 127, 176, 90, 11, 146, 61, 11, 79, 196, 173, 41, 88, 125, 15, 124, 8, 68, 88, 145, 171, 125, 177, 115, 162, 157, 127, 171, 41, 209, 86, 179, 79, 69, 28, 197, 177, 11, 211, 170, 162, 45, 123, 46, 59, 112, 11, 132, 84, 167, 203, 152, 42, 204, 198, 204, 248, 88, 61, 195, 248, 241, 145, 19, 231, 207, 226, 95, 130, 216, 147, 124, 19, 2, 55, 69, 223, 30, 178, 166, 99, 76, 122, 6, 136, 36, 19, 123, 147, 144, 105, 205, 243, 85, 216, 117, 77, 151, 122, 1, 161, 20, 133, 47, 0, 23, 127, 205, 49, 146, 136, 12, 98, 52, 141, 4, 237, 120, 230, 103, 248, 181, 37, 105, 127, 1, 174, 183, 137, 187, 134, 119, 102, 124, 195, 229, 3, 111, 49, 203, 33, 22, 18, 110, 11, 163, 202, 92, 158, 15, 187, 172, 222, 135, 79, 72, 178, 135, 131, 248, 213, 21, 243, 106, 98, 144, 114, 194, 103, 175, 69, 182, 57, 15, 93, 146, 221, 86, 173, 19, 112, 155, 52, 74, 82, 104, 174, 125, 47, 203, 64, 77, 230, 90, 110, 37, 36, 254, 210, 96, 29, 161, 124, 195, 83, 164, 43, 200, 101, 65] }), routing: None, fragment: Some(Ipv6FragmentHeader { next_header: 51 (AH - Authentication Header), fragment_offset: IpFragOffset(40), more_fragments: false, identification: 936320599 }), auth: Some(IpAuthHeader { next_header: 50 (ESP - Encap Security Payload), spi: 1791434917, sequence_number: 265966117, raw_icv: [226, 4, 122, 88, 130, 221, 201, 80, 210, 94, 96, 230, 244, 224, 150, 80, 90, 103, 75, 80, 142, 100, 22, 49, 19, 18, 103, 246, 4, 136, 202, 81, 234, 216, 135, 16, 59, 70, 188, 72, 16, 69, 103, 133, 58, 104, 118, 132, 203, 4, 197, 196, 209, 61, 43, 216, 193, 158, 252, 235, 62, 176, 46, 22, 85, 121, 196, 55, 203, 99, 107, 202, 139, 163, 4, 43, 215, 57, 61, 135, 76, 79, 100, 179, 207, 129, 248, 136, 109, 67, 122, 133, 83, 221, 197, 195, 207, 30, 196, 26, 122, 186, 45, 90, 27, 145, 130, 62, 159, 10, 18, 250, 179, 135, 90, 159, 56, 160, 141, 78, 234, 181, 54, 177, 30, 122, 102, 236, 57, 143, 72, 148, 169, 129, 165, 114, 216, 73, 88, 62, 207, 82, 87, 241, 130, 203, 224, 142, 244, 149, 108, 61, 147, 60, 45, 129, 140, 184, 248, 87, 128, 163, 202, 235, 5, 215, 128, 67, 251, 220, 80, 196, 27, 25, 114, 119, 216, 197, 29, 212, 42, 18, 143, 89, 7, 247, 195, 83, 206, 210, 81, 214, 42, 59, 42, 130, 98, 192, 168, 195, 208, 68, 155, 172, 68, 69, 214, 85, 223, 245, 217, 63, 159, 119, 114, 250, 162, 118, 227, 52, 135, 81, 254, 127, 79, 91, 236, 233, 41, 194, 143, 216, 26, 116, 33, 163, 141, 4, 218, 81, 169, 168, 223, 212, 84, 75, 241, 47, 148, 84, 170, 192, 125, 251, 4, 131, 113, 185, 170, 168, 243, 250, 238, 179, 55, 77, 112, 231, 69, 109, 232, 134, 1, 33, 46, 205, 222, 34, 75, 161, 47, 132, 19, 253, 16, 76, 213, 108, 218, 211, 25, 136, 167, 246, 153, 246, 40, 30, 238, 57, 153, 123, 156, 86, 131, 132, 40, 220, 143, 242, 217, 198, 197, 236, 155, 15, 142, 157, 24, 204, 2, 222, 185, 172, 224, 226, 91, 159, 15, 16, 197, 136, 229, 78, 78, 198, 54, 7, 255, 197, 179, 160, 176, 177, 50, 55, 28, 32, 203, 232, 251, 177, 129, 92, 29, 185, 90, 69, 125, 146, 205, 109, 51, 35, 75, 233, 189, 10, 133, 177, 73, 14, 65, 151, 56, 152, 214, 74, 139, 135, 139, 17, 108, 66, 201, 87, 117, 52, 37, 76, 247, 158, 129, 84, 28, 248, 46, 32, 115, 4, 94, 0, 244, 14, 245, 68, 38, 142, 6, 47, 6, 224, 158, 18, 95, 142, 250, 64, 229, 178, 176, 251, 50, 182, 245, 206, 90, 184, 111, 198, 240, 81, 177, 44, 78, 169, 175, 170, 135, 207, 192, 171, 199, 228, 164, 111, 18, 53, 253, 123, 38, 44, 175, 190, 78, 113, 65, 146, 97, 180, 141, 90, 160, 227, 222, 46, 156, 64, 42, 104, 216, 208, 114, 2, 181, 123, 194, 176, 188, 122, 235, 223, 224, 74, 157, 182, 84, 172, 150, 234, 99, 166, 39, 103, 242, 247, 219, 75, 223, 92, 154, 54, 80, 238, 226, 18, 17, 81, 108, 162, 87, 51, 128, 239, 96, 174, 56, 51, 122, 130, 143, 178, 140, 188, 153, 141, 120, 4, 183, 54, 42, 87, 158, 20, 182, 136, 248, 40, 154, 108, 116, 143, 167, 140, 77, 136, 212, 23, 29, 218, 246, 248, 129, 103, 33, 99, 195, 33, 13, 56, 64, 230, 120, 141, 99, 102, 102, 37, 236, 166, 214, 165, 235, 52, 104, 22, 229, 182, 167, 107, 251, 158, 192, 184, 7, 13, 245, 144, 39, 201, 228, 99, 92, 49, 182, 183, 195, 101, 35, 221, 59, 253, 174, 138, 130, 87, 167, 231, 233, 117, 244, 39, 187, 98, 254, 13, 210, 71, 236, 231, 166, 108, 172, 157, 216, 176, 180, 210, 181, 195, 86, 135, 86, 73, 56, 249, 192, 135, 242, 237, 216, 74, 173, 153, 12, 204, 187, 143, 83, 118, 243, 110, 115, 190, 151, 193, 229, 2, 88, 190, 87, 81, 35, 14, 161, 112, 126, 69, 134, 55, 18, 201, 217, 9, 189, 223, 205, 149, 231, 44, 132, 24, 78, 1, 191, 34, 43, 122, 200, 83, 124, 98, 99, 21, 118, 217, 215, 181, 129, 38, 177, 231, 251, 206, 149, 119, 177, 246, 198, 95, 50, 131, 253, 147, 97, 75, 132, 96, 64, 88, 182, 56, 193, 142, 147, 198, 62, 190, 157, 230, 229, 35, 193, 150, 46, 88, 250, 176, 190, 152, 239, 129, 162, 208, 38, 204, 42, 11, 118, 69, 82, 159, 60, 192, 82, 69, 57, 81, 45, 63, 106, 129, 200, 70, 234, 232, 252, 230, 87, 122, 17, 83, 147, 7, 255, 195, 30, 22, 246, 238, 139, 155, 59, 219, 75, 176, 174, 135, 176, 86, 12, 6, 161, 96, 112, 119, 226, 90, 144, 38, 248, 39, 170, 9, 183, 76, 227, 22, 162, 241, 140, 31, 62, 134, 157, 28, 72, 154, 128, 47, 30, 93, 184, 234, 163, 117, 10, 47, 30, 174, 53, 247, 20, 142, 66, 87, 22, 152, 208, 79, 240, 92, 45, 156, 43, 175, 81, 122, 48, 155, 190, 155, 25, 188, 231, 135, 192, 234, 227, 237, 240, 41, 125, 28, 209, 125, 74, 76, 241, 81, 212, 255, 255, 20, 68, 137, 24, 100, 128, 25, 50, 180, 168, 154, 114, 205, 45, 3, 174, 21, 4, 220, 135, 233, 62, 215, 141, 123, 223, 19, 125, 9, 143, 19, 60, 98, 183, 219, 95, 45, 234, 188, 253, 112, 172, 187, 240, 193, 124, 201, 174, 21, 55, 220, 35, 113, 245, 76, 160, 174, 56, 255, 2, 242, 69, 206, 165, 79, 245, 98, 66, 100, 92, 20, 140, 133, 37, 152, 221, 124, 43, 242, 116, 153, 82, 21, 120, 145, 106, 218, 47, 131, 244, 98, 18, 177, 186, 102, 221, 164, 13, 220, 210, 251, 199, 183, 204, 93, 203, 122] }) }, ref udp = UdpHeader { source_port: 37417, destination_port: 9108, length: 55031, checksum: 30539 }, ref tcp = TcpHeader { source_port: 890, destination_port: 63900, sequence_number: 1007304455, acknowledgment_number: 4179598739, ns: false, fin: false, syn: false, rst: true, psh: true, ack: true, urg: true, ece: false, cwr: false, window_size: 26948, checksum: 44775, urgent_pointer: 49253, options: [Err(UnknownId(91))] }, ref icmpv4 = Icmpv4Header { icmp_type: Unknown { type_u8: 115, code_u8: 63, bytes5to8: [197, 108, 248, 120] }, checksum: 2225 }, ref icmpv6 = Icmpv6Header { icmp_type: Unknown { type_u8: 78, code_u8: 128, bytes5to8: [71, 156, 20, 196] }, checksum: 11773 }, ref payload = [167, 22, 235, 99, 149, 6, 145, 225, 240, 172, 90, 57, 224, 202, 18, 160, 59, 126, 6, 142, 246, 221, 178, 142, 119, 149, 118, 24, 85, 202, 49, 206, 46, 138, 89, 120, 205, 44, 146, 68, 109, 183, 62, 194, 214, 224, 242, 51, 244, 121, 41, 30, 100, 88, 113, 252, 247, 30, 5, 51, 65, 174, 180, 40, 186, 204, 107, 12, 94, 171, 77, 238, 219, 1, 48, 192, 233, 160, 118, 122, 231, 175, 205, 222, 71, 3, 152, 89, 179, 124, 235, 147, 214, 240, 151, 170, 249, 41, 219, 41, 174, 39, 72, 81, 93, 33, 92, 38, 81, 230, 203, 184, 62, 40, 164, 56, 17, 246, 208, 119, 53, 119, 56, 57, 208, 123, 242, 126, 113, 173, 155, 254, 34, 97, 224, 150, 30, 183, 26, 88, 233, 142, 107, 189, 73, 181, 188, 88, 191, 151, 196, 115, 94, 128, 216, 121, 206, 65, 184, 150, 7, 85, 157, 169, 48, 0, 91, 59, 140, 20, 61, 231, 19, 43, 25, 8, 45, 153, 139, 171, 203, 72, 129, 96, 125, 200, 107, 24, 66, 116, 196, 166, 70, 84, 100, 252, 148, 198, 140, 190, 179, 227, 171, 217, 73, 201, 196, 252, 103, 198, 139, 22, 44, 141, 44, 49, 115, 210, 114, 152, 85, 78, 192, 14, 153, 14, 216, 154, 194, 198, 148, 217, 216, 28, 163, 245, 132, 125, 198, 159, 156, 92, 241, 20, 198, 42, 79, 230, 130, 38, 126, 161, 11, 92, 79, 203, 0, 104, 164, 106, 146, 111, 205, 110, 59, 18, 211, 10, 1, 215, 152, 28, 161, 31, 213, 126, 52, 199, 37, 225, 26, 181, 172, 222, 157, 154, 89, 215, 89, 91, 22, 86, 209, 30, 49, 149, 245, 117, 42, 128, 12, 38, 228, 11, 123, 4, 20, 233, 167, 243, 162, 158, 85, 20, 26, 13, 56, 54, 140, 115, 124, 233, 205, 48, 216, 209, 24, 245, 201, 81, 124, 205, 226, 125, 168, 248, 160, 149, 105, 214, 105, 200, 88, 229, 231, 148, 61, 84, 108, 111, 227, 248, 41, 155, 141, 56, 50, 10, 62, 239, 227, 153, 54, 253, 167, 244, 2, 96, 251, 214, 151, 51, 26, 240, 151, 251, 49, 68, 199, 115, 158, 72, 220, 201, 221, 238, 235, 163, 62, 221, 143, 228, 206, 176, 232, 134, 88, 55, 239, 9, 16, 20, 170, 132, 185, 192, 183, 227, 25, 192, 190, 109, 197, 130, 244, 221, 246, 129, 185, 45, 83, 250, 230, 19, 17, 167, 53, 2, 58, 206, 250, 223, 172, 35, 161, 236, 241, 62, 48, 120, 252, 211, 64, 57, 197, 163, 207, 224, 97, 67, 98, 225, 232, 193, 3, 173, 63, 83, 208, 180, 85, 133, 217, 177, 127, 4, 83, 113, 190, 79, 44, 36, 135, 204, 213, 181, 92, 154, 81, 8, 96, 191, 91, 5, 156, 218, 86, 145, 145, 223, 3, 111, 113, 118, 201, 40, 188, 31, 173, 75, 75, 239, 52, 253, 125, 88, 33, 169, 110, 187, 46, 103, 172, 70, 65, 160, 214, 149, 119, 36, 247, 215, 247, 91, 174, 111, 33, 180, 116, 196, 18, 142, 170, 40, 254, 155, 252, 96, 233, 59, 227, 218, 60, 219, 50, 183, 191, 19, 217, 175, 91, 226, 201, 246, 119, 151, 30, 179, 161, 243, 140, 221, 241, 144, 7, 80, 218, 64, 130, 44, 149, 253, 30, 64, 102, 58, 7, 184, 195, 16, 22, 127, 220, 129, 169, 45, 227, 162, 222, 144, 129, 10, 214, 191, 198, 12, 226, 227, 96, 96, 206, 101, 42, 110, 187, 140, 235, 160, 180, 201, 233, 146, 73, 67, 88, 81, 159, 116, 121, 73, 152, 161, 155, 241, 104, 71, 192, 252, 199, 162, 135, 203, 243, 38, 156, 198, 220, 13, 77, 57, 78, 40, 30, 127, 227, 104, 95, 81, 58, 156, 141, 193, 142, 36, 243, 57, 42, 43, 255, 72, 3, 101, 2, 124, 126, 77, 225, 190, 211, 134, 82, 180, 50, 0, 160, 155, 232, 219, 242, 36, 57, 46, 165, 60, 68, 123, 196, 144, 153, 209, 191, 62, 224, 188, 250, 211, 179, 157, 71, 164, 218, 132, 202, 63, 21, 83, 68, 132, 213, 204, 189, 169, 126, 242, 176, 93, 211, 209, 164, 134, 153, 161, 22, 134, 197, 184, 105, 191, 186, 152, 145, 133, 119, 183, 65, 55, 248, 204, 31, 28, 172, 85, 228, 86, 235, 47, 63, 235, 254, 93, 132, 86, 70, 233, 216, 2, 63, 26, 82, 45, 10, 182, 171, 13, 142, 142, 154, 50, 15, 133, 65, 137, 197, 3, 209, 20, 126, 93, 195, 249, 135, 44, 114, 55, 115, 33, 231, 195, 252, 42, 143, 181, 34, 52, 2, 183, 132, 124, 255, 83, 13, 19, 99, 236, 138, 64, 54, 176, 106, 29, 46, 222, 141, 142, 26, 167, 207, 104, 82, 178, 150, 26, 2, 59, 105, 37, 10, 229, 93, 30, 46, 204, 55, 56, 4, 29, 8, 212, 178, 77, 221, 53, 222, 191, 172, 85, 127, 159, 44, 73, 37, 251, 153, 173, 134, 13, 224, 164, 23, 35, 153, 190, 22, 22, 52, 28, 6, 28, 112, 117, 123, 216, 141, 98, 47, 140, 113, 1, 84, 236, 144, 94, 7, 71, 16, 196, 144, 96, 129, 29, 209, 66, 237, 67, 224, 10, 16, 160, 244, 217, 145, 241, 58, 83, 30, 77, 179, 140, 124, 243, 97, 121, 10, 54, 1, 30, 11, 116, 127, 188, 15, 122, 29, 133, 100, 178, 110, 201, 188, 63, 216, 168, 30, 6, 55, 127, 57, 255, 158, 222, 190, 110, 23, 36, 64, 230, 20, 155, 114, 16, 4, 60, 7, 71, 18, 44, 110, 66, 47, 217, 153, 202, 72, 66, 158, 204, 97, 44, 107, 172] cc dc17582d10107fd94fddbed59a7cf5c4208a8b26c523d0cc916aee7e88bec69c # shrinks to ref eth = Ethernet2Header { source: [0, 0, 0, 0, 0, 0], destination: [0, 0, 0, 0, 0, 0], ether_type: 0x0000 }, ref vlan0 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref vlan1 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref vlan2 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref macsec0 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref macsec1 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref macsec2 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref ipv4 = Ipv4Header { dscp: Ipv4Dscp(42), ecn: Ipv4Ecn(1), total_len: 52204, identification: 44039, dont_fragment: false, more_fragments: false, fragment_offset: IpFragOffset(4556), time_to_live: 151, protocol: 221, header_checksum: 30718, source: [7, 225, 117, 228], destination: [23, 66, 165, 207], options: [] }, ref ipv4_exts = Ipv4Extensions { auth: Some(IpAuthHeader { next_header: 232, spi: 493231867, sequence_number: 3478019815, raw_icv: [85, 93, 40, 183, 94, 65, 80, 220, 128, 79, 196, 241, 63, 88, 131, 191, 146, 253, 53, 179, 71, 188, 51, 177, 27, 57, 41, 215, 66, 34, 251, 202, 162, 238, 250, 174, 198, 118, 130, 49, 97, 178, 170, 199, 100, 17, 54, 20, 237, 1, 212, 38, 237, 230, 154, 201, 135, 82, 210, 123, 101, 235, 35, 37, 223, 40, 127, 157, 246, 113, 60, 37, 80, 253, 134, 228, 72, 165, 149, 169, 216, 124, 247, 31, 208, 99, 21, 167, 183, 94, 139, 33, 179, 184, 146, 194, 58, 81, 124, 96, 163, 35, 22, 9, 54, 128, 27, 68, 123, 104, 139, 71, 174, 206, 216, 15, 74, 51, 113, 181, 79, 77, 104, 80, 89, 144, 162, 201, 111, 61, 217, 189, 60, 217, 114, 2, 234, 116, 144, 20, 220, 171, 64, 134, 79, 65, 19, 85, 253, 114, 29, 19, 137, 40, 24, 61, 230, 32, 100, 52, 114, 243, 159, 140, 38, 71, 217, 181, 108, 82, 93, 36, 254, 120, 22, 89, 65, 18, 199, 155, 150, 181, 175, 166, 180, 91, 249, 144, 207, 94, 227, 51, 138, 26, 216, 237, 157, 57, 165, 248, 76, 248, 29, 52, 138, 133, 108, 209, 223, 234, 185, 5, 61, 208, 64, 252, 152, 220, 75, 56, 103, 129, 198, 204, 222, 250, 195, 19, 68, 103, 32, 204, 235, 78, 30, 23, 0, 30, 42, 0, 109, 206, 97, 106, 35, 6, 52, 66, 51, 223, 127, 152, 18, 164, 123, 255, 248, 22, 18, 176, 71, 93, 250, 126, 103, 50, 21, 53, 255, 10, 63, 201, 18, 8, 214, 0, 93, 251, 198, 51, 74, 154, 132, 88, 61, 225, 93, 14, 144, 15, 165, 160, 87, 108, 232, 11, 165, 252, 125, 118, 91, 153, 3, 221, 169, 75, 206, 145, 66, 20, 163, 46, 134, 196, 133, 115, 112, 180, 84, 33, 253, 249, 154, 78, 158, 95, 149, 113, 0, 248, 80, 82, 236, 242, 193, 33, 104, 65, 90, 115, 24, 195, 144, 128, 161, 71, 201, 31, 167, 24, 40, 43, 187, 2, 3, 141, 215, 210, 5, 140, 25, 60, 27, 85, 105, 132, 78, 211, 155, 79, 74, 79, 176, 46, 44, 111, 137, 153, 131, 195, 111, 101, 132, 183, 117, 168, 54, 212, 180, 249, 140, 101, 67, 124, 218, 142, 167, 167, 0, 97, 242, 91, 118, 186, 25, 223, 178, 228, 203, 155, 190, 5, 222, 142, 122, 207, 35, 125, 107, 171, 172, 52, 141, 223, 187, 65, 100, 65, 145, 128, 29, 90, 155, 165, 60, 239, 191, 225, 165, 58, 73, 247, 96, 8, 117, 12, 235, 18, 43, 29, 196, 183, 179, 136, 207, 38, 13, 87, 52, 103, 140, 0, 65, 140, 135, 130, 121, 41, 48, 200, 230, 172, 127, 31, 67, 160, 27, 13, 224, 1, 120, 149, 34, 86, 69, 124, 222, 26, 210, 234, 104, 42, 123, 183, 239, 111, 153, 192, 206, 207, 143, 15, 121, 23, 202, 92, 197, 182, 193, 65, 225, 193, 128, 46, 174, 253, 154, 153, 37, 246, 164, 177, 31, 40, 65, 143, 55, 239, 105, 53, 141, 174, 198, 22, 77, 133, 81, 243, 16, 135, 170, 109, 166, 162, 57, 186, 225, 61, 165, 153, 200, 101, 12, 27, 1, 45, 47, 65, 20, 27, 16, 249, 203, 206, 70, 234, 91, 104, 135, 218, 40, 18, 46, 36, 214, 219, 101, 118, 95, 195, 33, 28, 69, 22, 127, 193, 226, 179, 202, 234, 21, 91, 28, 121, 81, 5, 47, 61, 208, 151, 143, 213, 94, 215, 115, 214, 175, 58, 70, 47, 89, 206, 96, 253, 183, 45, 161, 54, 104, 247, 155, 25, 153, 84, 125, 251, 128, 76, 225, 238, 247, 244, 140, 232, 34, 253, 12, 121, 207, 105, 206, 197, 105, 177, 2, 107, 181, 86, 75, 193, 245, 162, 246, 7, 32, 98, 224, 173, 219, 234, 4, 11, 235, 48, 7, 118, 57, 251, 234, 129, 112, 174, 34, 81, 177, 31, 203, 137, 254, 138, 26, 112, 239, 230, 131, 39, 82, 180, 159, 87, 84, 25, 69, 16, 44, 56, 65, 157, 67, 55, 191, 226, 5, 67, 57, 231, 94, 100, 249, 189, 209, 145, 189, 125, 78, 55, 104, 51, 77, 32, 189, 216, 250, 49, 237, 34, 13, 137, 237, 34, 48, 109, 147, 92, 62, 245, 95, 130, 56, 252, 38, 148, 144, 157, 89, 201, 215, 104, 144, 145, 244, 87, 156, 253, 61, 26, 85, 3, 190, 223, 104, 197, 98, 107, 80, 199, 24, 238] }) }, ref ipv6 = Ipv6Header { traffic_class: 5, flow_label: Ipv6FlowLabel(538693), payload_length: 46308, next_header: 234, hop_limit: 55, source: [146, 121, 12, 236, 217, 249, 88, 148, 98, 204, 179, 5, 182, 122, 210, 132], destination: [227, 105, 167, 21, 130, 114, 118, 136, 228, 155, 198, 110, 133, 134, 100, 229] }, ref ipv6_exts = Ipv6Extensions { hop_by_hop_options: None, destination_options: None, routing: None, fragment: None, auth: Some(IpAuthHeader { next_header: 206, spi: 1845380884, sequence_number: 3657579772, raw_icv: [137, 176, 140, 96, 42, 56, 82, 192, 212, 242, 182, 204, 182, 77, 134, 37, 163, 235, 161, 31, 37, 243, 77, 11, 86, 153, 172, 193, 33, 168, 126, 28, 208, 109, 115, 0, 243, 80, 211, 245, 142, 177, 139, 28, 156, 66, 38, 184, 116, 66, 2, 52, 205, 68, 104, 118, 80, 117, 112, 138, 42, 176, 25, 207, 35, 231, 46, 138, 193, 202, 229, 42, 226, 230, 100, 125, 127, 133, 111, 251, 185, 182, 238, 225, 185, 20, 80, 235, 215, 124, 180, 208, 103, 225, 164, 197, 85, 238, 178, 17, 231, 187, 134, 114, 67, 207, 189, 243, 196, 246, 105, 72, 187, 117, 14, 9, 155, 224, 221, 193, 246, 152, 90, 10, 100, 36, 181, 39, 61, 225, 210, 83, 52, 122, 207, 249, 248, 153, 0, 214, 32, 42, 234, 83, 117, 138, 0, 138, 77, 203, 1, 52, 120, 3, 217, 227, 239, 120, 241, 85, 81, 7, 118, 51, 3, 249, 139, 33, 33, 227, 120, 198, 0, 147, 247, 3, 84, 127, 77, 248, 236, 135, 201, 28, 183, 194, 48, 36, 45, 168, 225, 62, 181, 41, 20, 220, 174, 191, 50, 102, 219, 47, 220, 198, 42, 73, 174, 72, 129, 253, 125, 34, 142, 39, 190, 57, 154, 64, 62, 68, 125, 9, 2, 79, 239, 236, 58, 165, 62, 99, 56, 172, 236, 72, 147, 216, 34, 164, 218, 212, 174, 33, 209, 38, 67, 138, 214, 97, 220, 229, 40, 95, 34, 197, 91, 4, 175, 102, 76, 145, 86, 26, 31, 232, 179, 137, 51, 66, 202, 175, 42, 50, 137, 79, 119, 18, 130, 87, 132, 183, 141, 87, 220, 50, 10, 156, 54, 126, 59, 72, 88, 191, 108, 208, 41, 69, 215, 89, 222, 253, 52, 114, 12, 224, 59, 224, 92, 72, 124, 253, 51, 62, 3, 57, 3, 145, 20, 94, 215, 178, 116, 83, 19, 5, 134, 206, 226, 68, 224, 65, 12, 52, 167, 161, 38, 232, 86, 29, 165, 52, 148, 135, 54, 245, 212, 83, 178, 152, 157, 29, 193, 150, 95, 2, 188, 249, 4, 200, 144, 115, 203, 200, 108, 185, 117, 79, 123, 24, 114, 246, 215, 107, 84, 98, 96, 102, 193, 9, 12, 119, 206, 44, 66, 249, 223, 153, 193, 162, 199, 5, 146, 214, 75, 108, 32, 70, 185, 91, 56, 131, 97, 123, 10, 99, 230, 108, 164, 136, 107, 251, 199, 219, 115, 215, 140, 10, 129, 229, 182, 21, 8, 186, 255, 105, 152, 191, 49, 243, 239, 62, 3, 207, 68, 252, 158, 34, 130, 199, 104, 166, 74, 253, 197, 52, 6, 218, 137, 130, 173, 3, 241, 134, 168, 92, 163, 9, 175, 125, 201, 84, 58, 183, 109, 21, 225, 107, 109, 137, 52, 162, 201, 165, 241, 234, 39, 193, 113, 173, 117, 47, 0, 130, 98, 141, 23, 79, 12, 94, 147, 101, 32, 3, 95, 224, 154, 31, 91, 205, 250, 152, 186, 196, 130, 67, 180, 98, 248, 193, 205, 245, 49, 18, 208, 43, 139, 209, 168, 89, 244, 109, 27, 236, 30, 132, 230, 47, 107, 97, 209, 44, 145, 143, 1, 84, 182, 108, 250, 47, 136, 124, 205, 115, 79, 94, 96, 14, 65, 248, 24, 228, 30, 236, 17, 152, 159, 122, 214, 65, 28, 169, 151, 30, 74, 198, 122, 148, 181, 216, 160, 96, 51, 182, 157, 129, 212, 201, 64, 103, 236, 29, 130, 166, 207, 59, 148, 200, 143, 19, 5, 219, 52, 61, 228, 103, 2, 13, 221, 101, 130, 226, 147, 48, 141, 68, 177, 18, 52, 249, 133, 205, 47, 65, 122, 187, 224, 30, 204, 55, 73, 248, 199, 118, 255, 32, 173, 49, 178, 254, 138, 57, 195, 11, 58, 144, 215, 14, 197, 242, 62, 174, 86, 25, 38, 208, 118, 145, 235, 70, 26, 197, 39, 150, 24, 217, 74, 190, 243, 90, 5, 203, 133, 206, 130, 3, 42, 161, 111, 196, 63, 185, 22, 244, 104, 89, 117, 137, 102, 59, 155, 63, 8, 231, 253, 215, 99, 153, 213, 114, 19, 45, 81, 87, 238, 226, 216, 69, 213, 39, 93, 4, 94, 41, 172, 169, 46, 133, 176, 36, 202, 97, 57, 181, 101, 163, 72, 22, 239, 63, 220, 140, 81, 28, 77, 127, 76, 121, 139, 162, 94, 204, 196, 181, 152, 153, 237, 164, 177, 68, 102, 218, 114, 252, 223, 90, 190, 29, 92, 63, 45, 146, 89, 223, 114, 241, 180, 41, 225, 85, 134, 76, 109, 122, 242, 187, 201, 194, 74, 92, 55, 246, 192, 214] }) }, ref arp = ArpPacket { hw_addr_type: 0x0F46, proto_addr_type: 0x94F6, hw_addr_size: 134, proto_addr_size: 175, operation: ArpOperation(27167), sender_hw_addr: [1, 37, 42, 81, 80, 13, 11, 254, 107, 219, 175, 166, 244, 215, 252, 142, 129, 157, 212, 49, 164, 106, 165, 202, 84, 19, 240, 51, 96, 209, 73, 63, 156, 82, 61, 55, 120, 178, 55, 137, 10, 28, 166, 199, 141, 199, 58, 43, 120, 91, 219, 190, 237, 27, 161, 219, 173, 150, 213, 107, 76, 175, 225, 136, 56, 87, 46, 74, 27, 146, 166, 108, 88, 23, 49, 111, 188, 165, 25, 159, 101, 62, 139, 235, 130, 55, 244, 176, 178, 148, 171, 107, 61, 207, 72, 203, 178, 138, 85, 140, 216, 35, 188, 18, 26, 241, 131, 145, 73, 21, 168, 43, 198, 52, 37, 136, 79, 236, 66, 168, 88, 254, 185, 45, 195, 79, 189, 43, 42, 242, 248, 30, 201, 162], sender_protocol_addr: [40, 164, 92, 139, 53, 167, 187, 166, 110, 103, 56, 197, 44, 19, 237, 145, 63, 58, 182, 31, 79, 19, 153, 240, 248, 140, 83, 114, 163, 114, 25, 188, 222, 36, 84, 171, 104, 38, 102, 54, 157, 91, 39, 80, 248, 130, 75, 156, 147, 134, 44, 152, 216, 71, 107, 245, 223, 243, 113, 10, 108, 234, 195, 13, 187, 57, 84, 2, 175, 52, 209, 136, 192, 81, 253, 251, 90, 131, 178, 141, 177, 15, 148, 250, 149, 83, 24, 183, 255, 58, 231, 139, 153, 199, 85, 8, 33, 20, 140, 42, 221, 224, 174, 255, 213, 206, 18, 121, 2, 208, 20, 87, 226, 94, 144, 141, 97, 111, 68, 174, 76, 134, 138, 25, 128, 203, 30, 217, 157, 74, 181, 168, 115, 72, 174, 27, 78, 255, 54, 75, 135, 86, 92, 79, 215, 125, 222, 58, 58, 244, 216, 222, 45, 73, 162, 182, 136, 143, 64, 190, 96, 169, 10, 247, 180, 47, 73, 172, 237, 152, 232, 224, 205, 112, 49], target_hw_addr: [64, 93, 48, 199, 200, 118, 117, 109, 222, 199, 135, 210, 166, 48, 68, 119, 226, 83, 91, 128, 42, 58, 98, 4, 191, 41, 166, 227, 165, 160, 55, 238, 212, 190, 191, 46, 167, 78, 127, 135, 171, 191, 241, 98, 181, 40, 55, 64, 158, 82, 83, 106, 68, 93, 103, 228, 63, 126, 29, 52, 139, 209, 219, 123, 248, 106, 2, 123, 136, 105, 43, 245, 121, 80, 178, 96, 238, 21, 236, 205, 157, 226, 249, 174, 165, 148, 221, 246, 41, 132, 67, 192, 106, 23, 74, 73, 226, 62, 133, 163, 7, 94, 106, 152, 240, 6, 70, 10, 234, 58, 148, 89, 137, 84, 234, 17, 213, 86, 255, 81, 89, 168, 234, 133, 34, 10, 192, 121, 65, 188, 37, 54, 43, 147], target_protocol_addr: [65, 98, 185, 60, 223, 164, 70, 145, 101, 2, 85, 93, 120, 16, 153, 254, 41, 30, 58, 137, 170, 241, 248, 114, 195, 223, 30, 72, 18, 126, 73, 167, 226, 179, 27, 133, 48, 2, 181, 123, 221, 102, 192, 90, 61, 79, 203, 58, 155, 2, 66, 5, 98, 167, 11, 95, 13, 127, 83, 250, 129, 152, 234, 136, 2, 14, 245, 71, 124, 8, 146, 146, 67, 138, 213, 211, 181, 226, 36, 2, 69, 117, 186, 244, 29, 134, 179, 209, 254, 12, 171, 102, 221, 200, 114, 65, 241, 222, 117, 95, 179, 33, 18, 183, 223, 54, 252, 159, 0, 21, 196, 167, 147, 214, 233, 223, 188, 173, 152, 101, 138, 204, 128, 48, 140, 172, 101, 20, 64, 167, 154, 28, 158, 196, 129, 204, 61, 179, 186, 163, 21, 202, 179, 19, 135, 52, 43, 86, 101, 31, 76, 234, 49, 80, 101, 117, 60, 134, 108, 182, 150, 104, 55, 115, 207, 165, 215, 37, 20, 98, 106, 31, 80, 50, 222] }, ref udp = UdpHeader { source_port: 52220, destination_port: 51065, length: 11126, checksum: 17171 }, ref tcp = TcpHeader { source_port: 25330, destination_port: 52834, sequence_number: 1922811557, acknowledgment_number: 248581598, ns: false, fin: false, syn: true, rst: true, psh: true, ack: false, urg: true, ece: true, cwr: false, window_size: 44125, checksum: 60648, urgent_pointer: 14489, options: [Err(UnknownId(58))] }, ref icmpv4 = Icmpv4Header { icmp_type: Unknown { type_u8: 232, code_u8: 156, bytes5to8: [212, 237, 132, 212] }, checksum: 45828 }, ref icmpv6 = Icmpv6Header { icmp_type: Unknown { type_u8: 65, code_u8: 55, bytes5to8: [133, 7, 13, 249] }, checksum: 3792 }, ref payload = [103, 132, 44, 211, 53, 235, 103, 88, 148, 31, 116, 218, 219, 23, 4, 140, 221, 160] +cc d2a394e54977d582f4125437dcbb62a7599d85063cbac4d1c8b614e50d941815 # shrinks to ref eth = Ethernet2Header { source: [0, 0, 0, 0, 0, 0], destination: [0, 0, 0, 0, 0, 0], ether_type: 0x0000 }, ref vlan0 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref vlan1 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref vlan2 = SingleVlanHeader { pcp: VlanPcp(0), drop_eligible_indicator: false, vlan_id: VlanId(0), ether_type: 0x0000 }, ref macsec0 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref macsec1 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref macsec2 = MacsecHeader { ptype: Unmodified(0x0000), endstation_id: false, scb: false, an: MacsecAn(0), short_len: MacsecShortLen(0), packet_nr: 0, sci: None }, ref ipv4 = Ipv4Header { dscp: IpDscp(25), ecn: CongestionExperienced, total_len: 58017, identification: 54683, dont_fragment: true, more_fragments: false, fragment_offset: IpFragOffset(5834), time_to_live: 145, protocol: 215, header_checksum: 50452, source: [0, 0, 0, 0], destination: [54, 61, 56, 232], options: [] }, ref ipv4_exts = Ipv4Extensions { auth: Some(IpAuthHeader { next_header: 9 (IGP - any private interior gateway (used by Cisco for their IGRP)), spi: 3747240601, sequence_number: 1201438222, raw_icv: [162, 33, 213, 3, 101, 144, 232, 82, 10, 16, 116, 140, 22, 19, 187, 85, 114, 255, 88, 23, 121, 38, 68, 33, 190, 141, 167, 17, 14, 136, 160, 76, 229, 222, 8, 208, 38, 221, 169, 162, 0, 227, 147, 152, 135, 24, 209, 153, 77, 248, 155, 43, 3, 158, 141, 50, 60, 176, 61, 131, 154, 221, 31, 4, 171, 156, 247, 166, 77, 74, 114, 130, 198, 168, 115, 202, 113, 179, 190, 110, 231, 80, 76, 99, 169, 37, 148, 248, 5, 178, 76, 64, 214, 89, 236, 87, 47, 149, 100, 81, 251, 6, 164, 203, 177, 11, 219, 80, 32, 48, 142, 83, 46, 220, 125, 248, 215, 62, 126, 82, 20, 98, 82, 39] }) }, ref ipv6 = Ipv6Header { traffic_class: 247, flow_label: Ipv6FlowLabel(56740), payload_length: 19430, next_header: 2 (IGMP - Internet Group Management), hop_limit: 58, source: [182, 241, 230, 56, 184, 137, 145, 165, 239, 120, 36, 224, 173, 249, 210, 227], destination: [56, 140, 239, 213, 237, 196, 6, 102, 149, 220, 183, 162, 135, 225, 88, 213] }, ref ipv6_exts = Ipv6Extensions { hop_by_hop_options: Some(Ipv6RawExtHeader { next_header: 60 (IPv6-Opts - Destination Options for IPv6), payload: [162, 30, 222, 192, 238, 39, 224, 112, 145, 125, 139, 73, 92, 13, 29, 157, 65, 98, 242, 33, 122, 235] }), destination_options: Some(Ipv6RawExtHeader { next_header: 43 (IPv6-Route - Routing Header for IPv6), payload: [163, 239, 63, 196, 37, 43, 55, 145, 55, 162, 12, 27, 18, 249, 70, 135, 75, 208, 76, 97, 28, 184, 71, 106, 52, 160, 71, 34, 145, 187, 176, 206, 32, 229, 174, 100, 55, 209, 101, 22, 213, 23, 22, 61, 98, 119, 112, 137, 21, 34, 146, 134, 74, 128, 183, 199, 108, 236, 174, 2, 103, 237, 184, 213, 40, 201, 174, 254, 229, 165, 151, 209, 54, 112, 159, 204, 246, 225, 186, 101, 34, 146, 133, 183, 198, 227, 179, 220, 64, 154, 148, 118, 199, 70, 58, 1, 14, 138, 63, 182, 167, 207, 45, 47, 198, 116, 198, 245, 191, 198, 213, 85, 247, 150, 71, 58, 171, 39, 232, 24, 40, 168, 45, 107, 176, 233, 96, 69, 227, 126, 189, 185, 33, 162, 131, 250, 245, 191, 47, 55, 57, 87, 250, 129, 66, 29, 196, 137, 205, 30, 151, 38, 213, 20, 202, 82, 197, 247, 101, 8, 62, 15, 188, 145, 80, 136, 211, 181, 143, 197, 249, 155, 233, 221, 207, 248, 252, 87, 97, 228, 148, 109, 117, 23, 97, 188, 131, 225, 169, 165, 250, 123, 124, 118, 135, 188, 40, 123, 141, 57, 173, 4, 167, 251, 172, 98, 54, 243, 126, 30, 211, 205, 212, 182, 195, 55, 222, 211, 206, 206, 107, 60, 249, 50, 31, 124, 213, 135, 19, 176, 49, 96, 113, 135, 96, 107, 199, 192, 86, 210, 42, 60, 105, 75, 64, 53, 92, 186, 240, 7, 144, 145, 207, 199, 236, 13, 187, 221, 87, 54, 72, 209, 91, 223, 144, 185, 223, 110, 193, 29, 240, 178, 96, 32, 160, 183, 67, 206, 6, 121, 167, 127, 159, 109, 82, 218, 4, 43, 41, 99, 113, 152, 182, 127, 160, 250, 79, 155, 132, 23, 235, 143, 16, 239, 148, 77, 163, 217, 162, 169, 145, 232, 16, 68, 43, 219, 247, 69, 41, 2, 97, 254, 187, 224, 140, 144, 132, 239, 176, 167, 225, 193, 197, 84, 32, 219, 232, 25, 123, 195, 229, 127, 199, 101, 209, 112, 106, 163, 11, 138, 47, 44, 63, 36, 188, 47, 112, 1, 51, 68, 65, 65, 65, 32, 216, 152, 216, 185, 26, 176, 51, 104, 22, 70, 149, 82, 123, 69, 150, 227, 236, 91, 149, 44, 248, 72, 8, 201, 144, 152, 206, 26, 142, 0, 160, 203, 10, 107, 228, 43, 198, 178, 32, 132, 177, 245, 31, 240, 6, 114, 201, 6, 163, 21, 228, 87, 207, 112, 130, 67, 13, 221, 128, 4, 162, 229, 183, 223, 195, 141, 1, 193, 45, 131, 203, 135, 171, 38, 218, 128, 52, 73, 123, 128, 156, 201, 90, 132, 79, 156, 225, 55, 62, 163, 152, 68, 119, 218, 58, 91, 4, 155, 228, 87, 146, 100, 212, 205, 46, 95, 246, 25, 149, 167, 68, 106, 137, 169, 212, 102, 176, 242, 27, 196, 11, 233, 95, 30, 209, 110, 205, 196, 66, 224, 202, 6, 152, 74, 120, 238, 176, 142, 238, 83, 9, 7, 194, 61, 236, 197, 13, 231, 69, 131, 123, 31, 55, 132, 180, 51, 16, 140, 35, 147, 180, 19, 201, 227, 27, 66, 101, 228, 162, 40, 184, 99, 177, 176, 225, 127, 0, 76, 26, 154, 55, 170, 198, 56, 243, 142, 199, 96, 74, 81, 90, 171, 129, 82, 215, 122, 97, 147, 233, 133, 5, 93, 183, 58, 56, 227, 196, 2, 177, 90, 10, 196, 123, 118, 70, 165, 108, 85, 211, 139, 154, 73, 33, 93, 187, 169, 33, 9, 89, 166, 126, 63, 210, 162, 59, 117, 121, 156, 88, 246, 108, 137, 213, 99, 235, 45, 231, 115, 35, 179, 6, 251, 133, 213, 57, 99, 166, 157, 205, 19, 132, 29, 254, 137, 185, 177, 61, 1, 124, 170, 132, 105, 73, 2, 245, 189, 154, 122, 243, 243, 33, 121, 138, 197, 113, 0, 151, 167, 218, 153, 234, 94, 217, 80, 223, 42, 141, 145, 35, 181, 127, 66, 125, 150, 146, 242, 161, 218, 237, 125, 196, 193, 32, 197, 96, 40, 241, 7, 158, 197, 4, 129, 184, 133, 32, 37, 228, 152, 91, 142, 4, 241, 80, 221, 88, 117, 147, 243, 203, 214, 164, 236, 114, 58, 21, 233, 82, 7, 216, 34, 56, 194, 101, 72, 222, 0, 204, 255, 96, 172, 153, 172, 103, 91, 130, 158, 46, 178, 121, 94, 110, 37, 130, 149, 81, 34, 130, 90, 97, 125, 149, 178, 199, 126, 34, 49, 42, 80, 169, 134, 226, 161, 14, 239, 153, 192, 25, 3, 140, 110, 173, 23, 186, 39, 189, 52, 60, 55, 250, 5, 74, 167, 199, 215, 163, 84, 198, 172, 169, 74, 182, 6, 162, 16, 193, 226, 77, 164, 139, 10, 70, 19, 165, 246, 195, 103, 197, 194, 81, 247, 31, 18, 124, 115, 96, 4, 240, 152, 85, 224, 140, 231, 76, 168, 208, 37, 86, 8, 128, 224, 5, 62, 17, 58, 50, 122, 165, 49, 213, 85, 61, 226, 135, 164, 159, 192, 136, 6, 5, 174, 34, 159, 118, 125, 170, 135, 124, 107, 173, 44, 96, 55, 99, 112, 179, 194, 213, 158, 17, 204, 68, 178, 178, 138, 80, 242, 247, 57, 160, 255, 114, 145, 115, 43, 120, 88, 216, 96, 66, 178, 89, 91, 163, 214, 15, 50, 202, 122, 42, 125, 119, 212, 0, 241, 65, 232, 190, 40, 4, 223, 49, 80, 4, 109, 91, 206, 88, 60, 141, 234, 196, 145, 199, 55, 132, 146, 149, 15, 29, 217, 52, 164, 192, 76, 170, 25, 86, 197, 43, 222, 247, 162, 103, 188, 118, 14, 99, 196, 63, 175, 226, 214, 65, 179, 10, 196, 142, 190, 79, 195, 57, 173, 142, 36, 20, 15, 100, 201, 107, 227, 125, 252, 213, 77, 175, 198, 244, 169, 65, 154, 195, 164, 251, 63, 98, 20, 164, 108, 157, 164, 230, 38, 98, 123, 194, 149, 98, 157, 104, 16, 140, 4, 149, 168, 161, 84, 219, 98, 237, 60, 13, 45, 206, 22, 106, 146, 70, 238, 181, 245, 210, 68, 159, 199, 120, 50, 251, 206, 224, 44, 115, 156, 148, 128, 246, 201, 222, 118, 30, 178, 12, 248, 35, 44, 14, 152, 205, 217, 120, 52, 115, 11, 150, 252, 63, 39, 247, 250, 238, 50, 36, 250, 159, 40, 245, 238, 108, 184, 219, 30, 215, 232, 38, 24, 56, 117, 186, 152, 216, 227, 232, 69, 78, 110, 42, 215, 198, 250, 74, 120, 246, 184, 43, 51, 93, 124, 229, 141, 53, 214, 33, 193, 136, 40, 86, 126, 45, 89, 167, 61, 147, 23, 99, 227, 112, 43, 191, 141, 102, 199, 124, 47, 2, 131, 129, 158, 153, 0, 117, 50, 75, 133, 72, 113, 161, 230, 161, 111, 106, 240, 167, 179, 183, 59, 67, 221, 57, 111, 237, 86, 96, 97, 19, 147, 114, 45, 231, 187, 239, 152, 50, 22, 213, 100, 55, 172, 80, 38, 225, 152, 6, 48, 91, 208, 113, 162, 23, 45, 138, 20, 81, 179, 186, 3, 163, 14, 247, 107, 199, 39, 36, 35, 38, 229, 10, 97, 254, 124, 48, 197, 116, 229, 120, 114, 189, 14, 118, 110, 61, 189, 48, 219, 11, 38, 81, 149, 246, 191, 183, 100, 43, 14, 139, 211, 208, 219, 33, 214, 132, 66, 85, 123, 4, 244, 65, 7, 36, 22, 206, 96, 108, 149, 52, 193, 239, 209, 236, 103, 76, 156, 143, 246, 9, 29, 240, 239, 45, 45, 211, 160, 141, 211, 77, 154, 25, 166, 255, 81, 148, 153, 122, 75, 120, 210, 104, 193, 201, 235, 121, 128, 32, 168, 177, 206, 233, 240, 80, 166, 220, 98, 102, 106, 159, 124, 217, 128, 212, 238, 140, 136, 25, 221, 142, 208, 156, 146, 187, 227, 21, 67, 144, 236, 16, 11, 172, 193, 4, 204, 64, 66, 70, 171, 112, 133, 64, 68, 3, 192, 130, 36, 120, 122, 199, 218, 231, 135, 30, 239, 118, 139, 97, 73, 218, 80, 178, 64, 244, 160, 162, 11, 23, 178, 7, 243, 212, 126, 148, 199, 12, 100, 76, 219, 240, 19, 201, 231, 187, 100, 97, 193, 43, 9, 135, 166, 22, 104, 102, 235, 34, 2, 239, 107, 212, 176, 138, 32, 104, 19, 54, 164, 184, 255, 28, 94, 132, 56, 135, 186, 61, 100, 215, 26, 7, 2, 155, 41, 164, 111, 227, 68, 152, 228, 95, 200, 190, 187, 207, 111, 114, 23, 45, 100, 135, 180, 101, 108, 106, 237, 94, 62, 131, 201, 186, 141, 61, 143, 75, 14, 117, 198, 65, 47, 145, 75, 190, 61, 228, 31, 135, 210, 93, 29, 64, 180, 83, 150, 170, 208, 30, 33, 162, 142, 129, 166, 81, 136, 205, 220, 91, 75, 135, 64, 143, 91, 17, 227, 44, 6, 234, 239, 20, 56, 238, 212, 97, 2, 192, 109, 47, 246, 247, 76, 139, 219, 90, 54, 28, 255, 103, 160, 67, 144, 133, 47, 175, 108, 235, 165, 56, 57, 162, 127, 65, 76, 127, 94, 119, 125, 250, 67, 166, 69, 12, 36, 26, 67, 0, 17, 73, 150, 76, 3, 16, 98, 31, 238, 10, 230, 92, 10, 10, 202, 210, 198, 245, 31, 49, 219, 108, 138, 47, 130, 216, 172, 95, 185, 107, 43, 205, 110, 153, 237, 238, 179, 214, 201, 150, 222, 129, 77, 141, 89, 128, 237, 147, 152, 209, 59, 238, 106, 94, 229, 54, 70, 59, 64, 77, 252, 101, 55, 109, 34, 162, 174, 27, 5, 199, 102, 74, 221, 102, 246, 132, 143, 9, 81, 182, 29, 191, 252, 97, 153, 207, 237, 26, 102, 28, 90, 145, 26, 13, 222, 164, 255, 112, 16, 77, 98, 30, 111, 17, 58, 225, 115, 181, 29, 209, 218, 91, 99, 63, 186, 9, 234, 234, 78, 139, 205, 160, 149, 112, 18, 91, 97, 201, 101, 51, 180, 142, 193, 17, 196, 172, 4, 97, 18, 181, 250, 144, 198, 225, 0, 161, 188, 229, 81, 27, 59, 163, 115, 91, 221, 171, 178, 170, 243, 40, 12, 171, 151, 96, 239, 143, 121, 97, 195, 9, 65, 19, 54, 214, 122, 136, 122, 147, 241, 163, 124, 3, 171, 82, 201, 153, 7, 202, 245, 220, 143, 30, 249, 95, 196, 199, 18, 203, 199, 106, 184, 216, 99, 143, 79, 154, 0, 129, 206, 197, 142, 222, 235, 9, 189, 214, 214, 92, 40, 234, 215, 181, 63, 207, 26, 60, 152, 115, 147, 9, 182, 43, 141, 241, 200, 6, 255, 11, 163, 213, 175, 49, 153, 32, 163] }), routing: Some(Ipv6RoutingExtensions { routing: Ipv6RawExtHeader { next_header: 51 (AH - Authentication Header), payload: [172, 30, 89, 118, 158, 203, 163, 33, 212, 135, 169, 157, 243, 141, 109, 177, 193, 29, 110, 244, 215, 191, 30, 2, 225, 92, 187, 59, 173, 119, 205, 49, 119, 127, 194, 133, 249, 166, 104, 163, 60, 24, 130, 59, 212, 91, 125, 216, 175, 127, 182, 12, 41, 211, 1, 26, 190, 233, 54, 184, 119, 147, 113, 20, 84, 254, 241, 69, 246, 96, 151, 103, 83, 65, 251, 80, 151, 163, 109, 46, 146, 85, 82, 152, 31, 247, 50, 55, 110, 199, 249, 31, 186, 109, 58, 134, 221, 109, 255, 13, 84, 71, 5, 32, 127, 214, 24, 154, 138, 110, 37, 6, 245, 45, 146, 33, 36, 48, 106, 128, 149, 37, 238, 213, 190, 63, 105, 212, 188, 24, 2, 182, 109, 228, 155, 21, 250, 171, 61, 220, 73, 170, 248, 88, 184, 57, 162, 130, 142, 85] }, final_destination_options: None }), fragment: None, auth: Some(IpAuthHeader { next_header: 168, spi: 411637621, sequence_number: 1418460161, raw_icv: [249, 35, 106, 89, 25, 25, 23, 50, 71, 103, 5, 171, 92, 192, 190, 105, 8, 28, 235, 40, 72, 34, 85, 49, 20, 39, 220, 153, 70, 4, 129, 231, 81, 171, 31, 99, 99, 42, 73, 2, 135, 127, 177, 35, 216, 144, 117, 165, 213, 125, 21, 244, 206, 131, 241, 102, 147, 26, 238, 104, 92, 41, 224, 203, 74, 30, 75, 189, 149, 60, 167, 249, 88, 69, 225, 193, 67, 126, 185, 159, 48, 187, 153, 102, 101, 235, 30, 29, 156, 44, 235, 65, 81, 92, 160, 253, 78, 217, 162, 136, 148, 224, 163, 76, 30, 74, 26, 28, 64, 165, 142, 240, 25, 215, 36, 110, 143, 186, 234, 133, 75, 89, 92, 83, 180, 171, 174, 166, 229, 47, 230, 50, 59, 124, 196, 13, 203, 7, 22, 246, 226, 234, 48, 106, 30, 84, 209, 109, 164, 160, 160, 214, 35, 254, 29, 80, 206, 23, 154, 77, 59, 71, 208, 110, 236, 75, 232, 186, 20, 152, 93, 208, 29, 123, 41, 175, 45, 204, 87, 195, 216, 109, 78, 111, 223, 88, 78, 69, 118, 69, 139, 197, 181, 189, 42, 224, 20, 68, 206, 48, 146, 32, 159, 164, 162, 44, 137, 62, 69, 81, 87, 197, 195, 94, 141, 71, 237, 84, 20, 252, 246, 194, 188, 176, 95, 28, 246, 106, 30, 45, 59, 99, 37, 190, 102, 139, 172, 93, 88, 17, 232, 167, 161, 9, 79, 31, 15, 168, 107, 193, 43, 42, 8, 254, 228, 130, 8, 172, 227, 165, 102, 230, 24, 105, 50, 221, 68, 164, 224, 230, 59, 116, 19, 215, 232, 126, 28, 20, 77, 236, 77, 190, 115, 167, 21, 45, 104, 246, 34, 198, 207, 123, 45, 217, 135, 174, 56, 87, 246, 159, 239, 82, 200, 88, 250, 180, 26, 181, 33, 86, 192, 32, 239, 37, 125, 20, 17, 239, 99, 11, 247, 134, 80, 177, 58, 205, 14, 60, 36, 138, 191, 103, 241, 44, 227, 226, 237, 181, 148, 28, 12, 41, 174, 70, 15, 52, 48, 103, 117, 115, 38, 124, 163, 96, 1, 82, 160, 19, 102, 139, 211, 94, 203, 57, 61, 160, 26, 63, 157, 206, 254, 63, 86, 234, 182, 42, 220, 89, 196, 158, 231, 9, 207, 61, 254, 169, 56, 247, 255, 49, 4, 136, 120, 91, 179, 233, 152, 142, 134, 125, 34, 205, 199, 165, 62, 91, 24, 73, 237, 171, 210, 127, 118, 23, 40, 49, 232, 61, 252, 58, 172, 156, 106, 12, 210, 115, 248, 134, 177, 104, 15, 38, 163, 98, 78, 236, 179, 58, 10, 240, 67, 185, 39, 200, 218, 129, 16, 57, 238, 226, 12, 253, 202, 16, 8, 135, 47, 82, 140, 212, 251, 252, 97, 218, 228, 120, 170, 208, 215, 255, 113, 195, 185, 141, 48, 39, 114, 135, 48, 219, 50, 26, 84, 15, 234, 23, 154, 33, 27, 99, 154, 146, 145, 253, 76, 240, 183, 222, 139, 69] }) }, ref arp = ArpPacket { hw_addr_type: 0x45B7, proto_addr_type: 0xA60A, hw_addr_size: 52, proto_addr_size: 208, operation: ArpOperation(50121), sender_hw_addr: [122, 101, 198, 122, 176, 143, 46, 227, 124, 116, 20, 243, 55, 199, 82, 55, 104, 70, 88, 21, 254, 56, 12, 56, 14, 158, 222, 179, 219, 230, 251, 116, 10, 9, 189, 229, 233, 168, 226, 13, 4, 9, 47, 200, 91, 189, 93, 130, 14, 224, 11, 40], sender_protocol_addr: [33, 25, 95, 39, 154, 0, 238, 46, 50, 34, 84, 15, 73, 152, 90, 251, 10, 221, 76, 66, 190, 43, 18, 126, 46, 137, 47, 143, 155, 217, 88, 106, 117, 198, 165, 124, 30, 36, 166, 162, 75, 225, 124, 159, 226, 211, 75, 196, 230, 148, 205, 209, 39, 54, 143, 0, 199, 245, 166, 145, 78, 167, 109, 217, 18, 97, 176, 91, 185, 239, 228, 183, 134, 153, 189, 245, 52, 37, 246, 67, 29, 137, 104, 151, 90, 184, 146, 96, 169, 197, 144, 65, 148, 228, 155, 178, 178, 226, 153, 211, 246, 162, 175, 135, 77, 164, 57, 103, 114, 119, 135, 171, 222, 32, 55, 126, 128, 138, 90, 139, 160, 84, 35, 121, 28, 118, 248, 74, 116, 69, 160, 144, 34, 64, 126, 62, 62, 20, 8, 28, 176, 138, 137, 102, 30, 72, 48, 69, 197, 220, 206, 71, 255, 153, 173, 57, 195, 232, 104, 208, 12, 167, 233, 103, 50, 177, 61, 176, 179, 186, 172, 247, 5, 243, 164, 253, 74, 228, 243, 3, 166, 122, 75, 234, 39, 114, 242, 50, 40, 76, 39, 254, 149, 17, 7, 167, 88, 27, 114, 162, 73, 110, 64, 42, 7, 15, 224, 150], target_hw_addr: [141, 126, 78, 165, 210, 243, 2, 146, 201, 199, 95, 101, 72, 44, 188, 75, 38, 216, 13, 205, 64, 56, 244, 196, 27, 47, 218, 39, 26, 147, 130, 59, 238, 94, 55, 221, 133, 100, 154, 153, 158, 113, 115, 162, 174, 4, 97, 118, 219, 70, 17, 29], target_protocol_addr: [164, 241, 147, 156, 25, 195, 52, 71, 188, 51, 211, 75, 142, 206, 53, 54, 202, 102, 70, 76, 92, 98, 61, 188, 36, 50, 95, 225, 152, 131, 166, 240, 120, 36, 123, 110, 158, 70, 38, 90, 80, 105, 200, 227, 20, 81, 37, 36, 100, 79, 31, 60, 136, 145, 26, 74, 255, 40, 1, 232, 166, 250, 205, 162, 80, 43, 67, 84, 66, 73, 196, 12, 142, 49, 67, 225, 248, 104, 172, 186, 133, 17, 195, 52, 176, 199, 144, 155, 88, 12, 5, 233, 205, 248, 197, 224, 56, 221, 183, 137, 178, 197, 192, 8, 96, 66, 63, 232, 30, 236, 28, 113, 98, 223, 52, 26, 149, 169, 210, 19, 180, 219, 143, 8, 190, 61, 71, 217, 163, 234, 146, 205, 191, 174, 136, 66, 59, 236, 101, 23, 204, 222, 52, 217, 109, 10, 154, 143, 123, 199, 221, 199, 170, 201, 233, 240, 235, 126, 180, 185, 58, 139, 94, 211, 159, 68, 73, 163, 130, 13, 245, 198, 80, 158, 20, 202, 173, 148, 49, 245, 127, 172, 52, 212, 55, 7, 140, 21, 20, 175, 26, 52, 81, 212, 92, 158, 142, 64, 245, 95, 235, 0, 27, 37, 143, 142, 183, 119] }, ref udp = UdpHeader { source_port: 31336, destination_port: 42113, length: 38372, checksum: 18124 }, ref tcp = TcpHeader { source_port: 43235, destination_port: 59311, sequence_number: 1871621020, acknowledgment_number: 453163792, ns: true, fin: false, syn: true, rst: false, psh: true, ack: false, urg: false, ece: false, cwr: true, window_size: 37917, checksum: 64480, urgent_pointer: 23254, options: [Err(UnknownId(143))] }, ref icmpv4 = Icmpv4Header { icmp_type: Unknown { type_u8: 105, code_u8: 101, bytes5to8: [63, 8, 202, 56] }, checksum: 57220 }, ref icmpv6 = Icmpv6Header { icmp_type: Unknown { type_u8: 34, code_u8: 205, bytes5to8: [242, 72, 255, 183] }, checksum: 56859 }, ref payload = [91, 230, 239, 53, 72, 47, 186, 252, 192, 25, 245, 73, 254, 37, 231, 61, 59, 11, 183, 254, 45, 94, 190, 131, 169, 233, 159, 90, 157, 100, 246, 117, 62, 151, 63, 155, 248, 49, 47, 152, 157, 178, 112, 143, 119, 52, 46, 77, 205, 89, 47, 123, 96, 62, 249, 180, 203, 198, 29, 22, 136, 35, 107, 33, 172, 11, 57, 237, 191, 163, 29, 169, 206, 135, 55, 117, 108, 160, 142, 35, 21, 135, 17, 116, 38, 217, 84, 80, 176, 153, 243, 41, 124, 51, 154, 74, 231, 213, 194, 230, 182, 134, 45, 140, 88, 56, 42, 99, 232, 214, 213, 10, 7, 2, 192, 202, 192, 233, 16, 146, 211, 36, 225, 146, 105, 11, 122, 125, 40, 135, 130, 28, 154, 212, 155, 84, 98, 119, 175, 159, 116, 31, 164, 77, 73, 120, 154, 73, 9, 175, 19, 13, 152, 214, 147, 78, 255, 176, 116, 131, 135, 226, 147, 10, 78, 93, 97, 2, 10, 194, 65, 89, 54, 202, 215, 54, 212, 102, 139, 70, 130, 93, 206, 69, 14, 200, 62, 45, 166, 41, 13, 5, 157, 212, 0, 248, 50, 43, 112, 58, 197, 2, 102, 179, 159, 49, 9, 206, 162, 219, 225, 233, 11, 190, 227, 183, 30, 143, 249, 39, 30, 239, 181, 28, 255, 85, 231, 141, 123, 136, 98, 81, 86, 5, 198, 132, 246, 69, 197, 250, 55, 104, 24, 18, 233, 223, 54, 38, 223, 50, 36, 207, 1, 127, 120, 63, 82, 234, 13, 163, 38, 70, 68, 210, 24, 8, 122, 197, 43, 247, 168, 49, 195, 127, 214, 91, 133, 84, 210, 76, 239, 174, 54, 234, 76, 163, 29, 180, 17, 205, 5, 50, 61, 85, 195, 74, 220, 50, 235, 151, 152, 140, 33, 248, 38, 67, 191, 129, 86, 74, 62, 109, 7, 144, 91, 126, 51, 188, 19, 216, 210, 237, 21, 171, 119, 29, 190, 41, 199, 69, 66, 31, 11, 76, 143, 211, 155, 104, 16, 36, 19, 216, 250, 226, 156, 120, 73, 151, 163, 243, 238, 106, 13, 49, 121, 163, 244, 204, 123, 241, 9, 237, 31, 158, 95, 170, 213, 149, 162, 163, 55, 148, 90, 239, 199, 50, 136, 21, 227, 52, 50, 213, 116, 49, 123, 2, 198, 127, 6, 93, 243, 142, 192, 102, 23, 153, 166, 73, 246, 211, 17, 33, 169, 32, 67, 63, 30, 70, 226, 152, 212, 199, 201, 252, 25, 173, 70, 243, 38, 124, 249, 120, 26, 6, 86, 163, 100, 149, 4, 64, 31, 216, 255, 146, 54, 249, 187, 151, 10, 32, 98, 167, 159, 111, 36, 152, 109, 70, 145, 121, 68, 73, 47, 79, 142, 34, 6, 249, 243, 22, 154, 205, 128, 137, 46, 150, 186, 125, 114, 226, 101, 219, 236, 101, 136, 160, 141, 241, 151, 227, 109, 120, 250, 235, 102, 211, 241, 32, 169, 199, 101, 87, 84, 8, 197, 203, 156, 170, 82, 21, 102, 95, 121, 14, 121, 172, 17, 226, 117, 43, 219, 109, 93, 185, 194, 190, 149, 144, 240, 171, 186, 108, 232, 146, 251, 47, 147, 30, 70, 222, 239, 160, 118, 4, 104, 180, 205, 177, 159, 60, 23, 234, 32, 221, 163, 74, 200, 207, 215, 10, 81, 64, 48, 42, 223, 127, 9, 241, 129, 66, 185, 102, 18, 45, 159, 48, 144, 142, 79, 110, 127, 95, 161, 232, 99, 254, 126, 79, 207, 218, 102, 248, 233, 255, 73, 107, 227, 246, 90, 57, 243, 10, 167, 250, 123, 147, 68, 232, 73, 171, 22, 202, 181, 237, 45, 143, 209, 236, 226, 7, 132, 63, 61, 118, 0, 153, 156, 217, 105, 11, 34, 150, 81, 181, 250, 127, 106, 2, 87, 71, 57, 87, 41, 210, 190, 158, 107, 81, 24, 82, 8, 49, 221, 244, 204, 62, 199, 120, 118, 34, 40, 254, 22, 247, 227, 65, 70, 153, 30, 176, 177, 200, 125, 36, 73, 135, 23, 87, 104, 205, 37, 132, 241, 73, 250, 220, 251, 112, 254, 154, 128, 3, 220, 192, 127, 235, 192, 65, 148, 16, 195, 224, 164, 74, 30, 46, 220, 59, 49, 241, 168, 173, 100, 123, 72, 22, 121, 205, 215, 178, 51, 37, 14, 241, 144, 183, 194, 50, 222, 8, 69, 79, 129, 115, 117, 89, 69, 107, 60, 231, 15, 196, 17, 148, 225, 121, 172, 149, 216, 3, 58, 32, 164, 101, 190, 61, 209, 241, 216, 144, 35, 60, 168, 112, 166, 238, 61, 72, 55, 61, 84, 175, 105, 119, 179, 99, 55, 201, 215, 177, 165, 40, 99, 1, 43, 123, 65, 81, 142, 134, 106, 209, 94, 247, 13, 90, 195, 201, 151, 207, 43, 35, 136, 179, 202, 80, 194, 208, 47, 119, 44, 9, 165, 175, 122, 84, 63, 64, 67, 30, 124, 174, 94, 252, 138, 192, 89, 215, 109, 142, 249, 29, 166, 18, 175, 59, 240, 226, 157, 231, 171, 210, 25, 47, 39, 211, 129, 78, 226, 208, 133, 49, 176, 173, 127, 90, 133, 15, 27, 72, 6, 193, 90, 64, 110, 74, 117, 182, 122, 158, 228, 187, 64, 160, 191, 255, 28, 71, 104, 157, 140, 155, 253, 29, 113, 78, 238, 249, 76, 124, 192, 35, 20, 218, 77, 208, 121, 197, 215, 175, 115, 65, 91, 100, 41, 163, 46, 240, 213, 76, 245, 18, 213, 229, 6, 83, 203, 139, 133, 219, 5, 17, 89, 231, 23, 187, 255, 201, 146, 223, 12, 183, 224, 81, 40, 231, 222, 37, 163, 54, 115, 51, 49, 143, 69, 10, 99, 3, 158, 219, 97, 99, 78, 228, 85, 84, 187, 187, 151, 169, 62, 23, 48, 96, 147, 144, 127, 45, 100, 13, 237, 89, 75, 129, 107, 248, 187, 188, 167, 148, 231, 76, 48, 255, 180, 3, 72, 255, 134, 47, 216, 228, 189, 125, 213, 205, 238, 58, 50, 29, 26, 14, 255, 218, 193, 110, 236, 1, 254, 161, 191, 117, 166, 204, 231, 29, 73, 94, 49, 159, 229, 251, 61, 44, 97, 255] diff --git a/etherparse/src/compositions_tests.rs b/etherparse/src/compositions_tests.rs index 42a19fe7..f0eacbde 100644 --- a/etherparse/src/compositions_tests.rs +++ b/etherparse/src/compositions_tests.rs @@ -65,6 +65,7 @@ impl<'a> ComponentTest<'a> { match &self.transport { Some(TransportHeader::Icmpv6(header)) => header.write(&mut buffer).unwrap(), Some(TransportHeader::Icmpv4(header)) => header.write(&mut buffer).unwrap(), + Some(TransportHeader::Igmp(header)) => header.write(&mut buffer).unwrap(), Some(TransportHeader::Udp(header)) => header.write(&mut buffer).unwrap(), Some(TransportHeader::Tcp(header)) => header.write(&mut buffer).unwrap(), None => {} @@ -405,6 +406,7 @@ impl<'a> ComponentTest<'a> { Some(TransportHeader::Icmpv4(actual.header())), Some(TransportSlice::Icmpv6(actual)) => Some(TransportHeader::Icmpv6(actual.header())), + Some(TransportSlice::Igmp(actual)) => Some(TransportHeader::Igmp(actual.header())), Some(TransportSlice::Udp(actual)) => Some(TransportHeader::Udp(actual.to_header())), Some(TransportSlice::Tcp(actual)) => Some(TransportHeader::Tcp(actual.to_header())), None => None, @@ -426,6 +428,9 @@ impl<'a> ComponentTest<'a> { Some(TransportSlice::Icmpv6(icmpv6)) => { assert_eq!(&self.payload[..], icmpv6.payload()); } + Some(TransportSlice::Igmp(igmp)) => { + assert_eq!(&self.payload[..], igmp.payload()); + } Some(TransportSlice::Udp(udp)) => { assert_eq!(&self.payload[..], udp.payload()); } @@ -471,6 +476,7 @@ impl<'a> ComponentTest<'a> { tcp: &TcpHeader, icmpv4: &Icmpv4Header, icmpv6: &Icmpv6Header, + igmp: &IgmpHeader, ) { // add vlan { @@ -489,11 +495,12 @@ impl<'a> ComponentTest<'a> { if !test.link_exts.is_full() { test.run_link_exts( vlans, macsecs, arp, ipv4, ipv4_ext, ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6, + igmp, ); } test.run_arp(arp); - test.run_ipv4(ipv4, ipv4_ext, udp, tcp, icmpv4, icmpv6); - test.run_ipv6(ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6); + test.run_ipv4(ipv4, ipv4_ext, udp, tcp, icmpv4, icmpv6, igmp); + test.run_ipv6(ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6, igmp); } // add macsec @@ -513,11 +520,12 @@ impl<'a> ComponentTest<'a> { if !test.link_exts.is_full() { test.run_link_exts( vlans, macsecs, arp, ipv4, ipv4_ext, ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6, + igmp, ); } test.run_arp(arp); - test.run_ipv4(ipv4, ipv4_ext, udp, tcp, icmpv4, icmpv6); - test.run_ipv6(ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6); + test.run_ipv4(ipv4, ipv4_ext, udp, tcp, icmpv4, icmpv6, igmp); + test.run_ipv6(ipv6, ipv6_ext, udp, tcp, icmpv4, icmpv6, igmp); } } @@ -536,6 +544,7 @@ impl<'a> ComponentTest<'a> { tcp: &TcpHeader, icmpv4: &Icmpv4Header, icmpv6: &Icmpv6Header, + igmp: &IgmpHeader, ) { // fragmenting { @@ -565,7 +574,7 @@ impl<'a> ComponentTest<'a> { non_frag.protocol = ip_exts.set_next_headers(ip.protocol); NetHeaders::Ipv4(non_frag, ip_exts) }); - test.run_transport(udp, tcp, icmpv4, icmpv6); + test.run_transport(udp, tcp, icmpv4, icmpv6, igmp); } } @@ -577,6 +586,7 @@ impl<'a> ComponentTest<'a> { tcp: &TcpHeader, icmpv4: &Icmpv4Header, icmpv6: &Icmpv6Header, + igmp: &IgmpHeader, ) { // fragmenting { @@ -612,7 +622,7 @@ impl<'a> ComponentTest<'a> { ip.next_header = non_frag.set_next_headers(ip.next_header); NetHeaders::Ipv6(ip, non_frag) }); - test.run_transport(udp, tcp, icmpv4, icmpv6); + test.run_transport(udp, tcp, icmpv4, icmpv6, igmp); } } @@ -622,10 +632,23 @@ impl<'a> ComponentTest<'a> { tcp: &TcpHeader, icmpv4: &Icmpv4Header, icmpv6: &Icmpv6Header, + igmp: &IgmpHeader, ) { // unknown transport layer self.run(); + // igmp + { + let mut test = self.clone(); + test.net + .as_mut() + .unwrap() + .try_set_next_headers(ip_number::IGMP) + .unwrap(); + test.transport = Some(TransportHeader::Igmp(igmp.clone())); + test.run() + } + // udp { let mut test = self.clone(); @@ -730,6 +753,7 @@ proptest! { ref tcp in tcp_any(), ref icmpv4 in icmpv4_header_any(), ref icmpv6 in icmpv6_header_any(), + ref igmp in igmp_header_any(), ref payload in proptest::collection::vec(any::(), 0..1024)) { let setup_eth = || -> ComponentTest { @@ -745,14 +769,14 @@ proptest! { // ethernet 2: standalone, ipv4, ipv6 setup_eth().run(); setup_eth().run_arp(arp); - setup_eth().run_ipv4(ipv4, ipv4_exts, udp, tcp, icmpv4, icmpv6); - setup_eth().run_ipv6(ipv6, ipv6_exts, udp, tcp, icmpv4, icmpv6); + setup_eth().run_ipv4(ipv4, ipv4_exts, udp, tcp, icmpv4, icmpv6, igmp); + setup_eth().run_ipv6(ipv6, ipv6_exts, udp, tcp, icmpv4, icmpv6, igmp); // link exts { let vlans = [vlan0.clone(), vlan1.clone(), vlan2.clone()]; let macsecs = [macsec0.clone(), macsec1.clone(), macsec2.clone()]; - setup_eth().run_link_exts(&vlans[..], &macsecs[..], arp, ipv4, ipv4_exts, ipv6, ipv6_exts, udp, tcp, icmpv4, icmpv6); + setup_eth().run_link_exts(&vlans[..], &macsecs[..], arp, ipv4, ipv4_exts, ipv6, ipv6_exts, udp, tcp, icmpv4, icmpv6, igmp); } } } diff --git a/etherparse/src/lax_packet_headers.rs b/etherparse/src/lax_packet_headers.rs index 776e9da7..e5365cbd 100644 --- a/etherparse/src/lax_packet_headers.rs +++ b/etherparse/src/lax_packet_headers.rs @@ -132,6 +132,12 @@ impl<'a> LaxPacketHeaders<'a> { /// println!(" Icmpv6 payload incomplete (length in IP header indicated more data should be present)"); /// } /// } + /// LaxPayloadSlice::Igmp{ payload, incomplete } => { + /// println!("Igmp payload: {:?}", payload); + /// if incomplete { + /// println!(" Igmp payload incomplete (length in IP header indicated more data should be present)"); + /// } + /// } /// LaxPayloadSlice::LinuxSll(linux_sll) => { /// println!("Linux SLL payload (protocol type {:?}): {:?}", linux_sll.protocol_type, linux_sll.payload); /// } @@ -258,6 +264,12 @@ impl<'a> LaxPacketHeaders<'a> { /// println!(" Icmpv6 payload incomplete (length in IP header indicated more data should be present)"); /// } /// } + /// LaxPayloadSlice::Igmp{ payload, incomplete } => { + /// println!("Igmp payload: {:?}", payload); + /// if incomplete { + /// println!(" Igmp payload incomplete (length in IP header indicated more data should be present)"); + /// } + /// } /// LaxPayloadSlice::LinuxSll(linux_sll) => { /// println!("Linux SLL payload (protocol type {:?}): {:?}", linux_sll.protocol_type, linux_sll.payload); /// } @@ -516,6 +528,12 @@ impl<'a> LaxPacketHeaders<'a> { /// println!(" Icmpv6 payload incomplete (length in IP header indicated more data should be present)"); /// } /// } + /// LaxPayloadSlice::Igmp{ payload, incomplete } => { + /// println!("Igmp payload: {:?}", payload); + /// if incomplete { + /// println!(" Igmp payload incomplete (length in IP header indicated more data should be present)"); + /// } + /// } /// } /// } /// } @@ -632,6 +650,12 @@ impl<'a> LaxPacketHeaders<'a> { /// println!(" Icmpv6 payload incomplete (length in IP header indicated more data should be present)"); /// } /// } + /// LaxPayloadSlice::Igmp{ payload, incomplete } => { + /// println!("Igmp payload: {:?}", payload); + /// if incomplete { + /// println!(" Igmp payload incomplete (length in IP header indicated more data should be present)"); + /// } + /// } /// LaxPayloadSlice::LinuxSll(linux_sll) => { /// println!("Linux SLL payload (protocol type {:?}): {:?}", linux_sll.protocol_type, linux_sll.payload); /// } @@ -781,6 +805,18 @@ impl<'a> LaxPacketHeaders<'a> { self.stop_err = Some((add_len_source(e), Layer::Icmpv6)); } }, + IGMP => match IgmpSlice::from_slice(ip_payload.payload) { + Ok(i) => { + self.transport = Some(TransportHeader::Igmp(i.header())); + self.payload = LaxPayloadSlice::Igmp { + payload: i.payload(), + incomplete: ip_payload.incomplete, + }; + } + Err(e) => { + self.stop_err = Some((add_len_source(e), Layer::Igmp)); + } + }, UDP => { match UdpSlice::from_slice_lax(ip_payload.payload) { Ok(u) => { @@ -1423,7 +1459,7 @@ mod test { for fragmented in [false, true] { let ipv4 = { let mut ipv4 = - Ipv4Header::new(0, 1, 2.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); + Ipv4Header::new(0, 1, 3.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); ipv4.more_fragments = fragmented; ipv4 }; @@ -2115,6 +2151,16 @@ mod test { } ); } + Some(H::Igmp(igmp)) => { + assert_eq!(&test.transport, &Some(H::Igmp(igmp.clone()))); + assert_eq!( + actual.payload, + LaxPayloadSlice::Igmp { + payload: expected_payload, + incomplete: false + } + ); + } Some(H::Udp(s)) => { assert_eq!(&test.transport, &Some(H::Udp(s.clone()))); assert_eq!( diff --git a/etherparse/src/lax_payload_slice.rs b/etherparse/src/lax_payload_slice.rs index 2c87c8f1..d3b298db 100644 --- a/etherparse/src/lax_payload_slice.rs +++ b/etherparse/src/lax_payload_slice.rs @@ -52,6 +52,14 @@ pub enum LaxPayloadSlice<'a> { incomplete: bool, }, + /// Payload part of an IGMP message. Check [`crate::IgmpType`] + /// for a description what will be part of the payload. + Igmp { + payload: &'a [u8], + /// True if the payload has been cut off. + incomplete: bool, + }, + /// Linux SLL payload. LinuxSll(LinuxSllPayloadSlice<'a>), } @@ -82,6 +90,10 @@ impl<'a> LaxPayloadSlice<'a> { payload, incomplete: _, } => payload, + LaxPayloadSlice::Igmp { + payload, + incomplete: _, + } => payload, LaxPayloadSlice::LinuxSll(linux_sll) => linux_sll.payload, } } @@ -200,6 +212,14 @@ mod test { .slice(), &payload ); + assert_eq!( + Igmp { + payload: &payload, + incomplete: false + } + .slice(), + &payload + ); assert_eq!( LinuxSll(LinuxSllPayloadSlice { protocol_type: LinuxSllProtocolType::Ignored(0), diff --git a/etherparse/src/lax_sliced_packet.rs b/etherparse/src/lax_sliced_packet.rs index cea10b7e..05f781ab 100644 --- a/etherparse/src/lax_sliced_packet.rs +++ b/etherparse/src/lax_sliced_packet.rs @@ -1038,7 +1038,7 @@ mod test { for fragmented in [false, true] { let ipv4 = { let mut ipv4 = - Ipv4Header::new(0, 1, 2.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); + Ipv4Header::new(0, 1, 3.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); ipv4.more_fragments = fragmented; ipv4 }; @@ -1814,6 +1814,10 @@ mod test { assert_eq!(&test.transport, &Some(H::Icmpv6(icmpv6.header()))); assert_eq!(icmpv6.payload(), expected_payload); } + Some(S::Igmp(igmp)) => { + assert_eq!(&test.transport, &Some(H::Igmp(igmp.header()))); + assert_eq!(igmp.payload(), expected_payload); + } Some(S::Udp(s)) => { assert_eq!(&test.transport, &Some(H::Udp(s.to_header()))); } diff --git a/etherparse/src/lax_sliced_packet_cursor.rs b/etherparse/src/lax_sliced_packet_cursor.rs index 60cb9e10..865d1f4b 100644 --- a/etherparse/src/lax_sliced_packet_cursor.rs +++ b/etherparse/src/lax_sliced_packet_cursor.rs @@ -342,6 +342,19 @@ impl<'a> LaxSlicedPacketCursor<'a> { self.result.stop_err = Some((O::Len(err), Layer::Icmpv6)); } }, + ip_number::IGMP => match IgmpSlice::from_slice(slice.payload) { + Ok(igmp) => { + self.offset += igmp.slice().len(); + self.result.transport = Some(TransportSlice::Igmp(igmp)); + } + Err(mut err) => { + err.layer_start_offset += self.offset; + if LenSource::Slice == err.len_source { + err.len_source = slice.len_source; + } + self.result.stop_err = Some((O::Len(err), Layer::Igmp)); + } + }, _ => {} } self.result diff --git a/etherparse/src/lib.rs b/etherparse/src/lib.rs index d9d8205f..3f1b3584 100644 --- a/etherparse/src/lib.rs +++ b/etherparse/src/lib.rs @@ -10,6 +10,7 @@ //! * UDP //! * TCP //! * ICMP & ICMPv6 (not all message types are supported) +//! * IGMP (v1, v2 & v3) //! //! Reconstruction of fragmented IP packets is also supported, but requires allocations. //! @@ -137,6 +138,7 @@ //! * [`TcpSlice::from_slice`] //! * [`Icmpv4Slice::from_slice`] //! * [`Icmpv6Slice::from_slice`] +//! * [`IgmpSlice::from_slice`] //! //! The resulting data types allow access to both the header(s) and the payload of the layer //! and will automatically limit the length of payload if the layer has a length field limiting the @@ -182,6 +184,7 @@ //! * [`TcpHeader::read`] & [`TcpHeader::from_slice`] //! * [`Icmpv4Header::read`] & [`Icmpv4Header::from_slice`] //! * [`Icmpv6Header::read`] & [`Icmpv6Header::from_slice`] +//! * [`IgmpHeader::from_slice`] //! //! # How to generate fake packet data? //! @@ -247,6 +250,7 @@ //! * [`TcpHeader::to_bytes`] & [`TcpHeader::write`] //! * [`Icmpv4Header::to_bytes`] & [`Icmpv4Header::write`] //! * [`Icmpv6Header::to_bytes`] & [`Icmpv6Header::write`] +//! * [`IgmpHeader::to_bytes`] & [`IgmpHeader::write`] //! //! # References //! * Darpa Internet Program Protocol Specification [RFC 791](https://tools.ietf.org/html/rfc791) diff --git a/etherparse/src/net/ipv6_fragment_header.rs b/etherparse/src/net/ipv6_fragment_header.rs index 06f6d7cd..97c73d50 100644 --- a/etherparse/src/net/ipv6_fragment_header.rs +++ b/etherparse/src/net/ipv6_fragment_header.rs @@ -409,7 +409,8 @@ mod test { // set. The offset fills bytes 2..3 bits 15..3 and M is bit 0 (bits // 2..1 are reserved & zero). { - let header = Ipv6FragmentHeader::new(UDP, IpFragOffset::try_new(8191).unwrap(), true, 0); + let header = + Ipv6FragmentHeader::new(UDP, IpFragOffset::try_new(8191).unwrap(), true, 0); let bytes = [UDP.0, 0, 0b1111_1111, 0b1111_1001, 0, 0, 0, 0]; assert_eq!(header.to_bytes(), bytes); assert_eq!(Ipv6FragmentHeader::from_slice(&bytes).unwrap().0, header); diff --git a/etherparse/src/packet_builder.rs b/etherparse/src/packet_builder.rs index 8d72238a..849afe23 100644 --- a/etherparse/src/packet_builder.rs +++ b/etherparse/src/packet_builder.rs @@ -1675,6 +1675,61 @@ impl PacketBuilderStep { } } + /// Adds an IGMP header based on the given [`IgmpType`]. + /// + /// The checksum is calculated automatically during serialization. + /// + /// # Example + /// + /// Basic usage: + /// + /// ``` + /// # use etherparse::{PacketBuilder, IgmpType, igmp::MembershipReportV2Type}; + /// # + /// let builder = PacketBuilder:: + /// ipv4([192,168,1,1], // source ip + /// [192,168,1,2], // destination ip + /// 20) // time to life + /// .igmp(IgmpType::MembershipReportV2(MembershipReportV2Type { + /// group_address: [224, 0, 0, 1].into(), + /// })); + /// + /// // the payload is written after the IGMP header + /// let payload = []; + /// + /// // get some memory to store the result + /// let mut result = Vec::::with_capacity( + /// builder.size(payload.len())); + /// + /// // serialize + /// builder.write(&mut result, &payload).unwrap(); + /// ``` + pub fn igmp(mut self, igmp_type: IgmpType) -> PacketBuilderStep { + self.state.transport_header = Some(TransportHeader::Igmp(IgmpHeader { + igmp_type, + checksum: 0, // calculated later + })); + //return for next step + PacketBuilderStep { + state: self.state, + _marker: marker::PhantomData:: {}, + } + } + + /// Adds an IGMP header. + /// + /// The checksum is calculated automatically during serialization + /// (the `checksum` field of the given header is ignored & overwritten). + pub fn igmp_header(mut self, mut igmp_header: IgmpHeader) -> PacketBuilderStep { + igmp_header.checksum = 0; // calculated later + self.state.transport_header = Some(TransportHeader::Igmp(igmp_header)); + //return for next step + PacketBuilderStep { + state: self.state, + _marker: marker::PhantomData:: {}, + } + } + /// Write all the headers and the payload with the given ip number. /// /// `last_next_header_ip_number` will be set in the last extension header @@ -1826,6 +1881,45 @@ impl PacketBuilderStep { } } +impl PacketBuilderStep { + /// Write all the headers and the payload. + #[cfg(feature = "std")] + pub fn write( + self, + writer: &mut T, + payload: &[u8], + ) -> Result<(), BuildWriteError> { + final_write_with_net::<_, _, BuildWriteError>(self, &mut IoWriter(writer), payload) + } + + /// Write all the headers and the payload to a [`Vec`]. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] + pub fn write_to_vec( + self, + buffer: &mut Vec, + payload: &[u8], + ) -> Result<(), BuildVecWriteError> { + final_write_with_net::<_, _, BuildVecWriteError>(self, &mut VecWriter(buffer), payload) + } + + /// Write all the headers and the payload to a byte slice. + /// + /// Returns the number of bytes written. + pub fn write_to_slice( + self, + buffer: &mut [u8], + payload: &[u8], + ) -> Result { + final_write_to_slice(self, buffer, payload) + } + + ///Returns the size of the packet when it is serialized + pub fn size(&self, payload_size: usize) -> usize { + final_size(self, payload_size) + } +} + impl PacketBuilderStep { ///Write all the headers and the payload. #[cfg(feature = "std")] @@ -2162,6 +2256,7 @@ where Some(Tcp(_)) => {} Some(Icmpv4(_)) => {} Some(Icmpv6(_)) => {} + Some(Igmp(_)) => {} None => {} } @@ -2180,6 +2275,7 @@ where ip.protocol = ip_exts.set_next_headers(match &transport { Icmpv4(_) => ip_number::ICMP, Icmpv6(_) => ip_number::IPV6_ICMP, + Igmp(_) => ip_number::IGMP, Udp(_) => ip_number::UDP, Tcp(_) => ip_number::TCP, }); @@ -2210,6 +2306,7 @@ where ip.next_header = ip_exts.set_next_headers(match &transport { Icmpv4(_) => ip_number::ICMP, Icmpv6(_) => ip_number::IPV6_ICMP, + Igmp(_) => ip_number::IGMP, Udp(_) => ip_number::UDP, Tcp(_) => ip_number::TCP, }); @@ -2241,6 +2338,7 @@ where TransportHeader::Icmpv6(value) => { writer.write_all(&value.to_bytes()).map_err(E::from)? } + TransportHeader::Igmp(value) => writer.write_all(&value.to_bytes()).map_err(E::from)?, TransportHeader::Udp(value) => writer.write_all(&value.to_bytes()).map_err(E::from)?, TransportHeader::Tcp(value) => writer.write_all(&value.to_bytes()).map_err(E::from)?, } @@ -2271,6 +2369,7 @@ fn final_size(builder: &PacketBuilderStep, payload_size: usize) -> usize { } + match builder.state.transport_header { Some(Icmpv4(ref value)) => value.header_len(), Some(Icmpv6(ref value)) => value.header_len(), + Some(Igmp(ref value)) => value.header_len(), Some(Udp(_)) => UdpHeader::LEN, Some(Tcp(ref value)) => value.header_len(), None => 0, diff --git a/etherparse/src/packet_headers.rs b/etherparse/src/packet_headers.rs index 84fd8c7b..c2e1f953 100644 --- a/etherparse/src/packet_headers.rs +++ b/etherparse/src/packet_headers.rs @@ -476,6 +476,14 @@ fn read_transport( PayloadSlice::Icmpv6(value.payload()), ) }), + IGMP => IgmpSlice::from_slice(ip_payload.payload) + .map_err(add_len_source) + .map(|value| { + ( + Some(TransportHeader::Igmp(value.header())), + PayloadSlice::Igmp(value.payload()), + ) + }), UDP => UdpHeader::from_slice(ip_payload.payload) .map_err(add_len_source) .map(|value| { @@ -914,7 +922,7 @@ mod test { for fragmented in [false, true] { let ipv4 = { let mut ipv4 = - Ipv4Header::new(0, 1, 2.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); + Ipv4Header::new(0, 1, 3.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); ipv4.more_fragments = fragmented; ipv4 }; diff --git a/etherparse/src/payload_slice.rs b/etherparse/src/payload_slice.rs index bd9241ca..850ee669 100644 --- a/etherparse/src/payload_slice.rs +++ b/etherparse/src/payload_slice.rs @@ -30,6 +30,10 @@ pub enum PayloadSlice<'a> { /// Payload part of an ICMP V4 message. Check [`crate::Icmpv6Type`] /// for a description what will be part of the payload. Icmpv6(&'a [u8]), + + /// Payload part of an IGMP message. Check [`crate::IgmpType`] + /// for a description what will be part of the payload. + Igmp(&'a [u8]), } impl<'a> PayloadSlice<'a> { @@ -43,6 +47,7 @@ impl<'a> PayloadSlice<'a> { PayloadSlice::Tcp(s) => s, PayloadSlice::Icmpv4(s) => s, PayloadSlice::Icmpv6(s) => s, + PayloadSlice::Igmp(s) => s, } } } @@ -113,5 +118,6 @@ mod test { assert_eq!(Tcp(&payload).slice(), &payload); assert_eq!(Icmpv4(&payload).slice(), &payload); assert_eq!(Icmpv6(&payload).slice(), &payload); + assert_eq!(Igmp(&payload).slice(), &payload); } } diff --git a/etherparse/src/sliced_packet.rs b/etherparse/src/sliced_packet.rs index 86e8b9e0..6eabca23 100644 --- a/etherparse/src/sliced_packet.rs +++ b/etherparse/src/sliced_packet.rs @@ -1474,7 +1474,7 @@ mod test { for fragmented in [false, true] { let ipv4 = { let mut ipv4 = - Ipv4Header::new(0, 1, 2.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); + Ipv4Header::new(0, 1, 3.into(), [3, 4, 5, 6], [7, 8, 9, 10]).unwrap(); ipv4.more_fragments = fragmented; ipv4 }; @@ -2124,6 +2124,10 @@ mod test { assert_eq!(&test.transport, &Some(H::Icmpv6(icmpv6.header()))); assert_eq!(icmpv6.payload(), expected_payload); } + Some(S::Igmp(igmp)) => { + assert_eq!(&test.transport, &Some(H::Igmp(igmp.header()))); + assert_eq!(igmp.payload(), expected_payload); + } Some(S::Udp(s)) => { assert_eq!(&test.transport, &Some(H::Udp(s.to_header()))); } diff --git a/etherparse/src/sliced_packet_cursor.rs b/etherparse/src/sliced_packet_cursor.rs index faa1775d..7d5e96f7 100644 --- a/etherparse/src/sliced_packet_cursor.rs +++ b/etherparse/src/sliced_packet_cursor.rs @@ -196,6 +196,7 @@ impl<'a> SlicedPacketCursor<'a> { } }), ip_number::IPV6_ICMP => self.slice_icmp6(payload.payload).map_err(Len), + ip_number::IGMP => self.slice_igmp(payload.payload).map_err(Len), _ => Ok(self.result), } } @@ -247,6 +248,7 @@ impl<'a> SlicedPacketCursor<'a> { }), ip_number::ICMP => self.slice_icmp4(payload.payload).map_err(Len), ip_number::IPV6_ICMP => self.slice_icmp6(payload.payload).map_err(Len), + ip_number::IGMP => self.slice_igmp(payload.payload).map_err(Len), _ => Ok(self.result), } } @@ -300,6 +302,7 @@ impl<'a> SlicedPacketCursor<'a> { } }), ip_number::IPV6_ICMP => self.slice_icmp6(payload.payload).map_err(Len), + ip_number::IGMP => self.slice_igmp(payload.payload).map_err(Len), _ => Ok(self.result), } } @@ -358,6 +361,25 @@ impl<'a> SlicedPacketCursor<'a> { Ok(self.result) } + pub fn slice_igmp(mut self, slice: &'a [u8]) -> Result, err::LenError> { + use crate::TransportSlice::*; + + let result = IgmpSlice::from_slice(slice).map_err(|mut err| { + err.layer_start_offset += self.offset; + if LenSource::Slice == err.len_source { + err.len_source = self.len_source; + } + err + })?; + + //set the new data + self.offset += result.slice().len(); + self.result.transport = Some(Igmp(result.clone())); + + //done + Ok(self.result) + } + pub fn slice_udp(mut self, slice: &'a [u8]) -> Result, err::LenError> { use crate::TransportSlice::*; diff --git a/etherparse/src/test_gens/mod.rs b/etherparse/src/test_gens/mod.rs index ad5871ea..8cae2c46 100644 --- a/etherparse/src/test_gens/mod.rs +++ b/etherparse/src/test_gens/mod.rs @@ -482,6 +482,7 @@ prop_compose! { static IPV4_KNOWN_PROTOCOLS: &[IpNumber] = &[ ip_number::ICMP, + ip_number::IGMP, ip_number::UDP, ip_number::TCP, ip_number::AUTH, @@ -580,6 +581,7 @@ prop_compose! { static IPV6_KNOWN_NEXT_HEADERS: &[IpNumber] = &[ ip_number::ICMP, + ip_number::IGMP, ip_number::UDP, ip_number::TCP, ip_number::IPV6_HOP_BY_HOP, @@ -884,6 +886,25 @@ prop_compose! { } } +prop_compose! { + /// Generates a simple, fixed-length (8 byte header) IGMP header + /// (an IGMPv2 "Membership Report") suitable for round-trip tests + /// with arbitrary trailing payloads. + pub fn igmp_header_any() + ( + group_address in any::<[u8;4]>(), + checksum in any::(), + ) -> IgmpHeader + { + IgmpHeader { + igmp_type: IgmpType::MembershipReportV2(crate::igmp::MembershipReportV2Type { + group_address: group_address.into(), + }), + checksum, + } + } +} + prop_compose! { pub fn icmpv6_type_any() ( diff --git a/etherparse/src/test_packet.rs b/etherparse/src/test_packet.rs index f3e66f29..e80bfef8 100644 --- a/etherparse/src/test_packet.rs +++ b/etherparse/src/test_packet.rs @@ -141,6 +141,7 @@ impl TestPacket { Some(Tcp(_)) => {} Some(Icmpv4(_)) => {} Some(Icmpv6(_)) => {} + Some(Igmp(_)) => {} } } diff --git a/etherparse/src/transport/igmp/igmp_unknown_slice.rs b/etherparse/src/transport/igmp/igmp_unknown_slice.rs new file mode 100644 index 00000000..950e5099 --- /dev/null +++ b/etherparse/src/transport/igmp/igmp_unknown_slice.rs @@ -0,0 +1,86 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMP message with a type unknown to etherparse. +/// +/// See [`UnknownHeader`] for the field layout. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IgmpUnknownSlice<'a> { + slice: &'a [u8], +} + +impl<'a> IgmpUnknownSlice<'a> { + /// Creates a slice from bytes without checking the length. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`UnknownHeader::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> IgmpUnknownSlice<'a> { + debug_assert!(slice.len() >= UnknownHeader::LEN); + IgmpUnknownSlice { slice } + } + + /// Returns the "type" byte value in the IGMP header. + #[inline] + pub fn type_u8(&self) -> u8 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { *self.slice.get_unchecked(0) } + } + + /// Returns the raw byte value after the type value. + #[inline] + pub fn raw_byte_1(&self) -> u8 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { *self.slice.get_unchecked(1) } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// Returns the raw byte values after the checksum (bytes 4-7). + #[inline] + pub fn raw_bytes_4_7(&self) -> [u8; 4] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + } + } + + /// Decodes the fixed fields into an owned [`UnknownHeader`]. + #[inline] + pub fn to_header(&self) -> UnknownHeader { + UnknownHeader { + igmp_type: self.type_u8(), + raw_byte_1: self.raw_byte_1(), + raw_bytes_4_7: self.raw_bytes_4_7(), + } + } + + /// Returns the bytes after the 8-byte header. + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice.as_ptr().add(UnknownHeader::LEN), + self.slice.len() - UnknownHeader::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/leave_group_slice.rs b/etherparse/src/transport/igmp/leave_group_slice.rs new file mode 100644 index 00000000..1eb88832 --- /dev/null +++ b/etherparse/src/transport/igmp/leave_group_slice.rs @@ -0,0 +1,78 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv2 "Leave Group" message (type `0x17`). +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type = 0x11 | 0 | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Group Address | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct LeaveGroupSlice<'a> { + slice: &'a [u8], +} + +impl<'a> LeaveGroupSlice<'a> { + /// Creates a slice from bytes without checking the length. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`LeaveGroupType::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> LeaveGroupSlice<'a> { + debug_assert!(slice.len() >= LeaveGroupType::LEN); + LeaveGroupSlice { slice } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// The IP multicast group address of the group being left. + #[inline] + pub fn group_address(&self) -> GroupAddress { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + GroupAddress::new(unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + }) + } + + /// Decodes the fixed fields into an owned [`LeaveGroupType`]. + #[inline] + pub fn to_header(&self) -> LeaveGroupType { + LeaveGroupType { + group_address: self.group_address(), + } + } + + /// Returns the bytes after the 8-byte header (usually empty). + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice.as_ptr().add(LeaveGroupType::LEN), + self.slice.len() - LeaveGroupType::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/membership_query_slice.rs b/etherparse/src/transport/igmp/membership_query_slice.rs new file mode 100644 index 00000000..447ef195 --- /dev/null +++ b/etherparse/src/transport/igmp/membership_query_slice.rs @@ -0,0 +1,76 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv1/IGMPv2 "Membership Query" (type `0x11`, +/// exactly 8 octets long). +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type = 0x11 | Max Resp Time | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Group Address | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MembershipQuerySlice<'a> { + slice: &'a [u8], +} + +impl<'a> MembershipQuerySlice<'a> { + /// Creates a slice from bytes without checking that they contain a + /// valid IGMPv1/v2 query. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`MembershipQueryType::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> MembershipQuerySlice<'a> { + debug_assert!(slice.len() >= MembershipQueryType::LEN); + MembershipQuerySlice { slice } + } + + /// The maximum response time (0 for IGMPv1, non-zero for IGMPv2). + #[inline] + pub fn max_response_time(&self) -> u8 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { *self.slice.get_unchecked(1) } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// The group address being queried. + #[inline] + pub fn group_address(&self) -> GroupAddress { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + GroupAddress::new(unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + }) + } + + /// Decodes the fixed fields into an owned [`MembershipQueryType`]. + #[inline] + pub fn to_header(&self) -> MembershipQueryType { + MembershipQueryType { + max_response_time: self.max_response_time(), + group_address: self.group_address(), + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/membership_query_with_sources_slice.rs b/etherparse/src/transport/igmp/membership_query_with_sources_slice.rs new file mode 100644 index 00000000..068245d4 --- /dev/null +++ b/etherparse/src/transport/igmp/membership_query_with_sources_slice.rs @@ -0,0 +1,190 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv3 "Membership Query" (type `0x11`, at +/// least 12 octets long) including the source address list. +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// | Type = 0x11 | Max Resp Code | Checksum | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | part of header and +/// | Group Address | | this type +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +/// | Flags |S| QRV | QQIC | Number of Sources (N) | ↓ +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// | Source Address [1] | | +/// +- -+ | +/// | Source Address [2] | | +/// +- . -+ | part of payload +/// . . . | +/// . . . | +/// +- -+ | +/// | Source Address [N] | ↓ +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MembershipQueryWithSourcesSlice<'a> { + slice: &'a [u8], +} + +impl<'a> MembershipQueryWithSourcesSlice<'a> { + /// Creates a slice from bytes without checking that they contain a + /// valid IGMPv3 query. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` contains the complete + /// declared payload: + /// + /// * at least [`MembershipQueryWithSourcesHeader::LEN`] (12) bytes for + /// the header, and + /// * additionally `num_of_sources * 4` bytes for the source address + /// list, where `num_of_sources` is the `u16` read from bytes 10..12 + /// of the header. + /// + /// In other words `slice.len()` must be at least + /// `12 + num_of_sources * 4` so that all source addresses accessed by + /// [`MembershipQueryWithSourcesSlice::source_addrs_bytes`] are in + /// bounds. + #[inline] + pub(crate) unsafe fn from_slice_unchecked( + slice: &'a [u8], + ) -> MembershipQueryWithSourcesSlice<'a> { + debug_assert!(slice.len() >= MembershipQueryWithSourcesHeader::LEN); + // The slice must also be long enough to hold all declared source + // addresses (4 bytes each). `IgmpSlice::from_slice` guarantees this. + debug_assert!( + slice.len() + >= MembershipQueryWithSourcesHeader::LEN + + usize::from(u16::from_be_bytes([slice[10], slice[11]])) * 4 + ); + MembershipQueryWithSourcesSlice { slice } + } + + /// The Max Resp Code field. + #[inline] + pub fn max_response_code(&self) -> MaxResponseCode { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + MaxResponseCode(unsafe { *self.slice.get_unchecked(1) }) + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// The group address being queried. + #[inline] + pub fn group_address(&self) -> GroupAddress { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + GroupAddress::new(unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + }) + } + + /// Raw byte containing "flags", "s" & "QRV". + #[inline] + pub fn raw_byte_8(&self) -> u8 { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + unsafe { *self.slice.get_unchecked(8) } + } + + /// Extracts the "flags" from the `raw_byte_8` field. + #[inline] + pub fn flags(&self) -> u8 { + (self.raw_byte_8() & MembershipQueryWithSourcesHeader::RAW_BYTE_8_MASK_FLAGS) + >> MembershipQueryWithSourcesHeader::RAW_BYTE_8_OFFSET_FLAGS + } + + /// Extracts the S flag (Suppress Router-Side Processing). + #[inline] + pub fn s_flag(&self) -> bool { + 0 != (self.raw_byte_8() & MembershipQueryWithSourcesHeader::RAW_BYTE_8_MASK_S_FLAG) + } + + /// Extracts the QRV (Querier's Robustness Variable). + #[inline] + pub fn qrv(&self) -> Qrv { + // SAFETY: the value is guaranteed to be within range after the mask. + unsafe { + Qrv::new_unchecked( + self.raw_byte_8() & MembershipQueryWithSourcesHeader::RAW_BYTE_8_MASK_QRV, + ) + } + } + + /// QQIC (Querier's Query Interval Code). + #[inline] + pub fn qqic(&self) -> u8 { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + unsafe { *self.slice.get_unchecked(9) } + } + + /// Number of source addresses declared in the query header. + #[inline] + pub fn num_of_sources(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(10)) } + } + + /// Decodes the fixed fields into an owned [`MembershipQueryWithSourcesHeader`]. + #[inline] + pub fn to_header(&self) -> MembershipQueryWithSourcesHeader { + MembershipQueryWithSourcesHeader { + max_response_code: self.max_response_code(), + group_address: self.group_address(), + raw_byte_8: self.raw_byte_8(), + qqic: self.qqic(), + num_of_sources: self.num_of_sources(), + } + } + + /// Returns the raw source address bytes. + /// + /// The returned slice contains `num_of_sources * 4` bytes (each 4 + /// consecutive bytes are one IPv4 source address). + #[inline] + pub fn source_addrs_bytes(&self) -> &'a [u8] { + let payload = self.payload(); + let len = usize::from(self.num_of_sources()) * 4; + // SAFETY: `IgmpSlice::from_slice` guarantees the payload contains + // all declared source addresses (num_of_sources * 4 bytes). + unsafe { core::slice::from_raw_parts(payload.as_ptr(), len) } + } + + /// Returns an iterator over the source addresses as `[u8; 4]` arrays. + #[inline] + pub fn source_addresses(&self) -> impl ExactSizeIterator + 'a { + self.source_addrs_bytes() + .chunks_exact(4) + .map(|c| [c[0], c[1], c[2], c[3]]) + } + + /// Returns the bytes after the 12-byte header (the source address list). + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 12 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice + .as_ptr() + .add(MembershipQueryWithSourcesHeader::LEN), + self.slice.len() - MembershipQueryWithSourcesHeader::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/membership_report_v1_slice.rs b/etherparse/src/transport/igmp/membership_report_v1_slice.rs new file mode 100644 index 00000000..bf3374f1 --- /dev/null +++ b/etherparse/src/transport/igmp/membership_report_v1_slice.rs @@ -0,0 +1,78 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv1 "Membership Report" (type `0x12`). +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type = 0x12 | Unused | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Group Address | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MembershipReportV1Slice<'a> { + slice: &'a [u8], +} + +impl<'a> MembershipReportV1Slice<'a> { + /// Creates a slice from bytes without checking the length. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`MembershipReportV1Type::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> MembershipReportV1Slice<'a> { + debug_assert!(slice.len() >= MembershipReportV1Type::LEN); + MembershipReportV1Slice { slice } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// The IP multicast group address of the group being reported. + #[inline] + pub fn group_address(&self) -> GroupAddress { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + GroupAddress::new(unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + }) + } + + /// Decodes the fixed fields into an owned [`MembershipReportV1Type`]. + #[inline] + pub fn to_header(&self) -> MembershipReportV1Type { + MembershipReportV1Type { + group_address: self.group_address(), + } + } + + /// Returns the bytes after the 8-byte header (usually empty). + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice.as_ptr().add(MembershipReportV1Type::LEN), + self.slice.len() - MembershipReportV1Type::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/membership_report_v2_slice.rs b/etherparse/src/transport/igmp/membership_report_v2_slice.rs new file mode 100644 index 00000000..2107b7b8 --- /dev/null +++ b/etherparse/src/transport/igmp/membership_report_v2_slice.rs @@ -0,0 +1,78 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv2 "Membership Report" (type `0x16`). +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Type = 0x16 | Unused | Checksum | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// | Group Address | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MembershipReportV2Slice<'a> { + slice: &'a [u8], +} + +impl<'a> MembershipReportV2Slice<'a> { + /// Creates a slice from bytes without checking the length. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`MembershipReportV2Type::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> MembershipReportV2Slice<'a> { + debug_assert!(slice.len() >= MembershipReportV2Type::LEN); + MembershipReportV2Slice { slice } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// The IP multicast group address of the group being reported. + #[inline] + pub fn group_address(&self) -> GroupAddress { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + GroupAddress::new(unsafe { + [ + *self.slice.get_unchecked(4), + *self.slice.get_unchecked(5), + *self.slice.get_unchecked(6), + *self.slice.get_unchecked(7), + ] + }) + } + + /// Decodes the fixed fields into an owned [`MembershipReportV2Type`]. + #[inline] + pub fn to_header(&self) -> MembershipReportV2Type { + MembershipReportV2Type { + group_address: self.group_address(), + } + } + + /// Returns the bytes after the 8-byte header (usually empty). + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice.as_ptr().add(MembershipReportV2Type::LEN), + self.slice.len() - MembershipReportV2Type::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/membership_report_v3_slice.rs b/etherparse/src/transport/igmp/membership_report_v3_slice.rs new file mode 100644 index 00000000..fbf067c6 --- /dev/null +++ b/etherparse/src/transport/igmp/membership_report_v3_slice.rs @@ -0,0 +1,112 @@ +use crate::{igmp::*, *}; + +/// Zero-copy slice of an IGMPv3 "Membership Report" (type `0x22`) +/// including the list of group records. +/// +/// ```text +/// 0 1 2 3 +/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// | Type = 0x22 | Reserved | Checksum | | part of header & +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | this type +/// | Flags | Number of Group Records (M) | ↓ +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// | | | +/// . . | +/// . Group Record [1] . | +/// . . | +/// | | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +/// | | | +/// . . | +/// . Group Record [2] . | part of payload +/// . . | +/// | | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +/// | . | | +/// . . . | +/// | . | | +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | +/// | | | +/// . . | +/// . Group Record [M] . | +/// . . | +/// | | ↓ +/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - +/// ``` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MembershipReportV3Slice<'a> { + slice: &'a [u8], +} + +impl<'a> MembershipReportV3Slice<'a> { + /// Creates a slice from bytes without checking the length. + /// + /// # Safety + /// + /// The caller must guarantee that `slice` is at least + /// [`MembershipReportV3Header::LEN`] (8) bytes long. + #[inline] + pub(crate) unsafe fn from_slice_unchecked(slice: &'a [u8]) -> MembershipReportV3Slice<'a> { + debug_assert!(slice.len() >= MembershipReportV3Header::LEN); + MembershipReportV3Slice { slice } + } + + /// Returns the "checksum" value stored in the IGMP header. + #[inline] + pub fn checksum(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + } + + /// Additional flags (bytes 4-5 of the header). + #[inline] + pub fn flags(&self) -> [u8; 2] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { [*self.slice.get_unchecked(4), *self.slice.get_unchecked(5)] } + } + + /// Number of group records declared in the report header. + #[inline] + pub fn num_of_records(&self) -> u16 { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(6)) } + } + + /// Decodes the fixed fields into an owned [`MembershipReportV3Header`]. + #[inline] + pub fn to_header(&self) -> MembershipReportV3Header { + MembershipReportV3Header { + flags: self.flags(), + num_of_records: self.num_of_records(), + } + } + + /// Returns an iterator over the group records. + /// + /// The iterator yields at most [`MembershipReportV3Slice::num_of_records`] + /// items and stops early with an [`err::LenError`] if the payload is + /// shorter than the records claim. + #[inline] + pub fn group_records(&self) -> ReportGroupRecordV3SliceIter<'a> { + ReportGroupRecordV3SliceIter::new(self.payload(), self.num_of_records()) + } + + /// Returns the bytes after the 8-byte header (the group records). + #[inline] + pub fn payload(&self) -> &'a [u8] { + // SAFETY: from_slice_unchecked guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + self.slice.as_ptr().add(MembershipReportV3Header::LEN), + self.slice.len() - MembershipReportV3Header::LEN, + ) + } + } + + /// Returns the slice containing the entire IGMP message. + #[inline] + pub fn slice(&self) -> &'a [u8] { + self.slice + } +} diff --git a/etherparse/src/transport/igmp/mod.rs b/etherparse/src/transport/igmp/mod.rs index cb2cfb8c..ee192549 100644 --- a/etherparse/src/transport/igmp/mod.rs +++ b/etherparse/src/transport/igmp/mod.rs @@ -1,27 +1,45 @@ mod group_address; pub use group_address::*; +mod leave_group_slice; +pub use leave_group_slice::*; + mod leave_group_type; pub use leave_group_type::*; mod max_response_code; pub use max_response_code::*; +mod membership_query_slice; +pub use membership_query_slice::*; + mod membership_query_type; pub use membership_query_type::*; mod membership_query_with_sources_header; pub use membership_query_with_sources_header::*; +mod membership_query_with_sources_slice; +pub use membership_query_with_sources_slice::*; + +mod membership_report_v1_slice; +pub use membership_report_v1_slice::*; + mod membership_report_v1_type; pub use membership_report_v1_type::*; +mod membership_report_v2_slice; +pub use membership_report_v2_slice::*; + mod membership_report_v2_type; pub use membership_report_v2_type::*; mod membership_report_v3_header; pub use membership_report_v3_header::*; +mod membership_report_v3_slice; +pub use membership_report_v3_slice::*; + mod qrv; pub use qrv::*; @@ -34,6 +52,9 @@ pub use report_group_record_v3_header::*; mod report_group_record_v3_slice; pub use report_group_record_v3_slice::*; +mod igmp_unknown_slice; +pub use igmp_unknown_slice::*; + mod unknown_header; pub use unknown_header::*; diff --git a/etherparse/src/transport/igmp/report_group_record_v3_slice.rs b/etherparse/src/transport/igmp/report_group_record_v3_slice.rs index 9f12317d..f117a30d 100644 --- a/etherparse/src/transport/igmp/report_group_record_v3_slice.rs +++ b/etherparse/src/transport/igmp/report_group_record_v3_slice.rs @@ -84,7 +84,7 @@ impl<'a> ReportGroupRecordV3Slice<'a> { /// Decode the fixed header into a [`ReportGroupRecordV3Header`]. #[inline] pub fn header(&self) -> ReportGroupRecordV3Header { - // SAFETY: from_slice guarantees at least LEN bytes. + // `from_slice` guarantees at least LEN bytes, so this cannot fail. let (header, _) = ReportGroupRecordV3Header::from_slice(self.slice).unwrap(); header } @@ -136,6 +136,14 @@ impl<'a> ReportGroupRecordV3Slice<'a> { unsafe { core::slice::from_raw_parts(self.slice.as_ptr().add(start), len) } } + /// Returns an iterator over the source addresses as `[u8; 4]` arrays. + #[inline] + pub fn source_addresses(&self) -> impl ExactSizeIterator + 'a { + self.source_addrs_bytes() + .chunks_exact(4) + .map(|c| [c[0], c[1], c[2], c[3]]) + } + /// Returns the auxiliary data bytes. #[inline] pub fn aux_data(&self) -> &'a [u8] { @@ -197,6 +205,8 @@ impl<'a> Iterator for ReportGroupRecordV3SliceIter<'a> { } } +impl core::iter::FusedIterator for ReportGroupRecordV3SliceIter<'_> {} + #[cfg(test)] mod test { use super::*; @@ -250,6 +260,9 @@ mod test { assert_eq!(rest, &[0xFF]); assert_eq!(slice.num_of_sources(), 2); assert_eq!(slice.source_addrs_bytes(), &[10, 0, 0, 1, 10, 0, 0, 2]); + let addrs: Vec<_> = slice.source_addresses().collect(); + assert_eq!(addrs, vec![[10, 0, 0, 1], [10, 0, 0, 2]]); + assert_eq!(slice.source_addresses().len(), 2); } #[test] diff --git a/etherparse/src/transport/igmp_header.rs b/etherparse/src/transport/igmp_header.rs index 6d6e5a31..7516c20f 100644 --- a/etherparse/src/transport/igmp_header.rs +++ b/etherparse/src/transport/igmp_header.rs @@ -386,6 +386,13 @@ impl IgmpHeader { } } + /// Write the IGMP header to the given writer. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + pub fn write(&self, writer: &mut T) -> Result<(), std::io::Error> { + writer.write_all(self.to_bytes().as_slice()) + } + /// Converts the header to on-the-wire bytes. pub fn to_bytes(&self) -> ArrayVec { use IgmpType::*; diff --git a/etherparse/src/transport/igmp_slice.rs b/etherparse/src/transport/igmp_slice.rs index 8048d24a..89b16930 100644 --- a/etherparse/src/transport/igmp_slice.rs +++ b/etherparse/src/transport/igmp_slice.rs @@ -1,9 +1,13 @@ use crate::{igmp::*, *}; -/// A slice containing an IGMP network packet. +/// A zero-copy slice of an IGMP network packet, decoded into one variant +/// per message type. /// -/// Struct allows the selective read of fields in the IGMP -/// packet without copying the data. +/// This mirrors [`IgmpType`] but keeps the variable-length parts (source +/// address lists & group records) as zero-copy slices instead of copying +/// them. Match on the variant to get typed, compile-time-checked access +/// to the message specific accessors (e.g. `group_records` is only +/// reachable on [`IgmpSlice::MembershipReportV3`]). /// /// # Important: Caller must trim to IGMP message length /// @@ -19,8 +23,27 @@ use crate::{igmp::*, *}; /// calling [`IgmpSlice::from_slice`]. If extra trailing bytes are /// present, a query may be misidentified as IGMPv3. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct IgmpSlice<'a> { - slice: &'a [u8], +pub enum IgmpSlice<'a> { + /// Membership Query message (IGMPv1 & IGMPv2, type `0x11`, 8 octets). + MembershipQuery(MembershipQuerySlice<'a>), + + /// Membership Query message (IGMPv3, type `0x11`, >= 12 octets) with sources. + MembershipQueryWithSources(MembershipQueryWithSourcesSlice<'a>), + + /// Membership Report message (IGMPv1, type `0x12`). + MembershipReportV1(MembershipReportV1Slice<'a>), + + /// Membership Report message (IGMPv2, type `0x16`). + MembershipReportV2(MembershipReportV2Slice<'a>), + + /// Membership Report message (IGMPv3, type `0x22`) with group records. + MembershipReportV3(MembershipReportV3Slice<'a>), + + /// Leave Group message (IGMPv2, type `0x17`). + LeaveGroup(LeaveGroupSlice<'a>), + + /// Unknown type of IGMP message. + Unknown(IgmpUnknownSlice<'a>), } impl<'a> IgmpSlice<'a> { @@ -34,17 +57,88 @@ impl<'a> IgmpSlice<'a> { /// Query (which is invalid per RFC 9776). #[inline] pub fn from_slice(slice: &'a [u8]) -> Result, err::LenError> { - // Validate by attempting to parse the header. This checks both - // the minimum length and the 9-11 byte invalid range for queries. - let _ = IgmpHeader::from_slice(slice)?; - Ok(IgmpSlice { slice }) + // Ensure the slice is large enough for the minimum IGMP header. + if slice.len() < IgmpHeader::MIN_LEN { + return Err(err::LenError { + required_len: IgmpHeader::MIN_LEN, + len: slice.len(), + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + }); + } + + // SAFETY: length checked above to be >= IgmpHeader::MIN_LEN (8). + let type_u8 = unsafe { *slice.get_unchecked(0) }; + Ok(match type_u8 { + IGMP_TYPE_MEMBERSHIP_QUERY => { + if slice.len() == MembershipQueryType::LEN { + // A query of exactly 8 bytes is an IGMPv1/v2 query. + // SAFETY: length is exactly MembershipQueryType::LEN (8). + IgmpSlice::MembershipQuery(unsafe { + MembershipQuerySlice::from_slice_unchecked(slice) + }) + } else if slice.len() >= MembershipQueryWithSourcesHeader::LEN { + // A query of at least 12 bytes is an IGMPv3 query. + // Validate that all declared source addresses (4 bytes + // each) are actually present in the payload. + // SAFETY: length checked above to be >= 12, so bytes 10..12 exist. + let num_of_sources = + usize::from(unsafe { get_unchecked_be_u16(slice.as_ptr().add(10)) }); + let required_len = MembershipQueryWithSourcesHeader::LEN + num_of_sources * 4; + if slice.len() < required_len { + return Err(err::LenError { + required_len, + len: slice.len(), + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + }); + } + // SAFETY: length checked to be >= MembershipQueryWithSourcesHeader::LEN (12). + IgmpSlice::MembershipQueryWithSources(unsafe { + MembershipQueryWithSourcesSlice::from_slice_unchecked(slice) + }) + } else { + // A query with a length of 9-11 bytes is invalid per RFC 9776. + return Err(err::LenError { + required_len: MembershipQueryWithSourcesHeader::LEN, + len: slice.len(), + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + }); + } + } + // SAFETY: length checked above to be >= 8. + IGMPV1_TYPE_MEMBERSHIP_REPORT => IgmpSlice::MembershipReportV1(unsafe { + MembershipReportV1Slice::from_slice_unchecked(slice) + }), + // SAFETY: length checked above to be >= 8. + IGMPV2_TYPE_MEMBERSHIP_REPORT => IgmpSlice::MembershipReportV2(unsafe { + MembershipReportV2Slice::from_slice_unchecked(slice) + }), + // The group records of a v3 report are variable length and are + // NOT validated here. Use `MembershipReportV3Slice::group_records` + // to iterate them; the iterator reports length errors lazily. + // SAFETY: length checked above to be >= 8. + IGMPV3_TYPE_MEMBERSHIP_REPORT => IgmpSlice::MembershipReportV3(unsafe { + MembershipReportV3Slice::from_slice_unchecked(slice) + }), + // SAFETY: length checked above to be >= 8. + IGMPV2_TYPE_LEAVE_GROUP => { + IgmpSlice::LeaveGroup(unsafe { LeaveGroupSlice::from_slice_unchecked(slice) }) + } + // SAFETY: length checked above to be >= 8. + _ => IgmpSlice::Unknown(unsafe { IgmpUnknownSlice::from_slice_unchecked(slice) }), + }) } /// Decode the header values into an [`IgmpHeader`] struct. #[inline] pub fn header(&self) -> IgmpHeader { - // SAFETY: from_slice already validated the slice, so this cannot fail. - let (header, _) = IgmpHeader::from_slice(self.slice).unwrap(); + // `from_slice` already validated the slice, so this cannot fail. + let (header, _) = IgmpHeader::from_slice(self.slice()).unwrap(); header } @@ -52,13 +146,8 @@ impl<'a> IgmpSlice<'a> { /// [`IgmpHeader`] when [`IgmpSlice::header`] gets called. #[inline] pub fn header_len(&self) -> usize { - // SAFETY: Safe as from_slice checks that the slice has at least - // IgmpHeader::MIN_LEN (8) bytes. - let type_u8 = unsafe { *self.slice.get_unchecked(0) }; - match type_u8 { - IGMP_TYPE_MEMBERSHIP_QUERY if self.slice.len() >= MembershipQueryWithSourcesHeader::LEN => { - MembershipQueryWithSourcesHeader::LEN - } + match self { + IgmpSlice::MembershipQueryWithSources(_) => MembershipQueryWithSourcesHeader::LEN, _ => IgmpHeader::MIN_LEN, } } @@ -72,9 +161,8 @@ impl<'a> IgmpSlice<'a> { /// Returns the "type" byte value in the IGMP header. #[inline] pub fn type_u8(&self) -> u8 { - // SAFETY: Safe as from_slice checks that the slice has at least - // IgmpHeader::MIN_LEN (8) bytes. - unsafe { *self.slice.get_unchecked(0) } + // SAFETY: from_slice guarantees at least 8 bytes. + unsafe { *self.slice().get_unchecked(0) } } /// Returns the second byte of the IGMP header. @@ -85,17 +173,15 @@ impl<'a> IgmpSlice<'a> { /// - All other types: unused/reserved #[inline] pub fn max_resp_code_or_reserved(&self) -> u8 { - // SAFETY: Safe as from_slice checks that the slice has at least - // IgmpHeader::MIN_LEN (8) bytes. - unsafe { *self.slice.get_unchecked(1) } + // SAFETY: from_slice guarantees at least 8 bytes. + unsafe { *self.slice().get_unchecked(1) } } /// Returns the "checksum" value in the IGMP header. #[inline] pub fn checksum(&self) -> u16 { - // SAFETY: Safe as from_slice checks that the slice has at least - // IgmpHeader::MIN_LEN (8) bytes. - unsafe { get_unchecked_be_u16(self.slice.as_ptr().add(2)) } + // SAFETY: from_slice guarantees at least 8 bytes. + unsafe { get_unchecked_be_u16(self.slice().as_ptr().add(2)) } } /// Returns the bytes from position 4 through 7 in the IGMP header. @@ -105,14 +191,14 @@ impl<'a> IgmpSlice<'a> { /// Number of Group Records. #[inline] pub fn bytes4to7(&self) -> [u8; 4] { - // SAFETY: Safe as from_slice checks that the slice has at least - // IgmpHeader::MIN_LEN (8) bytes. + // SAFETY: from_slice guarantees at least 8 bytes. + let slice = self.slice(); unsafe { [ - *self.slice.get_unchecked(4), - *self.slice.get_unchecked(5), - *self.slice.get_unchecked(6), - *self.slice.get_unchecked(7), + *slice.get_unchecked(4), + *slice.get_unchecked(5), + *slice.get_unchecked(6), + *slice.get_unchecked(7), ] } } @@ -123,30 +209,65 @@ impl<'a> IgmpSlice<'a> { /// /// | Message Type | Payload Content | /// |---|---| - /// | [`IgmpType::MembershipQuery`] (v1/v2) | Nothing (empty) | - /// | [`IgmpType::MembershipQueryWithSources`] (v3) | Source Address list | - /// | [`IgmpType::MembershipReportV1`] | Nothing (empty, unless trailing data) | - /// | [`IgmpType::MembershipReportV2`] | Nothing (empty, unless trailing data) | - /// | [`IgmpType::MembershipReportV3`] | Group Records | - /// | [`IgmpType::LeaveGroup`] | Nothing (empty, unless trailing data) | - /// | [`IgmpType::Unknown`] | Everything after the 8th byte | + /// | [`IgmpSlice::MembershipQuery`] (v1/v2) | Nothing (empty) | + /// | [`IgmpSlice::MembershipQueryWithSources`] (v3) | Source Address list | + /// | [`IgmpSlice::MembershipReportV1`] | Nothing (empty, unless trailing data) | + /// | [`IgmpSlice::MembershipReportV2`] | Nothing (empty, unless trailing data) | + /// | [`IgmpSlice::MembershipReportV3`] | Group Records | + /// | [`IgmpSlice::LeaveGroup`] | Nothing (empty, unless trailing data) | + /// | [`IgmpSlice::Unknown`] | Everything after the 8th byte | #[inline] pub fn payload(&self) -> &'a [u8] { - let header_len = self.header_len(); - // SAFETY: Safe as from_slice validated that the slice length is - // at least header_len. - unsafe { - core::slice::from_raw_parts( - self.slice.as_ptr().add(header_len), - self.slice.len() - header_len, - ) + match self { + IgmpSlice::MembershipQuery(s) => { + // v1/v2 queries are always exactly 8 bytes long. + let slice = s.slice(); + // SAFETY: from_slice guarantees at least 8 bytes. + unsafe { + core::slice::from_raw_parts( + slice.as_ptr().add(MembershipQueryType::LEN), + slice.len() - MembershipQueryType::LEN, + ) + } + } + IgmpSlice::MembershipQueryWithSources(s) => s.payload(), + IgmpSlice::MembershipReportV1(s) => s.payload(), + IgmpSlice::MembershipReportV2(s) => s.payload(), + IgmpSlice::MembershipReportV3(s) => s.payload(), + IgmpSlice::LeaveGroup(s) => s.payload(), + IgmpSlice::Unknown(s) => s.payload(), } } /// Returns the slice containing the entire IGMP packet. #[inline] pub fn slice(&self) -> &'a [u8] { - self.slice + match self { + IgmpSlice::MembershipQuery(s) => s.slice(), + IgmpSlice::MembershipQueryWithSources(s) => s.slice(), + IgmpSlice::MembershipReportV1(s) => s.slice(), + IgmpSlice::MembershipReportV2(s) => s.slice(), + IgmpSlice::MembershipReportV3(s) => s.slice(), + IgmpSlice::LeaveGroup(s) => s.slice(), + IgmpSlice::Unknown(s) => s.slice(), + } + } + + /// Verifies the checksum of the IGMP message. + /// + /// Unlike ICMPv6 (and TCP/UDP), IGMP does not use an IP pseudo + /// header. Per RFC 1112, RFC 2236 and RFC 9776 the checksum is the + /// 16-bit one's complement of the one's complement sum of the whole + /// IGMP message (header + payload). This is why no IP addresses are + /// required here (in contrast to [`crate::Icmpv6Slice::is_checksum_valid`]). + /// + /// Returns `true` if the checksum stored in the message matches the + /// one calculated over the entire slice. + pub fn is_checksum_valid(&self) -> bool { + checksum::Sum16BitWords::new() + .add_slice(self.slice()) + .ones_complement() + == 0 } } @@ -301,12 +422,12 @@ mod test { let mut bytes = vec![0u8; 16]; bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; bytes[1] = 0; // reserved - // bytes[2..4] = checksum (0) + // bytes[2..4] = checksum (0) bytes[4] = 0; // flags[0] bytes[5] = 0; // flags[1] bytes[6] = 0; // num_of_records high bytes[7] = 1; // num_of_records low = 1 - // group record (8 bytes) + // group record (8 bytes) bytes[8] = 1; // record type (MODE_IS_INCLUDE) bytes[9] = 0; // aux data len bytes[10] = 0; // num sources high @@ -440,7 +561,8 @@ mod test { #[test] fn slice_accessor(bytes in proptest::collection::vec(any::(), 8..64)) { let mut bytes = bytes; - // Avoid query type to prevent 9-11 byte rejection + // Avoid query type to prevent 9-11 byte rejection & source + // address length validation. if bytes[0] == IGMP_TYPE_MEMBERSHIP_QUERY { bytes[0] = 0xFF; } @@ -455,6 +577,9 @@ mod test { // Use v3 query type so 12 bytes is valid let mut bytes = bytes; bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + // Zero the source count so the 12 byte slice (no sources) is valid. + bytes[10] = 0; + bytes[11] = 0; let slice = IgmpSlice::from_slice(&bytes).unwrap(); assert_eq!(slice, slice.clone()); } @@ -469,7 +594,79 @@ mod test { } let slice = IgmpSlice::from_slice(&bytes).unwrap(); let dbg = format!("{:?}", slice); - assert!(dbg.starts_with("IgmpSlice")); + assert!(dbg.contains("Slice")); + } + } + + proptest! { + /// Round-trip an IGMP header + arbitrary payload through the + /// slice and confirm the decoded header & payload match. + #[test] + fn from_slice_roundtrip( + group in any::<[u8; 4]>(), + checksum in any::(), + payload in proptest::collection::vec(any::(), 0..64), + ) { + let header = IgmpHeader { + igmp_type: IgmpType::MembershipReportV2(MembershipReportV2Type { + group_address: group.into(), + }), + checksum, + }; + let mut bytes = header.to_bytes().to_vec(); + bytes.extend_from_slice(&payload); + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert_eq!(slice.header(), header); + assert_eq!(slice.header_len(), IgmpHeader::MIN_LEN); + assert_eq!(slice.payload(), &payload[..]); + assert_eq!(slice.slice(), &bytes[..]); + } + } + + proptest! { + /// A checksum written by `IgmpHeader::with_checksum` must be + /// accepted by `IgmpSlice::is_checksum_valid`, and any single + /// bit flip must invalidate it. + #[test] + fn is_checksum_valid_proptest( + group in any::<[u8; 4]>(), + payload in proptest::collection::vec(any::(), 0..64), + corrupt_idx in any::(), + ) { + let header = IgmpHeader::with_checksum( + IgmpType::MembershipReportV2(MembershipReportV2Type { + group_address: group.into(), + }), + &payload, + ); + let mut bytes = header.to_bytes().to_vec(); + bytes.extend_from_slice(&payload); + + assert!(IgmpSlice::from_slice(&bytes).unwrap().is_checksum_valid()); + + // corrupt a single byte -> checksum must fail + let idx = usize::from(corrupt_idx) % bytes.len(); + bytes[idx] = bytes[idx].wrapping_add(1); + assert!(!IgmpSlice::from_slice(&bytes).unwrap().is_checksum_valid()); + } + } + + proptest! { + /// Reject slices shorter than the minimum IGMP header length. + #[test] + fn from_slice_too_short(len in 0usize..8) { + let bytes = [0u8; 8]; + assert_eq!( + IgmpSlice::from_slice(&bytes[..len]).unwrap_err(), + err::LenError { + required_len: IgmpHeader::MIN_LEN, + len, + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + } + ); } } @@ -481,7 +678,7 @@ mod test { bytes[1] = 10; // max_resp_code bytes[10] = 0; bytes[11] = 2; // 2 sources - // source 1: 10.0.0.1 + // source 1: 10.0.0.1 bytes[12] = 10; bytes[15] = 1; // source 2: 10.0.0.2 @@ -497,4 +694,301 @@ mod test { assert_eq!(payload[4], 10); // first byte of source 2 assert_eq!(payload[7], 2); } + + #[test] + fn group_records_v3_report() { + // type 0x22, 8-byte header + 2 group records + let mut bytes = vec![0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + bytes[7] = 2; // num_of_records = 2 + // group record 1 (0 sources, 0 aux) + bytes.extend_from_slice(&[1, 0, 0, 0, 224, 0, 0, 1]); + // group record 2 (1 source, 0 aux) + bytes.extend_from_slice(&[2, 0, 0, 1, 224, 0, 0, 2, 10, 0, 0, 5]); + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + let report = match slice { + IgmpSlice::MembershipReportV3(r) => r, + other => panic!("expected MembershipReportV3, got {:?}", other), + }; + let records: alloc::vec::Vec<_> = report.group_records().collect::>().unwrap(); + assert_eq!(records.len(), 2); + assert_eq!(records[0].multicast_address(), [224, 0, 0, 1]); + assert_eq!(records[0].num_of_sources(), 0); + assert_eq!(records[1].multicast_address(), [224, 0, 0, 2]); + assert_eq!(records[1].source_addrs_bytes(), &[10, 0, 0, 5]); + } + + #[test] + fn group_records_only_for_v3_report() { + // a v2 report is not decoded as the v3 report variant + let mut bytes = [0u8; 8]; + bytes[0] = IGMPV2_TYPE_MEMBERSHIP_REPORT; + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(matches!(slice, IgmpSlice::MembershipReportV2(_))); + } + + #[test] + fn query_source_addrs_bytes_v3() { + // 12-byte header + 2 source addresses + let mut bytes = [0u8; 20]; + bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + bytes[11] = 2; // 2 sources + bytes[12] = 10; + bytes[15] = 1; + bytes[16] = 10; + bytes[19] = 2; + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + let query = match slice { + IgmpSlice::MembershipQueryWithSources(q) => q, + other => panic!("expected MembershipQueryWithSources, got {:?}", other), + }; + assert_eq!(query.source_addrs_bytes(), &[10, 0, 0, 1, 10, 0, 0, 2][..]); + } + + #[test] + fn from_slice_v3_query_too_short_sources() { + // Declares 5 sources but only carries room for 1 -> from_slice + // must reject the slice as too short. + let mut bytes = [0u8; 16]; + bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + bytes[11] = 5; // claims 5 sources (needs 12 + 5*4 = 32 bytes) + bytes[12] = 10; + bytes[15] = 1; + + assert_eq!( + IgmpSlice::from_slice(&bytes).unwrap_err(), + err::LenError { + required_len: MembershipQueryWithSourcesHeader::LEN + 5 * 4, + len: 16, + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + } + ); + } + + #[test] + fn from_slice_v3_query_exact_sources() { + // Exactly enough room for the declared sources must be accepted. + let mut bytes = [0u8; 20]; + bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + bytes[11] = 2; // 2 sources (needs 12 + 2*4 = 20 bytes) + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(matches!(slice, IgmpSlice::MembershipQueryWithSources(_))); + + // One byte short must be rejected. + assert_eq!( + IgmpSlice::from_slice(&bytes[..19]).unwrap_err(), + err::LenError { + required_len: MembershipQueryWithSourcesHeader::LEN + 2 * 4, + len: 19, + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + } + ); + } + + #[test] + fn from_slice_v3_report_records_not_validated() { + // Declares 2 records but only provides 1 -> from_slice still + // succeeds; the group records are validated lazily by the iterator. + let mut bytes = alloc::vec![0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + bytes[7] = 2; // num_of_records = 2 + // record 1: type 1, 0 aux, 1 source (8 + 4 = 12 bytes) + bytes.extend_from_slice(&[1, 0, 0, 1, 224, 0, 0, 1, 10, 0, 0, 5]); + // record 2 is missing entirely + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(matches!(slice, IgmpSlice::MembershipReportV3(_))); + } + + #[test] + fn group_records_iter_stops_on_missing_record() { + // Declares 2 records but only provides 1 -> from_slice succeeds, + // the iterator yields the first record then a length error. + let mut bytes = alloc::vec![0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + bytes[7] = 2; // num_of_records = 2 + // record 1: type 1, 0 aux, 1 source (8 + 4 = 12 bytes) + bytes.extend_from_slice(&[1, 0, 0, 1, 224, 0, 0, 1, 10, 0, 0, 5]); + // record 2 is missing entirely + + let report = match IgmpSlice::from_slice(&bytes).unwrap() { + IgmpSlice::MembershipReportV3(r) => r, + other => panic!("expected MembershipReportV3, got {:?}", other), + }; + let mut iter = report.group_records(); + // first record parses fine + assert!(iter.next().unwrap().is_ok()); + // second record is missing entirely -> error + assert_eq!( + iter.next().unwrap().unwrap_err(), + err::LenError { + required_len: ReportGroupRecordV3Header::LEN, + len: 0, + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + } + ); + // iteration stops after the error + assert!(iter.next().is_none()); + } + + #[test] + fn group_records_iter_stops_on_short_record() { + // A single record that declares more sources than it carries. + let mut bytes = alloc::vec![0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + bytes[7] = 1; // 1 record + // record: declares 2 sources but provides only 1 (needs 8 + 2*4 = 16). + bytes.extend_from_slice(&[1, 0, 0, 2, 224, 0, 0, 1, 10, 0, 0, 5]); + + let report = match IgmpSlice::from_slice(&bytes).unwrap() { + IgmpSlice::MembershipReportV3(r) => r, + other => panic!("expected MembershipReportV3, got {:?}", other), + }; + let mut iter = report.group_records(); + assert_eq!( + iter.next().unwrap().unwrap_err(), + err::LenError { + required_len: ReportGroupRecordV3Header::LEN + 2 * 4, + len: 12, // bytes available for the record + len_source: LenSource::Slice, + layer: err::Layer::Igmp, + layer_start_offset: 0, + } + ); + assert!(iter.next().is_none()); + } + + #[test] + fn group_records_iter_all_records() { + // Two well formed records are both yielded successfully. + let mut bytes = alloc::vec![0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + bytes[7] = 2; + bytes.extend_from_slice(&[1, 0, 0, 0, 224, 0, 0, 1]); // record 1 (no sources) + bytes.extend_from_slice(&[2, 0, 0, 1, 224, 0, 0, 2, 10, 0, 0, 5]); // record 2 (1 source) + + let report = match IgmpSlice::from_slice(&bytes).unwrap() { + IgmpSlice::MembershipReportV3(r) => r, + other => panic!("expected MembershipReportV3, got {:?}", other), + }; + let records: alloc::vec::Vec<_> = report.group_records().collect::>().unwrap(); + assert_eq!(records.len(), 2); + assert_eq!(records[0].multicast_address(), [224, 0, 0, 1]); + assert_eq!(records[1].multicast_address(), [224, 0, 0, 2]); + } + + #[test] + fn query_source_addrs_bytes_only_for_v3_query() { + // v1/v2 query (8 bytes) has no source list -> not the v3 variant + let mut bytes = [0u8; 8]; + bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(matches!(slice, IgmpSlice::MembershipQuery(_))); + + // non-query type + let mut bytes = [0u8; 8]; + bytes[0] = IGMPV3_TYPE_MEMBERSHIP_REPORT; + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(matches!(slice, IgmpSlice::MembershipReportV3(_))); + } + + #[test] + fn query_source_addresses_iter() { + let mut bytes = [0u8; 20]; + bytes[0] = IGMP_TYPE_MEMBERSHIP_QUERY; + bytes[11] = 2; + bytes[12] = 10; + bytes[15] = 1; + bytes[16] = 10; + bytes[19] = 2; + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + let query = match slice { + IgmpSlice::MembershipQueryWithSources(q) => q, + other => panic!("expected MembershipQueryWithSources, got {:?}", other), + }; + let addrs: alloc::vec::Vec<_> = query.source_addresses().collect(); + assert_eq!(addrs, alloc::vec![[10, 0, 0, 1], [10, 0, 0, 2]]); + } + + #[test] + fn is_checksum_valid() { + // Build a valid IGMPv2 membership report using the header's + // checksum calculation and confirm the slice validates it. + let header = IgmpHeader::with_checksum( + IgmpType::MembershipReportV2(MembershipReportV2Type { + group_address: [224, 0, 0, 1].into(), + }), + &[], + ); + let bytes = header.to_bytes(); + let slice = IgmpSlice::from_slice(bytes.as_slice()).unwrap(); + assert!(slice.is_checksum_valid()); + + // Corrupt a byte -> checksum must fail. + let mut corrupted = bytes.to_vec(); + corrupted[4] ^= 0xFF; + let slice = IgmpSlice::from_slice(&corrupted).unwrap(); + assert!(!slice.is_checksum_valid()); + } + + #[test] + fn is_checksum_valid_v3_report_with_payload() { + let group_record = [1u8, 0, 0, 0, 224, 0, 0, 1]; + let header = IgmpHeader::with_checksum( + IgmpType::MembershipReportV3(MembershipReportV3Header { + flags: [0, 0], + num_of_records: 1, + }), + &group_record, + ); + let mut bytes = header.to_bytes().to_vec(); + bytes.extend_from_slice(&group_record); + + let slice = IgmpSlice::from_slice(&bytes).unwrap(); + assert!(slice.is_checksum_valid()); + } + + #[test] + fn packet_builder_roundtrip() { + use crate::*; + + let builder = PacketBuilder::ethernet2([1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]) + .ipv4([192, 168, 1, 1], [192, 168, 1, 2], 20) + .igmp(IgmpType::MembershipReportV2(MembershipReportV2Type { + group_address: [224, 0, 0, 1].into(), + })); + + let mut buffer = alloc::vec::Vec::with_capacity(builder.size(0)); + builder.write(&mut buffer, &[]).unwrap(); + + // parse back via SlicedPacket + let sliced = SlicedPacket::from_ethernet(&buffer).unwrap(); + let igmp = match sliced.transport.unwrap() { + TransportSlice::Igmp(igmp) => igmp, + other => panic!("unexpected transport: {:?}", other), + }; + assert_eq!(igmp.type_u8(), IGMPV2_TYPE_MEMBERSHIP_REPORT); + // checksum must have been calculated by the builder + assert!(igmp.is_checksum_valid()); + assert_eq!( + igmp.header().igmp_type, + IgmpType::MembershipReportV2(MembershipReportV2Type { + group_address: [224, 0, 0, 1].into(), + }) + ); + + // parse back via PacketHeaders as well + let headers = PacketHeaders::from_ethernet_slice(&buffer).unwrap(); + assert!(matches!(headers.transport, Some(TransportHeader::Igmp(_)))); + } } diff --git a/etherparse/src/transport/transport_header.rs b/etherparse/src/transport/transport_header.rs index 2fca8c7b..8ecd731c 100644 --- a/etherparse/src/transport/transport_header.rs +++ b/etherparse/src/transport/transport_header.rs @@ -10,6 +10,7 @@ pub enum TransportHeader { Tcp(TcpHeader), Icmpv4(Icmpv4Header), Icmpv6(Icmpv6Header), + Igmp(IgmpHeader), } impl TransportHeader { @@ -101,6 +102,28 @@ impl TransportHeader { } } + /// Returns Result::Some containing the IGMP header if self has the value Igmp. + /// Otherwise None is returned. + pub fn igmp(self) -> Option { + use crate::TransportHeader::*; + if let Igmp(value) = self { + Some(value) + } else { + None + } + } + + /// Returns Result::Some containing a mutable reference to the IGMP header if self has the value Igmp. + /// Otherwise None is returned. + pub fn mut_igmp(&mut self) -> Option<&mut IgmpHeader> { + use crate::TransportHeader::*; + if let Igmp(value) = self { + Some(value) + } else { + None + } + } + /// Returns the size of the transport header (in case of UDP fixed, /// in case of TCP cotanining the options). pub fn header_len(&self) -> usize { @@ -110,6 +133,7 @@ impl TransportHeader { Tcp(value) => value.header_len(), Icmpv4(value) => value.header_len(), Icmpv6(value) => value.header_len(), + Igmp(value) => value.header_len(), } } @@ -135,6 +159,9 @@ impl TransportHeader { Icmpv4(header) => { header.update_checksum(payload); } + Igmp(header) => { + header.checksum = header.calc_checksum(payload); + } Icmpv6(_) => return Err(Icmpv6InIpv4), } Ok(()) @@ -153,6 +180,9 @@ impl TransportHeader { Icmpv6(header) => { header.update_checksum(ip_header.source, ip_header.destination, payload)? } + Igmp(header) => { + header.checksum = header.calc_checksum(payload); + } Udp(header) => { header.checksum = header.calc_checksum_ipv6(ip_header, payload)?; } @@ -171,6 +201,7 @@ impl TransportHeader { match self { Icmpv4(value) => value.write(writer), Icmpv6(value) => value.write(writer), + Igmp(value) => value.write(writer), Udp(value) => value.write(writer), Tcp(value) => value.write(writer), } diff --git a/etherparse/src/transport/transport_slice.rs b/etherparse/src/transport/transport_slice.rs index 8cfab0bb..4f0acd64 100644 --- a/etherparse/src/transport/transport_slice.rs +++ b/etherparse/src/transport/transport_slice.rs @@ -1,6 +1,6 @@ use crate::*; -/// Slice containing UDP, TCP, ICMP or ICMPv4 header & payload. +/// Slice containing UDP, TCP, ICMPv4, ICMPv6 or IGMP header & payload. #[derive(Clone, Debug, Eq, PartialEq)] pub enum TransportSlice<'a> { /// A slice containing an Icmp4 header & payload. @@ -9,6 +9,9 @@ pub enum TransportSlice<'a> { /// A slice containing an Icmp6 header & payload. Icmpv6(Icmpv6Slice<'a>), + /// A slice containing an IGMP header & payload. + Igmp(IgmpSlice<'a>), + /// A slice containing an UDP header & payload. Udp(UdpSlice<'a>), diff --git a/etherparse/tests/transport/icmpv4.rs b/etherparse/tests/transport/icmpv4.rs index a6c42b23..4fb11bc5 100644 --- a/etherparse/tests/transport/icmpv4.rs +++ b/etherparse/tests/transport/icmpv4.rs @@ -107,7 +107,7 @@ mod icmpv4_regression { use TransportSlice::*; let icmp4 = match echo.transport.unwrap() { Icmpv4(icmp4) => icmp4, - Icmpv6(_) | Udp(_) | Tcp(_) => panic!("Misparsed header!"), + Icmpv6(_) | Igmp(_) | Udp(_) | Tcp(_) => panic!("Misparsed header!"), }; assert!(matches!(icmp4.icmp_type(), Icmpv4Type::EchoRequest(_))); } diff --git a/etherparse/tests/transport/icmpv6.rs b/etherparse/tests/transport/icmpv6.rs index 76d45024..3a771929 100644 --- a/etherparse/tests/transport/icmpv6.rs +++ b/etherparse/tests/transport/icmpv6.rs @@ -101,7 +101,7 @@ mod regression { use TransportSlice::*; let icmp6 = match echo.transport.unwrap() { Icmpv6(icmp6) => icmp6, - Icmpv4(_) | Udp(_) | Tcp(_) => panic!("Misparsed header!"), + Icmpv4(_) | Igmp(_) | Udp(_) | Tcp(_) => panic!("Misparsed header!"), }; assert!(matches!( icmp6.header().icmp_type,