1use crate::error::McrxError;
2use crate::packet::ReceiveMetadata;
3use crate::platform::{
4 RawCapturedDatagram, RawReceiveSocket, RawSharedCaptureKey, join_raw_multicast_group,
5 leave_raw_multicast_group, open_shared_raw_socket, recv_shared_raw_datagram,
6 shared_raw_capture_key,
7};
8use crate::raw::{RawPacket, RawSubscriptionConfig};
9use crate::subscription::{SubscriptionId, SubscriptionState};
10use std::collections::hash_map::Entry;
11use std::collections::{HashMap, VecDeque};
12use std::net::IpAddr;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct SharedRawContextLimits {
21 pub max_subscriptions: usize,
23 pub max_pending_packets: usize,
25}
26
27impl Default for SharedRawContextLimits {
28 fn default() -> Self {
29 Self {
30 max_subscriptions: 4_096,
31 max_pending_packets: 1_024,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
43pub struct SharedRawSubscription {
44 id: SubscriptionId,
45 config: RawSubscriptionConfig,
46 capture_key: RawSharedCaptureKey,
47 state: SubscriptionState,
48}
49
50impl SharedRawSubscription {
51 fn new(
52 id: SubscriptionId,
53 config: RawSubscriptionConfig,
54 capture_key: RawSharedCaptureKey,
55 ) -> Self {
56 Self {
57 id,
58 config,
59 capture_key,
60 state: SubscriptionState::Bound,
61 }
62 }
63
64 pub fn id(&self) -> SubscriptionId {
66 self.id
67 }
68
69 pub fn config(&self) -> &RawSubscriptionConfig {
71 &self.config
72 }
73
74 pub fn state(&self) -> SubscriptionState {
76 self.state
77 }
78
79 pub fn is_joined(&self) -> bool {
81 matches!(self.state, SubscriptionState::Joined)
82 }
83}
84
85#[derive(Debug, Clone)]
92pub struct SharedRawPacket {
93 pub packet: RawPacket,
95 matching_subscription_ids: MatchingSubscriptionIds,
96}
97
98impl SharedRawPacket {
99 pub fn packet(&self) -> &RawPacket {
101 &self.packet
102 }
103
104 pub fn matching_subscription_ids(&self) -> &[SubscriptionId] {
106 self.matching_subscription_ids.as_slice()
107 }
108}
109
110#[derive(Debug, Clone)]
111enum MatchingSubscriptionIds {
112 One([SubscriptionId; 1]),
113 Many(Vec<SubscriptionId>),
114}
115
116impl MatchingSubscriptionIds {
117 fn from_sorted_slices(first: &[SubscriptionId], second: &[SubscriptionId]) -> Option<Self> {
118 match first.len() + second.len() {
119 0 => None,
120 1 => {
121 let id = match first.first() {
122 Some(id) => *id,
123 None => second[0],
124 };
125 Some(Self::One([id]))
126 }
127 total => Some(Self::Many(merge_ids(first, second, total))),
128 }
129 }
130
131 fn as_slice(&self) -> &[SubscriptionId] {
132 match self {
133 Self::One(id) => id,
134 Self::Many(ids) => ids,
135 }
136 }
137
138 fn first(&self) -> SubscriptionId {
139 self.as_slice()[0]
140 }
141
142 fn remove_existing(&mut self, index: usize) {
143 let Self::Many(ids) = self else {
144 unreachable!("a single matching subscription is removed with its queued packet");
145 };
146
147 ids.remove(index);
148 if ids.len() == 1 {
149 *self = Self::One([ids[0]]);
150 }
151 }
152}
153
154#[derive(Debug, Default)]
155struct GroupDemultiplexer {
156 any_source: Vec<SubscriptionId>,
157 sources: HashMap<IpAddr, Vec<SubscriptionId>>,
158}
159
160impl GroupDemultiplexer {
161 fn insert(&mut self, id: SubscriptionId, source: Option<IpAddr>) {
162 let ids = match source {
163 Some(source) => self.sources.entry(source).or_default(),
164 None => &mut self.any_source,
165 };
166 insert_id(ids, id);
167 }
168
169 fn remove(&mut self, id: SubscriptionId, source: Option<IpAddr>) {
170 match source {
171 Some(source) => {
172 if let Some(ids) = self.sources.get_mut(&source) {
173 remove_id(ids, id);
174 if ids.is_empty() {
175 self.sources.remove(&source);
176 }
177 }
178 }
179 None => remove_id(&mut self.any_source, id),
180 }
181 }
182
183 fn is_empty(&self) -> bool {
184 self.any_source.is_empty() && self.sources.is_empty()
185 }
186
187 fn matches(&self, source: IpAddr) -> Option<MatchingSubscriptionIds> {
188 let source_matches = self.sources.get(&source).map_or(&[][..], Vec::as_slice);
189 MatchingSubscriptionIds::from_sorted_slices(&self.any_source, source_matches)
190 }
191}
192
193#[derive(Debug, Default)]
194struct CaptureDemultiplexer {
195 groups: HashMap<IpAddr, GroupDemultiplexer>,
196}
197
198impl CaptureDemultiplexer {
199 fn insert(&mut self, id: SubscriptionId, config: &RawSubscriptionConfig) {
200 self.groups
201 .entry(config.group)
202 .or_default()
203 .insert(id, config.source_addr());
204 }
205
206 fn remove(&mut self, id: SubscriptionId, config: &RawSubscriptionConfig) {
207 let remove_group = if let Some(group) = self.groups.get_mut(&config.group) {
208 group.remove(id, config.source_addr());
209 group.is_empty()
210 } else {
211 false
212 };
213
214 if remove_group {
215 self.groups.remove(&config.group);
216 }
217 }
218
219 fn matches(&self, group: IpAddr, source: IpAddr) -> Option<MatchingSubscriptionIds> {
220 self.groups.get(&group)?.matches(source)
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
225struct KernelMembershipKey {
226 group: IpAddr,
227 source: Option<IpAddr>,
228}
229
230impl From<&RawSubscriptionConfig> for KernelMembershipKey {
231 fn from(config: &RawSubscriptionConfig) -> Self {
232 Self {
233 group: config.group,
234 source: config.source_addr(),
235 }
236 }
237}
238
239#[derive(Debug)]
240struct KernelMembership {
241 references: usize,
242 joined_config: RawSubscriptionConfig,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
246struct CaptureSelector {
247 family: crate::SubscriptionAddressFamily,
248 interface: Option<IpAddr>,
249 interface_index: Option<u32>,
250}
251
252impl From<&RawSubscriptionConfig> for CaptureSelector {
253 fn from(config: &RawSubscriptionConfig) -> Self {
254 Self {
255 family: config.family(),
256 interface: config.interface,
257 interface_index: config.interface_index,
258 }
259 }
260}
261
262#[derive(Debug)]
263struct ResolvedCaptureKey {
264 key: RawSharedCaptureKey,
265 references: usize,
266}
267
268#[derive(Debug)]
269struct SharedCaptureSocket {
270 socket: RawReceiveSocket,
271 memberships: usize,
272 kernel_memberships: HashMap<KernelMembershipKey, KernelMembership>,
273 demultiplexer: CaptureDemultiplexer,
274}
275
276impl SharedCaptureSocket {
277 fn new(socket: RawReceiveSocket) -> Self {
278 Self {
279 socket,
280 memberships: 0,
281 kernel_memberships: HashMap::new(),
282 demultiplexer: CaptureDemultiplexer::default(),
283 }
284 }
285}
286
287#[cfg(feature = "metrics")]
288#[derive(Debug, Default)]
289struct SharedRawCaptureMetrics {
290 received_packets_total: u64,
291 unmatched_packets_total: u64,
292 demultiplex_matches_total: u64,
293}
294
295#[cfg(feature = "metrics")]
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct SharedRawCaptureMetricsSnapshot {
299 pub capture_socket_count: usize,
301 pub active_memberships: usize,
303 pub received_packets_total: u64,
305 pub unmatched_packets_total: u64,
307 pub demultiplex_matches_total: u64,
309 pub pending_packets: usize,
311}
312
313#[derive(Debug)]
323pub struct SharedRawContext {
324 subscriptions: HashMap<SubscriptionId, SharedRawSubscription>,
325 resolved_capture_keys: HashMap<CaptureSelector, ResolvedCaptureKey>,
326 captures: HashMap<RawSharedCaptureKey, SharedCaptureSocket>,
327 capture_order: Vec<RawSharedCaptureKey>,
328 pending_packets: VecDeque<SharedRawPacket>,
329 limits: SharedRawContextLimits,
330 next_subscription_id: u64,
331 next_capture_index: usize,
332 #[cfg(feature = "metrics")]
333 metrics: SharedRawCaptureMetrics,
334}
335
336impl Default for SharedRawContext {
337 fn default() -> Self {
338 Self::new()
339 }
340}
341
342impl SharedRawContext {
343 pub fn new() -> Self {
345 Self::with_limits(SharedRawContextLimits::default())
346 .expect("default shared raw capture limits are valid")
347 }
348
349 pub fn with_limits(limits: SharedRawContextLimits) -> Result<Self, McrxError> {
351 if limits.max_subscriptions == 0 || limits.max_pending_packets == 0 {
352 return Err(McrxError::InvalidSharedRawCaptureLimits);
353 }
354
355 Ok(Self {
356 subscriptions: HashMap::new(),
357 resolved_capture_keys: HashMap::new(),
358 captures: HashMap::new(),
359 capture_order: Vec::new(),
360 pending_packets: VecDeque::new(),
361 limits,
362 next_subscription_id: 1,
363 next_capture_index: 0,
364 #[cfg(feature = "metrics")]
365 metrics: SharedRawCaptureMetrics::default(),
366 })
367 }
368
369 pub fn limits(&self) -> SharedRawContextLimits {
371 self.limits
372 }
373
374 pub fn subscription_count(&self) -> usize {
376 self.subscriptions.len()
377 }
378
379 pub fn capture_socket_count(&self) -> usize {
381 self.captures.len()
382 }
383
384 pub fn active_membership_count(&self) -> usize {
386 self.captures
387 .values()
388 .map(|capture| capture.memberships)
389 .sum()
390 }
391
392 pub fn pending_packet_count(&self) -> usize {
394 self.pending_packets.len()
395 }
396
397 pub fn contains_subscription(&self, id: SubscriptionId) -> bool {
399 self.subscriptions.contains_key(&id)
400 }
401
402 pub fn get_subscription(&self, id: SubscriptionId) -> Option<&SharedRawSubscription> {
404 self.subscriptions.get(&id)
405 }
406
407 pub fn add_subscription(
416 &mut self,
417 config: RawSubscriptionConfig,
418 ) -> Result<SubscriptionId, McrxError> {
419 config.validate()?;
420 if self.subscriptions.len() >= self.limits.max_subscriptions {
421 return Err(McrxError::SharedRawCaptureSubscriptionLimitExceeded {
422 limit: self.limits.max_subscriptions,
423 });
424 }
425
426 let capture_key = self.resolve_capture_key(&config)?;
427 let id = SubscriptionId(self.next_subscription_id);
428 self.next_subscription_id += 1;
429 self.subscriptions
430 .insert(id, SharedRawSubscription::new(id, config, capture_key));
431 Ok(id)
432 }
433
434 pub fn join_subscription(&mut self, id: SubscriptionId) -> Result<(), McrxError> {
441 let (capture_key, config) = {
442 let subscription = self
443 .subscriptions
444 .get(&id)
445 .ok_or(McrxError::SubscriptionNotFound)?;
446 if subscription.is_joined() {
447 return Err(McrxError::SubscriptionAlreadyJoined);
448 }
449 (subscription.capture_key, subscription.config.clone())
450 };
451 let membership_key = KernelMembershipKey::from(&config);
452
453 let created_capture = match self.captures.entry(capture_key) {
454 Entry::Occupied(_) => false,
455 Entry::Vacant(entry) => {
456 let socket = open_shared_raw_socket(capture_key)?;
457 entry.insert(SharedCaptureSocket::new(socket));
458 self.capture_order.push(capture_key);
459 true
460 }
461 };
462
463 let needs_kernel_join = !self
464 .captures
465 .get(&capture_key)
466 .expect("shared capture was inserted or already existed")
467 .kernel_memberships
468 .contains_key(&membership_key);
469 let join_result = if needs_kernel_join {
470 let capture = self
471 .captures
472 .get(&capture_key)
473 .expect("shared capture was inserted or already existed");
474 join_raw_multicast_group(&capture.socket, &config)
475 } else {
476 Ok(())
477 };
478 if let Err(error) = join_result {
479 if created_capture {
480 self.remove_capture(capture_key);
481 }
482 return Err(error);
483 }
484
485 let capture = self
486 .captures
487 .get_mut(&capture_key)
488 .expect("joined shared capture exists");
489 match capture.kernel_memberships.entry(membership_key) {
490 Entry::Occupied(mut entry) => entry.get_mut().references += 1,
491 Entry::Vacant(entry) => {
492 entry.insert(KernelMembership {
493 references: 1,
494 joined_config: config.clone(),
495 });
496 }
497 }
498 capture.demultiplexer.insert(id, &config);
499 capture.memberships += 1;
500 self.subscriptions
501 .get_mut(&id)
502 .expect("joined subscription exists")
503 .state = SubscriptionState::Joined;
504 Ok(())
505 }
506
507 pub fn leave_subscription(&mut self, id: SubscriptionId) -> Result<(), McrxError> {
509 let (capture_key, config) = {
510 let subscription = self
511 .subscriptions
512 .get(&id)
513 .ok_or(McrxError::SubscriptionNotFound)?;
514 if !subscription.is_joined() {
515 return Err(McrxError::SubscriptionNotJoined);
516 }
517 (subscription.capture_key, subscription.config.clone())
518 };
519 let membership_key = KernelMembershipKey::from(&config);
520
521 let should_remove_capture = {
522 let capture = self
523 .captures
524 .get_mut(&capture_key)
525 .expect("joined subscription has a shared capture");
526 let joined_config = {
527 let membership = capture
528 .kernel_memberships
529 .get_mut(&membership_key)
530 .expect("joined subscription has a kernel membership");
531 if membership.references == 1 {
532 Some(membership.joined_config.clone())
533 } else {
534 membership.references -= 1;
535 None
536 }
537 };
538 if let Some(joined_config) = joined_config {
539 leave_raw_multicast_group(&capture.socket, &joined_config)?;
540 capture.kernel_memberships.remove(&membership_key);
541 }
542 capture.demultiplexer.remove(id, &config);
543 capture.memberships = capture
544 .memberships
545 .checked_sub(1)
546 .expect("membership count");
547 debug_assert_eq!(
548 capture.memberships == 0,
549 capture.kernel_memberships.is_empty()
550 );
551 capture.memberships == 0
552 };
553
554 self.subscriptions
555 .get_mut(&id)
556 .expect("joined subscription exists")
557 .state = SubscriptionState::Bound;
558 self.remove_pending_membership(id);
559 if should_remove_capture {
560 self.remove_capture(capture_key);
561 }
562 Ok(())
563 }
564
565 pub fn remove_subscription(&mut self, id: SubscriptionId) -> Result<bool, McrxError> {
569 if !self.subscriptions.contains_key(&id) {
570 return Ok(false);
571 }
572 if self
573 .subscriptions
574 .get(&id)
575 .expect("subscription existence checked")
576 .is_joined()
577 {
578 self.leave_subscription(id)?;
579 }
580 let removed = self
581 .subscriptions
582 .remove(&id)
583 .expect("subscription existence checked");
584 self.release_capture_key(&removed.config);
585 Ok(true)
586 }
587
588 pub fn try_recv_any(&mut self) -> Result<Option<SharedRawPacket>, McrxError> {
593 if let Some(packet) = self.pending_packets.pop_front() {
594 return Ok(Some(packet));
595 }
596
597 self.fill_pending(1)?;
598 Ok(self.pending_packets.pop_front())
599 }
600
601 pub fn try_recv_batch_into(
605 &mut self,
606 packets: &mut Vec<SharedRawPacket>,
607 max_packets: usize,
608 ) -> Result<usize, McrxError> {
609 if max_packets == 0 {
610 return Ok(0);
611 }
612
613 let mut received = self.drain_pending_into(packets, max_packets);
614 while received < max_packets {
615 let queued = self.fill_pending(max_packets - received)?;
616 if queued == 0 {
617 break;
618 }
619 received += self.drain_pending_into(packets, max_packets - received);
620 }
621 Ok(received)
622 }
623
624 #[cfg(feature = "metrics")]
626 pub fn metrics_snapshot(&self) -> SharedRawCaptureMetricsSnapshot {
627 SharedRawCaptureMetricsSnapshot {
628 capture_socket_count: self.capture_socket_count(),
629 active_memberships: self.active_membership_count(),
630 received_packets_total: self.metrics.received_packets_total,
631 unmatched_packets_total: self.metrics.unmatched_packets_total,
632 demultiplex_matches_total: self.metrics.demultiplex_matches_total,
633 pending_packets: self.pending_packet_count(),
634 }
635 }
636
637 fn fill_pending(&mut self, max_new_packets: usize) -> Result<usize, McrxError> {
638 let target = max_new_packets.min(
639 self.limits
640 .max_pending_packets
641 .saturating_sub(self.pending_packets.len()),
642 );
643 if self.capture_order.is_empty() || target == 0 {
644 return Ok(0);
645 }
646
647 let capture_count = self.capture_order.len();
648 let mut queued_packets = 0;
649 let mut captured_datagrams = 0;
650 let capture_budget = target.saturating_add(capture_count);
651
652 'captures: for _ in 0..capture_count {
653 let capture_key = self.capture_order[self.next_capture_index];
654 self.next_capture_index = (self.next_capture_index + 1) % capture_count;
655
656 for _ in 0..target {
657 if queued_packets == target || captured_datagrams == capture_budget {
658 break 'captures;
659 }
660
661 let captured_and_matches = {
662 let capture = self
663 .captures
664 .get(&capture_key)
665 .expect("capture order and capture map stay in sync");
666 let Some(captured) = recv_shared_raw_datagram(&capture.socket, capture_key)?
667 else {
668 break;
669 };
670 let matches = capture
671 .demultiplexer
672 .matches(captured.group, captured.source_ip);
673 (captured, matches)
674 };
675 captured_datagrams += 1;
676 self.record_received_packet();
677
678 let (captured, Some(matching_ids)) = captured_and_matches else {
679 self.record_unmatched_packet();
680 continue;
681 };
682
683 self.record_demultiplex_matches(matching_ids.as_slice().len());
684 let primary_id = matching_ids.first();
685 let packet = {
686 let primary_config = &self
687 .subscriptions
688 .get(&primary_id)
689 .expect("demultiplexer only contains registered subscriptions")
690 .config;
691 raw_packet_from_captured(primary_id, primary_config, captured)
692 };
693 self.pending_packets.push_back(SharedRawPacket {
694 packet,
695 matching_subscription_ids: matching_ids,
696 });
697 queued_packets += 1;
698 }
699 }
700 Ok(queued_packets)
701 }
702
703 fn drain_pending_into(
704 &mut self,
705 packets: &mut Vec<SharedRawPacket>,
706 max_packets: usize,
707 ) -> usize {
708 let drained = max_packets.min(self.pending_packets.len());
709 packets.extend(self.pending_packets.drain(..drained));
710 drained
711 }
712
713 fn remove_capture(&mut self, capture_key: RawSharedCaptureKey) {
714 self.captures.remove(&capture_key);
715 let index = self
716 .capture_order
717 .iter()
718 .position(|key| *key == capture_key)
719 .expect("capture order contains every capture map key");
720 self.capture_order.remove(index);
721 if self.capture_order.is_empty() {
722 self.next_capture_index = 0;
723 } else {
724 self.next_capture_index %= self.capture_order.len();
725 }
726 }
727
728 fn resolve_capture_key(
729 &mut self,
730 config: &RawSubscriptionConfig,
731 ) -> Result<RawSharedCaptureKey, McrxError> {
732 let selector = CaptureSelector::from(config);
733 match self.resolved_capture_keys.entry(selector) {
734 Entry::Occupied(mut entry) => {
735 let resolved = entry.get_mut();
736 resolved.references += 1;
737 Ok(resolved.key)
738 }
739 Entry::Vacant(entry) => {
740 let key = shared_raw_capture_key(config)?;
741 entry.insert(ResolvedCaptureKey { key, references: 1 });
742 Ok(key)
743 }
744 }
745 }
746
747 fn release_capture_key(&mut self, config: &RawSubscriptionConfig) {
748 let selector = CaptureSelector::from(config);
749 let remove = {
750 let resolved = self
751 .resolved_capture_keys
752 .get_mut(&selector)
753 .expect("stored subscription has a resolved capture key");
754 resolved.references = resolved
755 .references
756 .checked_sub(1)
757 .expect("resolved capture key reference count");
758 resolved.references == 0
759 };
760 if remove {
761 self.resolved_capture_keys.remove(&selector);
762 }
763 }
764
765 fn remove_pending_membership(&mut self, id: SubscriptionId) {
766 let subscriptions = &self.subscriptions;
767 self.pending_packets.retain_mut(|shared_packet| {
768 let Some(index) = shared_packet
769 .matching_subscription_ids
770 .as_slice()
771 .iter()
772 .position(|matching_id| *matching_id == id)
773 else {
774 return true;
775 };
776
777 if shared_packet.matching_subscription_ids.as_slice().len() == 1 {
778 return false;
779 }
780
781 shared_packet
782 .matching_subscription_ids
783 .remove_existing(index);
784 let primary_id = shared_packet.matching_subscription_ids.first();
785 if shared_packet.packet.subscription_id != primary_id {
786 let primary_config = &subscriptions
787 .get(&primary_id)
788 .expect("queued packet only references registered subscriptions")
789 .config;
790 shared_packet.packet.subscription_id = primary_id;
791 shared_packet.packet.metadata.configured_interface = primary_config.interface;
792 shared_packet.packet.metadata.configured_interface_index =
793 primary_config.interface_index;
794 }
795 true
796 });
797 }
798
799 #[cfg(not(feature = "metrics"))]
800 fn record_received_packet(&mut self) {}
801
802 #[cfg(feature = "metrics")]
803 fn record_received_packet(&mut self) {
804 self.metrics.received_packets_total = self.metrics.received_packets_total.saturating_add(1);
805 }
806
807 #[cfg(not(feature = "metrics"))]
808 fn record_unmatched_packet(&mut self) {}
809
810 #[cfg(feature = "metrics")]
811 fn record_unmatched_packet(&mut self) {
812 self.metrics.unmatched_packets_total =
813 self.metrics.unmatched_packets_total.saturating_add(1);
814 }
815
816 #[cfg(not(feature = "metrics"))]
817 fn record_demultiplex_matches(&mut self, _matches: usize) {}
818
819 #[cfg(feature = "metrics")]
820 fn record_demultiplex_matches(&mut self, matches: usize) {
821 self.metrics.demultiplex_matches_total = self
822 .metrics
823 .demultiplex_matches_total
824 .saturating_add(matches as u64);
825 }
826}
827
828fn raw_packet_from_captured(
829 subscription_id: SubscriptionId,
830 config: &RawSubscriptionConfig,
831 captured: RawCapturedDatagram,
832) -> RawPacket {
833 RawPacket {
834 subscription_id,
835 datagram: captured.datagram,
836 source_ip: Some(captured.source_ip),
837 group: Some(captured.group),
838 ip_protocol: Some(captured.ip_protocol),
839 metadata: ReceiveMetadata {
840 socket_local_addr: None,
841 configured_interface: config.interface,
842 configured_interface_index: config.interface_index,
843 destination_local_ip: Some(captured.group),
844 ingress_interface_index: captured.ingress_interface_index,
845 },
846 }
847}
848
849fn insert_id(ids: &mut Vec<SubscriptionId>, id: SubscriptionId) {
850 match ids.binary_search_by_key(&id.0, |existing| existing.0) {
851 Ok(_) => {}
852 Err(index) => ids.insert(index, id),
853 }
854}
855
856fn remove_id(ids: &mut Vec<SubscriptionId>, id: SubscriptionId) {
857 if let Ok(index) = ids.binary_search_by_key(&id.0, |existing| existing.0) {
858 ids.remove(index);
859 }
860}
861
862fn merge_ids(
863 first: &[SubscriptionId],
864 second: &[SubscriptionId],
865 total: usize,
866) -> Vec<SubscriptionId> {
867 let mut matches = Vec::with_capacity(total);
868 let (mut first_index, mut second_index) = (0, 0);
869 while first_index < first.len() && second_index < second.len() {
870 if first[first_index].0 < second[second_index].0 {
871 matches.push(first[first_index]);
872 first_index += 1;
873 } else if first[first_index].0 > second[second_index].0 {
874 matches.push(second[second_index]);
875 second_index += 1;
876 } else {
877 matches.push(first[first_index]);
878 first_index += 1;
879 second_index += 1;
880 }
881 }
882 matches.extend_from_slice(&first[first_index..]);
883 matches.extend_from_slice(&second[second_index..]);
884 matches
885}
886
887#[cfg(test)]
888mod tests {
889 use super::*;
890 use crate::SourceFilter;
891 use std::net::{Ipv4Addr, Ipv6Addr};
892
893 fn asm(group: Ipv4Addr) -> RawSubscriptionConfig {
894 RawSubscriptionConfig::asm(group)
895 }
896
897 fn ssm(group: Ipv4Addr, source: Ipv4Addr) -> RawSubscriptionConfig {
898 RawSubscriptionConfig::ssm(group, source)
899 }
900
901 #[test]
902 fn demultiplexer_matches_duplicate_logical_memberships_once_each() {
903 let group = Ipv4Addr::new(239, 1, 2, 3);
904 let source = Ipv4Addr::new(192, 0, 2, 10);
905 let config = asm(group);
906 let mut demux = CaptureDemultiplexer::default();
907 demux.insert(SubscriptionId(9), &config);
908 demux.insert(SubscriptionId(3), &config);
909
910 assert_eq!(
911 demux
912 .matches(group.into(), source.into())
913 .unwrap()
914 .as_slice(),
915 &[SubscriptionId(3), SubscriptionId(9)]
916 );
917 }
918
919 #[test]
920 fn demultiplexer_uses_group_then_source_without_cross_group_matches() {
921 let first_group = Ipv4Addr::new(239, 1, 2, 3);
922 let second_group = Ipv4Addr::new(239, 1, 2, 4);
923 let source = Ipv4Addr::new(192, 0, 2, 10);
924 let mut demux = CaptureDemultiplexer::default();
925 demux.insert(SubscriptionId(1), &ssm(first_group, source));
926 demux.insert(SubscriptionId(2), &asm(second_group));
927
928 assert!(
929 demux
930 .matches(first_group.into(), Ipv4Addr::new(192, 0, 2, 11).into())
931 .is_none()
932 );
933 assert_eq!(
934 demux
935 .matches(second_group.into(), source.into())
936 .unwrap()
937 .as_slice(),
938 &[SubscriptionId(2)]
939 );
940 assert!(matches!(
941 demux.matches(second_group.into(), source.into()),
942 Some(MatchingSubscriptionIds::One(_))
943 ));
944 }
945
946 #[test]
947 fn removing_one_duplicate_does_not_disturb_the_other_logical_membership() {
948 let group = Ipv4Addr::new(239, 1, 2, 3);
949 let source = Ipv4Addr::new(192, 0, 2, 10);
950 let config = asm(group);
951 let mut demux = CaptureDemultiplexer::default();
952 demux.insert(SubscriptionId(1), &config);
953 demux.insert(SubscriptionId(2), &config);
954 demux.remove(SubscriptionId(1), &config);
955
956 assert_eq!(
957 demux
958 .matches(group.into(), source.into())
959 .unwrap()
960 .as_slice(),
961 &[SubscriptionId(2)]
962 );
963 assert_eq!(
964 demux
965 .matches(group.into(), Ipv4Addr::new(192, 0, 2, 11).into())
966 .unwrap()
967 .as_slice(),
968 &[SubscriptionId(2)]
969 );
970 }
971
972 #[test]
973 fn kernel_membership_keys_track_group_and_source_within_one_capture() {
974 let group = Ipv4Addr::new(239, 1, 2, 3);
975 let first = asm(group);
976 let mut duplicate = first.clone();
977 duplicate.interface = Some(Ipv4Addr::new(192, 0, 2, 10).into());
978
979 assert_eq!(
980 KernelMembershipKey::from(&first),
981 KernelMembershipKey::from(&duplicate)
982 );
983 assert_ne!(
984 KernelMembershipKey::from(&first),
985 KernelMembershipKey::from(&asm(Ipv4Addr::new(239, 1, 2, 4)))
986 );
987
988 let ssm_group = Ipv4Addr::new(232, 1, 2, 3);
989 assert_ne!(
990 KernelMembershipKey::from(&ssm(ssm_group, Ipv4Addr::new(192, 0, 2, 10))),
991 KernelMembershipKey::from(&ssm(ssm_group, Ipv4Addr::new(192, 0, 2, 11)))
992 );
993 }
994
995 #[test]
996 fn resolved_capture_key_cache_is_bounded_by_stored_selectors() {
997 let config = asm(Ipv4Addr::new(239, 1, 2, 3));
998 let selector = CaptureSelector::from(&config);
999 let capture_key = RawSharedCaptureKey {
1000 family: crate::SubscriptionAddressFamily::Ipv4,
1001 interface_index: 2,
1002 };
1003 let mut context = SharedRawContext::new();
1004 context.resolved_capture_keys.insert(
1005 selector,
1006 ResolvedCaptureKey {
1007 key: capture_key,
1008 references: 2,
1009 },
1010 );
1011
1012 context.release_capture_key(&config);
1013 assert_eq!(context.resolved_capture_keys[&selector].references, 1);
1014
1015 context.release_capture_key(&config);
1016 assert!(context.resolved_capture_keys.is_empty());
1017 }
1018
1019 #[test]
1020 fn capture_keys_separate_address_families_and_interfaces() {
1021 let ipv4_eth0 = RawSharedCaptureKey {
1022 family: crate::SubscriptionAddressFamily::Ipv4,
1023 interface_index: 2,
1024 };
1025 let ipv4_eth1 = RawSharedCaptureKey {
1026 family: crate::SubscriptionAddressFamily::Ipv4,
1027 interface_index: 3,
1028 };
1029 let ipv6_eth0 = RawSharedCaptureKey {
1030 family: crate::SubscriptionAddressFamily::Ipv6,
1031 interface_index: 2,
1032 };
1033
1034 assert_ne!(ipv4_eth0, ipv4_eth1);
1035 assert_ne!(ipv4_eth0, ipv6_eth0);
1036 }
1037
1038 #[test]
1039 fn limits_reject_zero_and_default_is_bounded() {
1040 assert!(matches!(
1041 SharedRawContext::with_limits(SharedRawContextLimits {
1042 max_subscriptions: 0,
1043 max_pending_packets: 1,
1044 }),
1045 Err(McrxError::InvalidSharedRawCaptureLimits)
1046 ));
1047 assert!(matches!(
1048 SharedRawContext::with_limits(SharedRawContextLimits {
1049 max_subscriptions: 1,
1050 max_pending_packets: 0,
1051 }),
1052 Err(McrxError::InvalidSharedRawCaptureLimits)
1053 ));
1054 assert!(SharedRawContextLimits::default().max_pending_packets > 0);
1055 }
1056
1057 #[test]
1058 fn subscription_limit_is_enforced_before_platform_socket_setup() {
1059 let first_config = asm(Ipv4Addr::new(239, 1, 2, 3));
1060 let mut context = SharedRawContext::with_limits(SharedRawContextLimits {
1061 max_subscriptions: 1,
1062 max_pending_packets: 1,
1063 })
1064 .unwrap();
1065 context.subscriptions.insert(
1066 SubscriptionId(1),
1067 SharedRawSubscription::new(
1068 SubscriptionId(1),
1069 first_config,
1070 RawSharedCaptureKey {
1071 family: crate::SubscriptionAddressFamily::Ipv4,
1072 interface_index: 2,
1073 },
1074 ),
1075 );
1076
1077 let error = context
1078 .add_subscription(asm(Ipv4Addr::new(239, 1, 2, 4)))
1079 .unwrap_err();
1080 assert!(matches!(
1081 error,
1082 McrxError::SharedRawCaptureSubscriptionLimitExceeded { limit: 1 }
1083 ));
1084 }
1085
1086 #[test]
1087 fn raw_packet_from_capture_preserves_complete_datagram_and_metadata() {
1088 let config = RawSubscriptionConfig::asm(Ipv4Addr::new(239, 1, 2, 3));
1089 let packet = raw_packet_from_captured(
1090 SubscriptionId(7),
1091 &config,
1092 RawCapturedDatagram {
1093 datagram: bytes::Bytes::from_static(&[0x45, 0, 0, 20]),
1094 source_ip: Ipv4Addr::new(192, 0, 2, 10).into(),
1095 group: config.group,
1096 ip_protocol: 17,
1097 ingress_interface_index: Some(2),
1098 },
1099 );
1100
1101 assert_eq!(packet.datagram.as_ref(), &[0x45, 0, 0, 20]);
1102 assert_eq!(packet.source_ip, Some(Ipv4Addr::new(192, 0, 2, 10).into()));
1103 assert_eq!(packet.metadata.ingress_interface_index, Some(2));
1104 }
1105
1106 #[test]
1107 fn removing_a_pending_overlap_keeps_the_remaining_membership() {
1108 let first_id = SubscriptionId(1);
1109 let second_id = SubscriptionId(2);
1110 let first_config = asm(Ipv4Addr::new(239, 1, 2, 3));
1111 let second_config = first_config.clone();
1112 let capture_key = RawSharedCaptureKey {
1113 family: crate::SubscriptionAddressFamily::Ipv4,
1114 interface_index: 2,
1115 };
1116 let mut context = SharedRawContext::new();
1117 context.subscriptions.insert(
1118 first_id,
1119 SharedRawSubscription::new(first_id, first_config.clone(), capture_key),
1120 );
1121 context.subscriptions.insert(
1122 second_id,
1123 SharedRawSubscription::new(second_id, second_config.clone(), capture_key),
1124 );
1125 context.pending_packets.push_back(SharedRawPacket {
1126 packet: raw_packet_from_captured(
1127 first_id,
1128 &first_config,
1129 RawCapturedDatagram {
1130 datagram: bytes::Bytes::from_static(&[0x45, 0, 0, 20]),
1131 source_ip: Ipv4Addr::new(192, 0, 2, 10).into(),
1132 group: first_config.group,
1133 ip_protocol: 17,
1134 ingress_interface_index: Some(2),
1135 },
1136 ),
1137 matching_subscription_ids: MatchingSubscriptionIds::Many(vec![first_id, second_id]),
1138 });
1139
1140 context.remove_pending_membership(first_id);
1141
1142 let packet = context.pending_packets.front().expect("remaining overlap");
1143 assert_eq!(packet.packet.subscription_id, second_id);
1144 assert_eq!(packet.matching_subscription_ids(), &[second_id]);
1145 }
1146
1147 #[test]
1148 fn batch_receive_drains_queued_packets_up_to_the_requested_limit() {
1149 fn queued_packet(id: SubscriptionId) -> SharedRawPacket {
1150 SharedRawPacket {
1151 packet: RawPacket {
1152 subscription_id: id,
1153 datagram: bytes::Bytes::from_static(&[0x45, 0, 0, 20]),
1154 source_ip: Some(Ipv4Addr::new(192, 0, 2, 10).into()),
1155 group: Some(Ipv4Addr::new(239, 1, 2, 3).into()),
1156 ip_protocol: Some(17),
1157 metadata: ReceiveMetadata::empty(),
1158 },
1159 matching_subscription_ids: MatchingSubscriptionIds::One([id]),
1160 }
1161 }
1162
1163 let mut context = SharedRawContext::new();
1164 context
1165 .pending_packets
1166 .push_back(queued_packet(SubscriptionId(1)));
1167 context
1168 .pending_packets
1169 .push_back(queued_packet(SubscriptionId(2)));
1170 let mut packets = Vec::new();
1171
1172 let received = context.try_recv_batch_into(&mut packets, 1).unwrap();
1173
1174 assert_eq!(received, 1);
1175 assert_eq!(packets[0].packet.subscription_id, SubscriptionId(1));
1176 assert_eq!(context.pending_packet_count(), 1);
1177 }
1178
1179 #[cfg(feature = "metrics")]
1180 #[test]
1181 fn metrics_snapshot_reports_shared_capture_counters() {
1182 let mut context = SharedRawContext::new();
1183 context.record_received_packet();
1184 context.record_received_packet();
1185 context.record_unmatched_packet();
1186 context.record_demultiplex_matches(3);
1187
1188 let snapshot = context.metrics_snapshot();
1189 assert_eq!(snapshot.capture_socket_count, 0);
1190 assert_eq!(snapshot.active_memberships, 0);
1191 assert_eq!(snapshot.received_packets_total, 2);
1192 assert_eq!(snapshot.unmatched_packets_total, 1);
1193 assert_eq!(snapshot.demultiplex_matches_total, 3);
1194 assert_eq!(snapshot.pending_packets, 0);
1195 }
1196
1197 #[test]
1198 fn source_filter_enum_remains_usable_for_shared_configs() {
1199 let config = RawSubscriptionConfig {
1200 group: Ipv6Addr::from(0xff3e_0000_0000_0000_0000_0000_0000_1234u128).into(),
1201 source: SourceFilter::Source("2001:db8::1".parse().unwrap()),
1202 interface: Some("fe80::1".parse().unwrap()),
1203 interface_index: Some(2),
1204 };
1205 assert!(config.validate().is_ok());
1206 }
1207}