1use super::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
4pub(in crate::node) struct FmpPlaintextTrafficClass {
5 pub(in crate::node) bulk_endpoint_data: bool,
6 pub(in crate::node) drop_on_backpressure: bool,
7}
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11pub enum EndpointPayloadLane {
12 #[default]
13 Priority,
14 Bulk,
15}
16
17impl EndpointPayloadLane {
18 fn command_lane(self) -> EndpointCommandLane {
19 match self {
20 Self::Priority => EndpointCommandLane::Priority,
21 Self::Bulk => EndpointCommandLane::Bulk,
22 }
23 }
24}
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
28pub struct EndpointPayloadClass {
29 lane: EndpointPayloadLane,
30 drop_on_backpressure: bool,
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub(in crate::node) struct EndpointFlowDispatchKey(u64);
35
36impl EndpointFlowDispatchKey {
37 pub(in crate::node) fn get(self) -> u64 {
38 self.0
39 }
40}
41
42impl EndpointPayloadClass {
43 pub fn lane(self) -> EndpointPayloadLane {
44 self.lane
45 }
46
47 pub fn is_latency_sensitive(self) -> bool {
48 self.lane == EndpointPayloadLane::Priority
49 }
50
51 pub fn drop_on_backpressure(self) -> bool {
52 self.drop_on_backpressure
53 }
54}
55
56#[cfg(unix)]
57pub(in crate::node) struct FmpWorkerSendReservation {
58 pub(in crate::node) counter: u64,
59 pub(in crate::node) header: [u8; ESTABLISHED_HEADER_SIZE],
60 pub(in crate::node) cipher: ring::aead::LessSafeKey,
61}
62
63#[cfg(unix)]
64pub(in crate::node) fn reserve_fmp_worker_send(
65 session: &mut crate::noise::NoiseSession,
66 their_index: crate::utils::index::SessionIndex,
67 flags: u8,
68 payload_len: u16,
69) -> Result<Option<FmpWorkerSendReservation>, crate::noise::NoiseError> {
70 let Some(cipher) = session.send_cipher_clone() else {
71 return Ok(None);
72 };
73 let counter = session.take_send_counter()?;
74 let header = build_established_header(their_index, counter, flags, payload_len);
75 Ok(Some(FmpWorkerSendReservation {
76 counter,
77 header,
78 cipher,
79 }))
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub(crate) enum EndpointCommandLane {
84 Priority,
85 Bulk,
86}
87
88pub(in crate::node) fn classify_fmp_plaintext_traffic(
89 plaintext: &[u8],
90) -> FmpPlaintextTrafficClass {
91 let bulk_endpoint_data = fmp_plaintext_is_bulk_session_datagram(plaintext);
92 FmpPlaintextTrafficClass {
97 bulk_endpoint_data,
98 drop_on_backpressure: false,
99 }
100}
101
102pub(in crate::node) fn fmp_plaintext_is_bulk_session_datagram(plaintext: &[u8]) -> bool {
103 if plaintext
104 .first()
105 .is_none_or(|ty| *ty != LinkMessageType::SessionDatagram.to_byte())
106 {
107 return false;
108 }
109 let Some(fsp_payload) = plaintext.get(crate::protocol::SESSION_DATAGRAM_HEADER_SIZE..) else {
110 return false;
111 };
112 FspCommonPrefix::parse(fsp_payload).is_some_and(|prefix| {
113 prefix.phase == FSP_PHASE_ESTABLISHED && !prefix.is_unencrypted() && !prefix.has_coords()
114 })
115}
116
117pub(in crate::node) fn endpoint_flow_dispatch_key(
118 payload: &[u8],
119) -> Option<EndpointFlowDispatchKey> {
120 endpoint_payload_flow_parts(payload).map(|parts| EndpointFlowDispatchKey(parts.hash()))
121}
122
123pub fn classify_endpoint_payload(payload: &[u8]) -> EndpointPayloadClass {
125 const IPPROTO_ICMP: u8 = 1;
126 const IPPROTO_TCP: u8 = 6;
127 const IPPROTO_ICMPV6: u8 = 58;
128
129 match parse_endpoint_payload_ip_proto(payload) {
130 Some((IPPROTO_ICMP, _)) => EndpointPayloadClass::default(),
131 Some((IPPROTO_ICMPV6, _)) => EndpointPayloadClass::default(),
132 Some((IPPROTO_TCP, offset)) => {
133 let latency_sensitive = endpoint_tcp_payload_is_latency_sensitive(payload, offset);
134 EndpointPayloadClass {
135 lane: if latency_sensitive {
136 EndpointPayloadLane::Priority
137 } else {
138 EndpointPayloadLane::Bulk
139 },
140 drop_on_backpressure: false,
141 }
142 }
143 _ => EndpointPayloadClass {
144 lane: EndpointPayloadLane::Bulk,
145 drop_on_backpressure: true,
146 },
147 }
148}
149
150pub fn endpoint_payload_is_latency_sensitive(payload: &[u8]) -> bool {
156 classify_endpoint_payload(payload).is_latency_sensitive()
157}
158
159#[cfg(test)]
160pub(crate) fn endpoint_command_lane_for_payload(payload: &[u8]) -> EndpointCommandLane {
161 if endpoint_payload_is_latency_sensitive(payload) {
162 EndpointCommandLane::Priority
163 } else {
164 EndpointCommandLane::Bulk
165 }
166}
167
168#[derive(Clone, Debug, PartialEq, Eq)]
170pub(crate) struct EndpointDataPayload {
171 bytes: Vec<u8>,
172 traffic_class: EndpointPayloadClass,
173}
174
175impl EndpointDataPayload {
176 pub(crate) fn new(bytes: Vec<u8>) -> Self {
177 let traffic_class = classify_endpoint_payload(&bytes);
178 Self {
179 bytes,
180 traffic_class,
181 }
182 }
183
184 pub(crate) fn from_classified(bytes: Vec<u8>, traffic_class: EndpointPayloadClass) -> Self {
185 Self {
186 bytes,
187 traffic_class,
188 }
189 }
190
191 pub(crate) fn lane(&self) -> EndpointCommandLane {
192 self.traffic_class.lane().command_lane()
193 }
194
195 pub(crate) fn bulk_endpoint_data(&self) -> bool {
196 self.traffic_class.lane() == EndpointPayloadLane::Bulk
197 }
198
199 pub(crate) fn drop_on_backpressure(&self) -> bool {
200 self.traffic_class.drop_on_backpressure()
201 }
202
203 pub(crate) fn as_slice(&self) -> &[u8] {
204 &self.bytes
205 }
206
207 pub(crate) fn len(&self) -> usize {
208 self.bytes.len()
209 }
210}
211
212impl From<Vec<u8>> for EndpointDataPayload {
213 fn from(bytes: Vec<u8>) -> Self {
214 Self::new(bytes)
215 }
216}
217
218#[derive(Debug)]
220pub(crate) struct EndpointDataSend {
221 dest_addr: NodeAddr,
222 dest_pubkey: secp256k1::PublicKey,
223 payload: EndpointDataPayload,
224}
225
226impl EndpointDataSend {
227 pub(crate) fn new(remote: PeerIdentity, payload: EndpointDataPayload) -> Self {
228 Self {
229 dest_addr: *remote.node_addr(),
230 dest_pubkey: remote.pubkey_full(),
231 payload,
232 }
233 }
234
235 pub(crate) fn dest_addr(&self) -> NodeAddr {
236 self.dest_addr
237 }
238
239 pub(crate) fn dest_pubkey(&self) -> secp256k1::PublicKey {
240 self.dest_pubkey
241 }
242
243 pub(crate) fn payload(&self) -> &EndpointDataPayload {
244 &self.payload
245 }
246
247 pub(crate) fn into_payload(self) -> EndpointDataPayload {
248 self.payload
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq, Eq)]
254pub(crate) struct PendingEndpointDataQueueAdmission {
255 dropped_oldest: bool,
256}
257
258impl PendingEndpointDataQueueAdmission {
259 pub(crate) fn dropped_oldest(&self) -> bool {
260 self.dropped_oldest
261 }
262}
263
264#[derive(Debug, Default)]
266pub(crate) struct PendingEndpointDataQueue {
267 payloads: VecDeque<EndpointDataPayload>,
268}
269
270impl PendingEndpointDataQueue {
271 pub(crate) fn push_bounded(
272 &mut self,
273 payload: EndpointDataPayload,
274 capacity: usize,
275 ) -> PendingEndpointDataQueueAdmission {
276 let dropped_oldest = self.payloads.len() >= capacity;
277 if dropped_oldest {
278 self.payloads.pop_front();
279 }
280 self.payloads.push_back(payload);
281 PendingEndpointDataQueueAdmission { dropped_oldest }
282 }
283
284 pub(crate) fn len(&self) -> usize {
285 self.payloads.len()
286 }
287
288 pub(crate) fn into_payloads(self) -> VecDeque<EndpointDataPayload> {
289 self.payloads
290 }
291
292 #[cfg(test)]
293 pub(crate) fn iter(&self) -> impl Iterator<Item = &EndpointDataPayload> {
294 self.payloads.iter()
295 }
296}
297
298#[derive(Clone, Copy, Debug, PartialEq, Eq)]
300pub(crate) struct PendingTunPacketQueueAdmission {
301 dropped_oldest: bool,
302}
303
304impl PendingTunPacketQueueAdmission {
305 pub(crate) fn dropped_oldest(&self) -> bool {
306 self.dropped_oldest
307 }
308}
309
310#[derive(Debug)]
312pub(crate) struct PendingTunPacket {
313 packet: Vec<u8>,
314 queued_at_ms: u64,
315}
316
317impl PendingTunPacket {
318 fn new(packet: Vec<u8>, queued_at_ms: u64) -> Self {
319 Self {
320 packet,
321 queued_at_ms,
322 }
323 }
324
325 fn is_stale(&self, now_ms: u64, max_age_ms: u64) -> bool {
326 now_ms.saturating_sub(self.queued_at_ms) > max_age_ms
327 }
328}
329
330#[derive(Debug, Default)]
332pub(crate) struct PendingTunPacketQueue {
333 packets: VecDeque<PendingTunPacket>,
334}
335
336impl PendingTunPacketQueue {
337 pub(crate) fn push_bounded(
338 &mut self,
339 packet: Vec<u8>,
340 queued_at_ms: u64,
341 capacity: usize,
342 ) -> PendingTunPacketQueueAdmission {
343 let dropped_oldest = self.packets.len() >= capacity;
344 if dropped_oldest {
345 self.packets.pop_front();
346 }
347 self.packets
348 .push_back(PendingTunPacket::new(packet, queued_at_ms));
349 PendingTunPacketQueueAdmission { dropped_oldest }
350 }
351
352 pub(crate) fn len(&self) -> usize {
353 self.packets.len()
354 }
355
356 pub(crate) fn into_packets(self) -> VecDeque<Vec<u8>> {
357 self.packets
358 .into_iter()
359 .map(|packet| packet.packet)
360 .collect()
361 }
362
363 pub(crate) fn into_fresh_packets(
364 self,
365 now_ms: u64,
366 max_age_ms: u64,
367 ) -> (VecDeque<Vec<u8>>, usize) {
368 let mut fresh = VecDeque::with_capacity(self.packets.len());
369 let mut stale = 0usize;
370 for packet in self.packets {
371 if packet.is_stale(now_ms, max_age_ms) {
372 stale = stale.saturating_add(1);
373 } else {
374 fresh.push_back(packet.packet);
375 }
376 }
377 (fresh, stale)
378 }
379
380 #[cfg(test)]
381 pub(crate) fn iter(&self) -> impl Iterator<Item = &Vec<u8>> {
382 self.packets.iter().map(|packet| &packet.packet)
383 }
384}
385
386#[derive(Clone, Copy, Debug, PartialEq, Eq)]
388pub(crate) struct PendingSessionTrafficAdmission {
389 destination_dropped: bool,
390 dropped_oldest: bool,
391}
392
393impl PendingSessionTrafficAdmission {
394 pub(crate) fn destination_dropped(&self) -> bool {
395 self.destination_dropped
396 }
397
398 pub(crate) fn dropped_oldest(&self) -> bool {
399 self.dropped_oldest
400 }
401}
402
403#[derive(Debug, Default)]
405pub(crate) struct PendingDestinationTraffic {
406 tun_packets: Option<PendingTunPacketQueue>,
407 endpoint_data: Option<PendingEndpointDataQueue>,
408}
409
410impl PendingDestinationTraffic {
411 pub(crate) fn tun_packets(&self) -> Option<&PendingTunPacketQueue> {
412 self.tun_packets.as_ref()
413 }
414
415 pub(crate) fn into_tun_packets(self) -> Option<PendingTunPacketQueue> {
416 self.tun_packets
417 }
418
419 pub(crate) fn endpoint_data(&self) -> Option<&PendingEndpointDataQueue> {
420 self.endpoint_data.as_ref()
421 }
422}
423
424#[derive(Debug, Default)]
426pub(crate) struct PendingSessionTrafficQueues {
427 pending_destinations: HashSet<NodeAddr>,
428 tun_packets: HashMap<NodeAddr, PendingTunPacketQueue>,
429 endpoint_data: HashMap<NodeAddr, PendingEndpointDataQueue>,
430}
431
432impl PendingSessionTrafficQueues {
433 pub(crate) fn push_tun_packet(
434 &mut self,
435 dest_addr: NodeAddr,
436 packet: Vec<u8>,
437 max_destinations: usize,
438 packets_per_dest: usize,
439 ) -> PendingSessionTrafficAdmission {
440 if !self.tun_packets.contains_key(&dest_addr) && self.tun_packets.len() >= max_destinations
441 {
442 return PendingSessionTrafficAdmission {
443 destination_dropped: true,
444 dropped_oldest: false,
445 };
446 }
447
448 let admission = self.tun_packets.entry(dest_addr).or_default().push_bounded(
449 packet,
450 crate::time::now_ms(),
451 packets_per_dest,
452 );
453 self.pending_destinations.insert(dest_addr);
454 PendingSessionTrafficAdmission {
455 destination_dropped: false,
456 dropped_oldest: admission.dropped_oldest(),
457 }
458 }
459
460 pub(crate) fn push_endpoint_data(
461 &mut self,
462 dest_addr: NodeAddr,
463 payload: impl Into<EndpointDataPayload>,
464 max_destinations: usize,
465 packets_per_dest: usize,
466 ) -> PendingSessionTrafficAdmission {
467 if !self.endpoint_data.contains_key(&dest_addr)
468 && self.endpoint_data.len() >= max_destinations
469 {
470 return PendingSessionTrafficAdmission {
471 destination_dropped: true,
472 dropped_oldest: false,
473 };
474 }
475
476 let admission = self
477 .endpoint_data
478 .entry(dest_addr)
479 .or_default()
480 .push_bounded(payload.into(), packets_per_dest);
481 self.pending_destinations.insert(dest_addr);
482 PendingSessionTrafficAdmission {
483 destination_dropped: false,
484 dropped_oldest: admission.dropped_oldest(),
485 }
486 }
487
488 pub(crate) fn remove_destination(&mut self, dest_addr: &NodeAddr) -> PendingDestinationTraffic {
489 self.pending_destinations.remove(dest_addr);
490 PendingDestinationTraffic {
491 tun_packets: self.tun_packets.remove(dest_addr),
492 endpoint_data: self.endpoint_data.remove(dest_addr),
493 }
494 }
495
496 pub(crate) fn take_tun_packets(
497 &mut self,
498 dest_addr: &NodeAddr,
499 ) -> Option<PendingTunPacketQueue> {
500 let packets = self.tun_packets.remove(dest_addr);
501 if packets.is_some() && !self.endpoint_data.contains_key(dest_addr) {
502 self.pending_destinations.remove(dest_addr);
503 }
504 packets
505 }
506
507 pub(crate) fn take_endpoint_data(
508 &mut self,
509 dest_addr: &NodeAddr,
510 ) -> Option<PendingEndpointDataQueue> {
511 let payloads = self.endpoint_data.remove(dest_addr);
512 if payloads.is_some() && !self.tun_packets.contains_key(dest_addr) {
513 self.pending_destinations.remove(dest_addr);
514 }
515 payloads
516 }
517
518 pub(crate) fn has_traffic_for(&self, dest_addr: &NodeAddr) -> bool {
519 self.pending_destinations.contains(dest_addr)
520 }
521
522 pub(crate) fn tun_packets_for(&self, dest_addr: &NodeAddr) -> Option<&PendingTunPacketQueue> {
523 self.tun_packets.get(dest_addr)
524 }
525
526 pub(crate) fn endpoint_data_for(
527 &self,
528 dest_addr: &NodeAddr,
529 ) -> Option<&PendingEndpointDataQueue> {
530 self.endpoint_data.get(dest_addr)
531 }
532
533 pub(crate) fn tun_destination_count(&self) -> usize {
534 self.tun_packets.len()
535 }
536
537 pub(crate) fn tun_packet_count(&self) -> usize {
538 self.tun_packets.values().map(|q| q.len()).sum()
539 }
540}
541
542fn endpoint_tcp_payload_is_latency_sensitive(payload: &[u8], tcp_offset: usize) -> bool {
543 const TCP_MIN_HEADER_LEN: usize = 20;
544 const TCP_FLAG_FIN: u8 = 0x01;
545 const TCP_FLAG_SYN: u8 = 0x02;
546 const TCP_FLAG_RST: u8 = 0x04;
547 const INTERACTIVE_TCP_PAYLOAD_MAX: usize = 256;
548
549 if payload.len() < tcp_offset + TCP_MIN_HEADER_LEN {
550 return true;
551 }
552
553 let tcp_header_len = usize::from(payload[tcp_offset + 12] >> 4) * 4;
554 if tcp_header_len < TCP_MIN_HEADER_LEN || payload.len() < tcp_offset + tcp_header_len {
555 return true;
556 }
557
558 let flags = payload[tcp_offset + 13];
559 if flags & (TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST) != 0 {
560 return true;
561 }
562
563 let payload_len = endpoint_ip_payload_len(payload)
564 .and_then(|ip_payload_len| ip_payload_len.checked_sub(tcp_header_len))
565 .unwrap_or_else(|| payload.len().saturating_sub(tcp_offset + tcp_header_len));
566 payload_len <= INTERACTIVE_TCP_PAYLOAD_MAX
567}
568
569fn endpoint_ip_payload_len(payload: &[u8]) -> Option<usize> {
570 const IPV4_MIN_HEADER_LEN: usize = 20;
571 const IPV6_HEADER_LEN: usize = 40;
572
573 let version_ihl = payload.first().copied()?;
574 match version_ihl >> 4 {
575 4 => {
576 if payload.len() < IPV4_MIN_HEADER_LEN {
577 return None;
578 }
579 let header_len = usize::from(version_ihl & 0x0f) * 4;
580 if header_len < IPV4_MIN_HEADER_LEN || payload.len() < header_len {
581 return None;
582 }
583 let total_len = usize::from(u16::from_be_bytes([payload[2], payload[3]]));
584 total_len.checked_sub(header_len)
585 }
586 6 => {
587 if payload.len() < IPV6_HEADER_LEN {
588 return None;
589 }
590 Some(usize::from(u16::from_be_bytes([payload[4], payload[5]])))
591 }
592 _ => None,
593 }
594}
595
596fn parse_endpoint_payload_ip_proto(payload: &[u8]) -> Option<(u8, usize)> {
597 const IPV4_MIN_HEADER_LEN: usize = 20;
598
599 let version_ihl = payload.first().copied()?;
600
601 match version_ihl >> 4 {
602 4 => {
603 if payload.len() < IPV4_MIN_HEADER_LEN {
604 return None;
605 }
606 let header_len = usize::from(version_ihl & 0x0f) * 4;
607 if header_len >= IPV4_MIN_HEADER_LEN && payload.len() >= header_len {
608 Some((payload[9], header_len))
609 } else {
610 None
611 }
612 }
613 6 => ipv6_payload_next_header(payload),
614 _ => None,
615 }
616}
617
618#[derive(Clone, Copy)]
619struct EndpointFlowParts<'a> {
620 version: u8,
621 proto: u8,
622 src: &'a [u8],
623 dst: &'a [u8],
624 ports: Option<[u8; 4]>,
625}
626
627impl EndpointFlowParts<'_> {
628 fn hash(self) -> u64 {
629 let mut h = EndpointFlowHasher::default();
630 h.write_u8(self.version);
631 h.write_u8(self.proto);
632 h.write(self.src);
633 h.write(self.dst);
634 if let Some(ports) = self.ports {
635 h.write(&ports);
636 }
637 h.finish()
638 }
639}
640
641#[derive(Clone, Copy)]
642struct EndpointFlowHasher(u64);
643
644impl Default for EndpointFlowHasher {
645 fn default() -> Self {
646 Self(0x9ae1_6a3b_2f90_404f)
647 }
648}
649
650impl EndpointFlowHasher {
651 fn write_u8(&mut self, value: u8) {
652 self.write(&[value]);
653 }
654
655 fn write(&mut self, bytes: &[u8]) {
656 for byte in bytes {
657 self.0 ^= u64::from(*byte);
658 self.0 = self.0.wrapping_mul(0x1000_0000_01b3);
659 self.0 ^= self.0 >> 32;
660 }
661 }
662
663 fn finish(self) -> u64 {
664 self.0
665 }
666}
667
668fn endpoint_payload_flow_parts(payload: &[u8]) -> Option<EndpointFlowParts<'_>> {
669 const IPV4_MIN_HEADER_LEN: usize = 20;
670 const IPV6_HEADER_LEN: usize = 40;
671
672 let version = payload.first().copied()? >> 4;
673 match version {
674 4 => {
675 if payload.len() < IPV4_MIN_HEADER_LEN {
676 return None;
677 }
678 let header_len = usize::from(payload[0] & 0x0f) * 4;
679 if header_len < IPV4_MIN_HEADER_LEN || payload.len() < header_len {
680 return None;
681 }
682 let fragment_bits = u16::from_be_bytes([payload[6], payload[7]]) & 0x3fff;
683 Some(EndpointFlowParts {
684 version,
685 proto: payload[9],
686 src: &payload[12..16],
687 dst: &payload[16..20],
688 ports: if fragment_bits == 0 {
689 endpoint_transport_ports(payload, payload[9], header_len)
690 } else {
691 None
692 },
693 })
694 }
695 6 => {
696 if payload.len() < IPV6_HEADER_LEN {
697 return None;
698 }
699 let (proto, offset, fragmented) = ipv6_payload_next_header_with_fragment(payload)?;
700 Some(EndpointFlowParts {
701 version,
702 proto,
703 src: &payload[8..24],
704 dst: &payload[24..40],
705 ports: if fragmented {
706 None
707 } else {
708 endpoint_transport_ports(payload, proto, offset)
709 },
710 })
711 }
712 _ => None,
713 }
714}
715
716fn endpoint_transport_ports(payload: &[u8], proto: u8, transport_offset: usize) -> Option<[u8; 4]> {
717 const IPPROTO_TCP: u8 = 6;
718 const IPPROTO_UDP: u8 = 17;
719 const IPPROTO_SCTP: u8 = 132;
720
721 if !matches!(proto, IPPROTO_TCP | IPPROTO_UDP | IPPROTO_SCTP) {
722 return None;
723 }
724 let ports = payload.get(transport_offset..transport_offset + 4)?;
725 Some([ports[0], ports[1], ports[2], ports[3]])
726}
727
728#[cfg(test)]
729pub(in crate::node) fn endpoint_payload_is_tcp(payload: &[u8]) -> bool {
730 const IPPROTO_TCP: u8 = 6;
731 parse_endpoint_payload_ip_proto(payload).is_some_and(|(proto, _)| proto == IPPROTO_TCP)
732}
733
734fn ipv6_payload_next_header(payload: &[u8]) -> Option<(u8, usize)> {
735 ipv6_payload_next_header_with_fragment(payload)
736 .map(|(next_header, offset, _)| (next_header, offset))
737}
738
739fn ipv6_payload_next_header_with_fragment(payload: &[u8]) -> Option<(u8, usize, bool)> {
740 const IPV6_HEADER_LEN: usize = 40;
741 const IPV6_FRAGMENT_HEADER_LEN: usize = 8;
742
743 if payload.len() < IPV6_HEADER_LEN || payload[0] >> 4 != 6 {
744 return None;
745 }
746
747 let mut next_header = payload[6];
748 let mut offset = IPV6_HEADER_LEN;
749 let mut extension_count = 0usize;
750 let mut fragmented = false;
751 while ipv6_extension_header_is_skippable(next_header) {
752 if next_header == 44 {
753 if payload.len() < offset + IPV6_FRAGMENT_HEADER_LEN {
754 return None;
755 }
756 fragmented = true;
757 next_header = payload[offset];
758 offset += IPV6_FRAGMENT_HEADER_LEN;
759 } else if next_header == 51 {
760 if payload.len() < offset + 2 {
761 return None;
762 }
763 let header_len = (usize::from(payload[offset + 1]) + 2) * 4;
764 if payload.len() < offset + header_len {
765 return None;
766 }
767 next_header = payload[offset];
768 offset += header_len;
769 } else {
770 if payload.len() < offset + 2 {
771 return None;
772 }
773 let header_len = (usize::from(payload[offset + 1]) + 1) * 8;
774 if payload.len() < offset + header_len {
775 return None;
776 }
777 next_header = payload[offset];
778 offset += header_len;
779 }
780 extension_count += 1;
781 if extension_count > 8 {
782 return None;
783 }
784 }
785
786 Some((next_header, offset, fragmented))
787}
788
789fn ipv6_extension_header_is_skippable(next_header: u8) -> bool {
790 matches!(next_header, 0 | 43 | 44 | 51 | 60 | 135)
791}