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: std::sync::Arc<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_handle() 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 direct_fmp_endpoint_allowed: bool,
174}
175
176impl EndpointDataPayload {
177 pub(crate) fn new(bytes: Vec<u8>) -> Self {
178 let traffic_class = classify_endpoint_payload(&bytes);
179 Self {
180 bytes,
181 traffic_class,
182 direct_fmp_endpoint_allowed: false,
183 }
184 }
185
186 pub(crate) fn from_classified_with_direct_fmp_endpoint_allowed(
187 bytes: Vec<u8>,
188 traffic_class: EndpointPayloadClass,
189 direct_fmp_endpoint_allowed: bool,
190 ) -> Self {
191 Self {
192 bytes,
193 traffic_class,
194 direct_fmp_endpoint_allowed,
195 }
196 }
197
198 #[cfg(test)]
199 pub(crate) fn allow_direct_fmp_endpoint_data(mut self) -> Self {
200 self.direct_fmp_endpoint_allowed = true;
201 self
202 }
203
204 pub(crate) fn lane(&self) -> EndpointCommandLane {
205 self.traffic_class.lane().command_lane()
206 }
207
208 pub(crate) fn bulk_endpoint_data(&self) -> bool {
209 self.traffic_class.lane() == EndpointPayloadLane::Bulk
210 }
211
212 pub(crate) fn drop_on_backpressure(&self) -> bool {
213 self.traffic_class.drop_on_backpressure()
214 }
215
216 pub(crate) fn direct_fmp_endpoint_allowed(&self) -> bool {
217 self.direct_fmp_endpoint_allowed
218 }
219
220 pub(crate) fn as_slice(&self) -> &[u8] {
221 &self.bytes
222 }
223
224 pub(crate) fn len(&self) -> usize {
225 self.bytes.len()
226 }
227}
228
229impl From<Vec<u8>> for EndpointDataPayload {
230 fn from(bytes: Vec<u8>) -> Self {
231 Self::new(bytes)
232 }
233}
234
235#[derive(Debug)]
237pub(crate) struct EndpointDataSend {
238 dest_addr: NodeAddr,
239 dest_pubkey: secp256k1::PublicKey,
240 payload: EndpointDataPayload,
241}
242
243impl EndpointDataSend {
244 pub(crate) fn new(remote: PeerIdentity, payload: EndpointDataPayload) -> Self {
245 Self {
246 dest_addr: *remote.node_addr(),
247 dest_pubkey: remote.pubkey_full(),
248 payload,
249 }
250 }
251
252 pub(crate) fn dest_addr(&self) -> NodeAddr {
253 self.dest_addr
254 }
255
256 pub(crate) fn dest_pubkey(&self) -> secp256k1::PublicKey {
257 self.dest_pubkey
258 }
259
260 pub(crate) fn payload(&self) -> &EndpointDataPayload {
261 &self.payload
262 }
263
264 pub(crate) fn into_payload(self) -> EndpointDataPayload {
265 self.payload
266 }
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq)]
271pub(crate) struct PendingEndpointDataQueueAdmission {
272 dropped_oldest: bool,
273}
274
275impl PendingEndpointDataQueueAdmission {
276 pub(crate) fn dropped_oldest(&self) -> bool {
277 self.dropped_oldest
278 }
279}
280
281#[derive(Debug, Default)]
283pub(crate) struct PendingEndpointDataQueue {
284 payloads: VecDeque<EndpointDataPayload>,
285}
286
287impl PendingEndpointDataQueue {
288 pub(crate) fn push_bounded(
289 &mut self,
290 payload: EndpointDataPayload,
291 capacity: usize,
292 ) -> PendingEndpointDataQueueAdmission {
293 let dropped_oldest = self.payloads.len() >= capacity;
294 if dropped_oldest {
295 self.payloads.pop_front();
296 }
297 self.payloads.push_back(payload);
298 PendingEndpointDataQueueAdmission { dropped_oldest }
299 }
300
301 pub(crate) fn len(&self) -> usize {
302 self.payloads.len()
303 }
304
305 pub(crate) fn into_payloads(self) -> VecDeque<EndpointDataPayload> {
306 self.payloads
307 }
308
309 #[cfg(test)]
310 pub(crate) fn iter(&self) -> impl Iterator<Item = &EndpointDataPayload> {
311 self.payloads.iter()
312 }
313}
314
315#[derive(Clone, Copy, Debug, PartialEq, Eq)]
317pub(crate) struct PendingTunPacketQueueAdmission {
318 dropped_oldest: bool,
319}
320
321impl PendingTunPacketQueueAdmission {
322 pub(crate) fn dropped_oldest(&self) -> bool {
323 self.dropped_oldest
324 }
325}
326
327#[derive(Debug, Default)]
329pub(crate) struct PendingTunPacketQueue {
330 packets: VecDeque<Vec<u8>>,
331}
332
333impl PendingTunPacketQueue {
334 pub(crate) fn push_bounded(
335 &mut self,
336 packet: Vec<u8>,
337 capacity: usize,
338 ) -> PendingTunPacketQueueAdmission {
339 let dropped_oldest = self.packets.len() >= capacity;
340 if dropped_oldest {
341 self.packets.pop_front();
342 }
343 self.packets.push_back(packet);
344 PendingTunPacketQueueAdmission { dropped_oldest }
345 }
346
347 pub(crate) fn len(&self) -> usize {
348 self.packets.len()
349 }
350
351 pub(crate) fn into_packets(self) -> VecDeque<Vec<u8>> {
352 self.packets
353 }
354
355 #[cfg(test)]
356 pub(crate) fn iter(&self) -> impl Iterator<Item = &Vec<u8>> {
357 self.packets.iter()
358 }
359}
360
361#[derive(Clone, Copy, Debug, PartialEq, Eq)]
363pub(crate) struct PendingSessionTrafficAdmission {
364 destination_dropped: bool,
365 dropped_oldest: bool,
366}
367
368impl PendingSessionTrafficAdmission {
369 pub(crate) fn destination_dropped(&self) -> bool {
370 self.destination_dropped
371 }
372
373 pub(crate) fn dropped_oldest(&self) -> bool {
374 self.dropped_oldest
375 }
376}
377
378#[derive(Debug, Default)]
380pub(crate) struct PendingDestinationTraffic {
381 tun_packets: Option<PendingTunPacketQueue>,
382 endpoint_data: Option<PendingEndpointDataQueue>,
383}
384
385impl PendingDestinationTraffic {
386 pub(crate) fn tun_packets(&self) -> Option<&PendingTunPacketQueue> {
387 self.tun_packets.as_ref()
388 }
389
390 pub(crate) fn into_tun_packets(self) -> Option<PendingTunPacketQueue> {
391 self.tun_packets
392 }
393
394 pub(crate) fn endpoint_data(&self) -> Option<&PendingEndpointDataQueue> {
395 self.endpoint_data.as_ref()
396 }
397}
398
399#[derive(Debug, Default)]
401pub(crate) struct PendingSessionTrafficQueues {
402 pending_destinations: HashSet<NodeAddr>,
403 tun_packets: HashMap<NodeAddr, PendingTunPacketQueue>,
404 endpoint_data: HashMap<NodeAddr, PendingEndpointDataQueue>,
405}
406
407impl PendingSessionTrafficQueues {
408 pub(crate) fn push_tun_packet(
409 &mut self,
410 dest_addr: NodeAddr,
411 packet: Vec<u8>,
412 max_destinations: usize,
413 packets_per_dest: usize,
414 ) -> PendingSessionTrafficAdmission {
415 if !self.tun_packets.contains_key(&dest_addr) && self.tun_packets.len() >= max_destinations
416 {
417 return PendingSessionTrafficAdmission {
418 destination_dropped: true,
419 dropped_oldest: false,
420 };
421 }
422
423 let admission = self
424 .tun_packets
425 .entry(dest_addr)
426 .or_default()
427 .push_bounded(packet, packets_per_dest);
428 self.pending_destinations.insert(dest_addr);
429 PendingSessionTrafficAdmission {
430 destination_dropped: false,
431 dropped_oldest: admission.dropped_oldest(),
432 }
433 }
434
435 pub(crate) fn push_endpoint_data(
436 &mut self,
437 dest_addr: NodeAddr,
438 payload: impl Into<EndpointDataPayload>,
439 max_destinations: usize,
440 packets_per_dest: usize,
441 ) -> PendingSessionTrafficAdmission {
442 if !self.endpoint_data.contains_key(&dest_addr)
443 && self.endpoint_data.len() >= max_destinations
444 {
445 return PendingSessionTrafficAdmission {
446 destination_dropped: true,
447 dropped_oldest: false,
448 };
449 }
450
451 let admission = self
452 .endpoint_data
453 .entry(dest_addr)
454 .or_default()
455 .push_bounded(payload.into(), packets_per_dest);
456 self.pending_destinations.insert(dest_addr);
457 PendingSessionTrafficAdmission {
458 destination_dropped: false,
459 dropped_oldest: admission.dropped_oldest(),
460 }
461 }
462
463 pub(crate) fn remove_destination(&mut self, dest_addr: &NodeAddr) -> PendingDestinationTraffic {
464 self.pending_destinations.remove(dest_addr);
465 PendingDestinationTraffic {
466 tun_packets: self.tun_packets.remove(dest_addr),
467 endpoint_data: self.endpoint_data.remove(dest_addr),
468 }
469 }
470
471 pub(crate) fn take_tun_packets(
472 &mut self,
473 dest_addr: &NodeAddr,
474 ) -> Option<PendingTunPacketQueue> {
475 let packets = self.tun_packets.remove(dest_addr);
476 if packets.is_some() && !self.endpoint_data.contains_key(dest_addr) {
477 self.pending_destinations.remove(dest_addr);
478 }
479 packets
480 }
481
482 pub(crate) fn take_endpoint_data(
483 &mut self,
484 dest_addr: &NodeAddr,
485 ) -> Option<PendingEndpointDataQueue> {
486 let payloads = self.endpoint_data.remove(dest_addr);
487 if payloads.is_some() && !self.tun_packets.contains_key(dest_addr) {
488 self.pending_destinations.remove(dest_addr);
489 }
490 payloads
491 }
492
493 pub(crate) fn has_traffic_for(&self, dest_addr: &NodeAddr) -> bool {
494 self.pending_destinations.contains(dest_addr)
495 }
496
497 pub(crate) fn tun_packets_for(&self, dest_addr: &NodeAddr) -> Option<&PendingTunPacketQueue> {
498 self.tun_packets.get(dest_addr)
499 }
500
501 pub(crate) fn endpoint_data_for(
502 &self,
503 dest_addr: &NodeAddr,
504 ) -> Option<&PendingEndpointDataQueue> {
505 self.endpoint_data.get(dest_addr)
506 }
507
508 pub(crate) fn tun_destination_count(&self) -> usize {
509 self.tun_packets.len()
510 }
511
512 pub(crate) fn tun_packet_count(&self) -> usize {
513 self.tun_packets.values().map(|q| q.len()).sum()
514 }
515}
516
517fn endpoint_tcp_payload_is_latency_sensitive(payload: &[u8], tcp_offset: usize) -> bool {
518 const TCP_MIN_HEADER_LEN: usize = 20;
519 const TCP_FLAG_FIN: u8 = 0x01;
520 const TCP_FLAG_SYN: u8 = 0x02;
521 const TCP_FLAG_RST: u8 = 0x04;
522 const INTERACTIVE_TCP_PAYLOAD_MAX: usize = 256;
523
524 if payload.len() < tcp_offset + TCP_MIN_HEADER_LEN {
525 return true;
526 }
527
528 let tcp_header_len = usize::from(payload[tcp_offset + 12] >> 4) * 4;
529 if tcp_header_len < TCP_MIN_HEADER_LEN || payload.len() < tcp_offset + tcp_header_len {
530 return true;
531 }
532
533 let flags = payload[tcp_offset + 13];
534 if flags & (TCP_FLAG_FIN | TCP_FLAG_SYN | TCP_FLAG_RST) != 0 {
535 return true;
536 }
537
538 let payload_len = endpoint_ip_payload_len(payload)
539 .and_then(|ip_payload_len| ip_payload_len.checked_sub(tcp_header_len))
540 .unwrap_or_else(|| payload.len().saturating_sub(tcp_offset + tcp_header_len));
541 payload_len <= INTERACTIVE_TCP_PAYLOAD_MAX
542}
543
544fn endpoint_ip_payload_len(payload: &[u8]) -> Option<usize> {
545 const IPV4_MIN_HEADER_LEN: usize = 20;
546 const IPV6_HEADER_LEN: usize = 40;
547
548 let version_ihl = payload.first().copied()?;
549 match version_ihl >> 4 {
550 4 => {
551 if payload.len() < IPV4_MIN_HEADER_LEN {
552 return None;
553 }
554 let header_len = usize::from(version_ihl & 0x0f) * 4;
555 if header_len < IPV4_MIN_HEADER_LEN || payload.len() < header_len {
556 return None;
557 }
558 let total_len = usize::from(u16::from_be_bytes([payload[2], payload[3]]));
559 total_len.checked_sub(header_len)
560 }
561 6 => {
562 if payload.len() < IPV6_HEADER_LEN {
563 return None;
564 }
565 Some(usize::from(u16::from_be_bytes([payload[4], payload[5]])))
566 }
567 _ => None,
568 }
569}
570
571fn parse_endpoint_payload_ip_proto(payload: &[u8]) -> Option<(u8, usize)> {
572 const IPV4_MIN_HEADER_LEN: usize = 20;
573
574 let version_ihl = payload.first().copied()?;
575
576 match version_ihl >> 4 {
577 4 => {
578 if payload.len() < IPV4_MIN_HEADER_LEN {
579 return None;
580 }
581 let header_len = usize::from(version_ihl & 0x0f) * 4;
582 if header_len >= IPV4_MIN_HEADER_LEN && payload.len() >= header_len {
583 Some((payload[9], header_len))
584 } else {
585 None
586 }
587 }
588 6 => ipv6_payload_next_header(payload),
589 _ => None,
590 }
591}
592
593#[derive(Clone, Copy)]
594struct EndpointFlowParts<'a> {
595 version: u8,
596 proto: u8,
597 src: &'a [u8],
598 dst: &'a [u8],
599 ports: Option<[u8; 4]>,
600}
601
602impl EndpointFlowParts<'_> {
603 fn hash(self) -> u64 {
604 let mut h = EndpointFlowHasher::default();
605 h.write_u8(self.version);
606 h.write_u8(self.proto);
607 h.write(self.src);
608 h.write(self.dst);
609 if let Some(ports) = self.ports {
610 h.write(&ports);
611 }
612 h.finish()
613 }
614}
615
616#[derive(Clone, Copy)]
617struct EndpointFlowHasher(u64);
618
619impl Default for EndpointFlowHasher {
620 fn default() -> Self {
621 Self(0x9ae1_6a3b_2f90_404f)
622 }
623}
624
625impl EndpointFlowHasher {
626 fn write_u8(&mut self, value: u8) {
627 self.write(&[value]);
628 }
629
630 fn write(&mut self, bytes: &[u8]) {
631 for byte in bytes {
632 self.0 ^= u64::from(*byte);
633 self.0 = self.0.wrapping_mul(0x1000_0000_01b3);
634 self.0 ^= self.0 >> 32;
635 }
636 }
637
638 fn finish(self) -> u64 {
639 self.0
640 }
641}
642
643fn endpoint_payload_flow_parts(payload: &[u8]) -> Option<EndpointFlowParts<'_>> {
644 const IPV4_MIN_HEADER_LEN: usize = 20;
645 const IPV6_HEADER_LEN: usize = 40;
646
647 let version = payload.first().copied()? >> 4;
648 match version {
649 4 => {
650 if payload.len() < IPV4_MIN_HEADER_LEN {
651 return None;
652 }
653 let header_len = usize::from(payload[0] & 0x0f) * 4;
654 if header_len < IPV4_MIN_HEADER_LEN || payload.len() < header_len {
655 return None;
656 }
657 let fragment_bits = u16::from_be_bytes([payload[6], payload[7]]) & 0x3fff;
658 Some(EndpointFlowParts {
659 version,
660 proto: payload[9],
661 src: &payload[12..16],
662 dst: &payload[16..20],
663 ports: if fragment_bits == 0 {
664 endpoint_transport_ports(payload, payload[9], header_len)
665 } else {
666 None
667 },
668 })
669 }
670 6 => {
671 if payload.len() < IPV6_HEADER_LEN {
672 return None;
673 }
674 let (proto, offset, fragmented) = ipv6_payload_next_header_with_fragment(payload)?;
675 Some(EndpointFlowParts {
676 version,
677 proto,
678 src: &payload[8..24],
679 dst: &payload[24..40],
680 ports: if fragmented {
681 None
682 } else {
683 endpoint_transport_ports(payload, proto, offset)
684 },
685 })
686 }
687 _ => None,
688 }
689}
690
691fn endpoint_transport_ports(payload: &[u8], proto: u8, transport_offset: usize) -> Option<[u8; 4]> {
692 const IPPROTO_TCP: u8 = 6;
693 const IPPROTO_UDP: u8 = 17;
694 const IPPROTO_SCTP: u8 = 132;
695
696 if !matches!(proto, IPPROTO_TCP | IPPROTO_UDP | IPPROTO_SCTP) {
697 return None;
698 }
699 let ports = payload.get(transport_offset..transport_offset + 4)?;
700 Some([ports[0], ports[1], ports[2], ports[3]])
701}
702
703#[cfg(test)]
704pub(in crate::node) fn endpoint_payload_is_tcp(payload: &[u8]) -> bool {
705 const IPPROTO_TCP: u8 = 6;
706 parse_endpoint_payload_ip_proto(payload).is_some_and(|(proto, _)| proto == IPPROTO_TCP)
707}
708
709fn ipv6_payload_next_header(payload: &[u8]) -> Option<(u8, usize)> {
710 ipv6_payload_next_header_with_fragment(payload)
711 .map(|(next_header, offset, _)| (next_header, offset))
712}
713
714fn ipv6_payload_next_header_with_fragment(payload: &[u8]) -> Option<(u8, usize, bool)> {
715 const IPV6_HEADER_LEN: usize = 40;
716 const IPV6_FRAGMENT_HEADER_LEN: usize = 8;
717
718 if payload.len() < IPV6_HEADER_LEN || payload[0] >> 4 != 6 {
719 return None;
720 }
721
722 let mut next_header = payload[6];
723 let mut offset = IPV6_HEADER_LEN;
724 let mut extension_count = 0usize;
725 let mut fragmented = false;
726 while ipv6_extension_header_is_skippable(next_header) {
727 if next_header == 44 {
728 if payload.len() < offset + IPV6_FRAGMENT_HEADER_LEN {
729 return None;
730 }
731 fragmented = true;
732 next_header = payload[offset];
733 offset += IPV6_FRAGMENT_HEADER_LEN;
734 } else if next_header == 51 {
735 if payload.len() < offset + 2 {
736 return None;
737 }
738 let header_len = (usize::from(payload[offset + 1]) + 2) * 4;
739 if payload.len() < offset + header_len {
740 return None;
741 }
742 next_header = payload[offset];
743 offset += header_len;
744 } else {
745 if payload.len() < offset + 2 {
746 return None;
747 }
748 let header_len = (usize::from(payload[offset + 1]) + 1) * 8;
749 if payload.len() < offset + header_len {
750 return None;
751 }
752 next_header = payload[offset];
753 offset += header_len;
754 }
755 extension_count += 1;
756 if extension_count > 8 {
757 return None;
758 }
759 }
760
761 Some((next_header, offset, fragmented))
762}
763
764fn ipv6_extension_header_is_skippable(next_header: u8) -> bool {
765 matches!(next_header, 0 | 43 | 44 | 51 | 60 | 135)
766}