Skip to main content

crafter/wire/ip/
config.rs

1//! Shared configuration for IP wire transforms.
2
3use core::time::Duration;
4
5use crate::{CrafterError, Result};
6
7/// Default maximum number of incomplete IP datagrams tracked by `IpDefrag`.
8pub const IP_DEFRAG_DEFAULT_MAX_DATAGRAMS: usize = 1024;
9
10/// Default maximum bytes retained for one incomplete IP datagram.
11pub const IP_DEFRAG_DEFAULT_MAX_BYTES_PER_DATAGRAM: usize = 65_535;
12
13/// Default maximum age for an incomplete IP datagram.
14pub const IP_DEFRAG_DEFAULT_MAX_AGE: Duration = Duration::from_secs(60);
15
16/// Smallest configured MTU that can make progress for IPv4 fragmentation.
17pub const IP_FRAGMENT_MIN_MTU: usize = 28;
18
19/// How `IpDefrag` handles conflicting overlapping fragment bytes.
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
21pub enum IpDefragOverlapPolicy {
22    /// Return a structured error instead of emitting an ambiguous datagram.
23    #[default]
24    RejectConflicting,
25    /// Drop the ambiguous datagram state and keep processing later records.
26    DropConflicting,
27    /// Pass conflicting fragment records through with explicit trace metadata.
28    PassThroughConflicting,
29}
30
31/// How `IpDefrag` handles IPv6 atomic fragments.
32#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
33pub enum Ipv6AtomicFragmentPolicy {
34    /// Emit atomic fragments unchanged, optionally with pass-through trace data.
35    PassThrough,
36    /// Normalize atomic fragments by removing the Fragment Header.
37    #[default]
38    Normalize,
39    /// Drop atomic fragments without emitting an output record.
40    Drop,
41}
42
43/// How `IpFragment` handles IPv4 packets with Don't Fragment set.
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
45pub enum Ipv4DontFragmentPolicy {
46    /// Return a structured error when fragmentation would be required.
47    #[default]
48    Error,
49    /// Emit the original record unchanged with explicit trace metadata.
50    PassThrough,
51    /// Ignore Don't Fragment and emit fragments anyway.
52    FragmentAnyway,
53}
54
55/// How `IpFragment` chooses an IPv4 Identification value when it emits fragments.
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
57pub enum Ipv4FragmentIdentificationPolicy {
58    /// Preserve an explicit packet ID, otherwise generate one.
59    #[default]
60    PreserveOrGenerate,
61    /// Require the input packet to carry the ID that should be used.
62    PreserveOnly,
63    /// Use one fixed ID for every emitted IPv4 fragment set.
64    Fixed(u16),
65}
66
67/// How `IpFragment` chooses an IPv6 Fragment Identification value.
68#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
69pub enum Ipv6FragmentIdentificationPolicy {
70    /// Generate an identification value for each emitted IPv6 fragment set.
71    #[default]
72    Generate,
73    /// Use one fixed ID for every emitted IPv6 fragment set.
74    Fixed(u32),
75}
76
77/// Configuration for the receive-side IP defragmentation transform.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
79pub struct IpDefragConfig {
80    pass_non_fragments: bool,
81    max_datagrams: usize,
82    max_bytes_per_datagram: usize,
83    max_age: Duration,
84    overlap_policy: IpDefragOverlapPolicy,
85    ipv6_atomic_fragment_policy: Ipv6AtomicFragmentPolicy,
86    trace_passthrough: bool,
87    trace_evictions: bool,
88}
89
90impl IpDefragConfig {
91    /// Create the default IP defragmentation configuration.
92    pub const fn new() -> Self {
93        Self {
94            pass_non_fragments: true,
95            max_datagrams: IP_DEFRAG_DEFAULT_MAX_DATAGRAMS,
96            max_bytes_per_datagram: IP_DEFRAG_DEFAULT_MAX_BYTES_PER_DATAGRAM,
97            max_age: IP_DEFRAG_DEFAULT_MAX_AGE,
98            overlap_policy: IpDefragOverlapPolicy::RejectConflicting,
99            ipv6_atomic_fragment_policy: Ipv6AtomicFragmentPolicy::Normalize,
100            trace_passthrough: false,
101            trace_evictions: false,
102        }
103    }
104
105    /// Configure whether records that are not handled as fragments pass through.
106    pub const fn pass_non_fragments(mut self, pass_non_fragments: bool) -> Self {
107        self.pass_non_fragments = pass_non_fragments;
108        self
109    }
110
111    /// Whether non-fragment records are configured for pass-through.
112    pub const fn emits_non_fragments(&self) -> bool {
113        self.pass_non_fragments
114    }
115
116    /// Set the maximum number of incomplete datagrams retained at once.
117    pub const fn max_datagrams(mut self, max_datagrams: usize) -> Self {
118        self.max_datagrams = max_datagrams;
119        self
120    }
121
122    /// Set the maximum number of incomplete datagrams retained at once.
123    pub fn try_max_datagrams(self, max_datagrams: usize) -> Result<Self> {
124        validate_nonzero("ip.defrag.max_datagrams", max_datagrams)?;
125        Ok(self.max_datagrams(max_datagrams))
126    }
127
128    /// Maximum number of incomplete datagrams retained at once.
129    pub const fn max_datagrams_limit(&self) -> usize {
130        self.max_datagrams
131    }
132
133    /// Set the maximum bytes retained for one incomplete datagram.
134    pub const fn max_bytes_per_datagram(mut self, max_bytes_per_datagram: usize) -> Self {
135        self.max_bytes_per_datagram = max_bytes_per_datagram;
136        self
137    }
138
139    /// Set the maximum bytes retained for one incomplete datagram.
140    pub fn try_max_bytes_per_datagram(self, max_bytes_per_datagram: usize) -> Result<Self> {
141        validate_nonzero("ip.defrag.max_bytes_per_datagram", max_bytes_per_datagram)?;
142        Ok(self.max_bytes_per_datagram(max_bytes_per_datagram))
143    }
144
145    /// Maximum bytes retained for one incomplete datagram.
146    pub const fn max_bytes_per_datagram_limit(&self) -> usize {
147        self.max_bytes_per_datagram
148    }
149
150    /// Set the maximum age for an incomplete datagram.
151    pub const fn max_age(mut self, max_age: Duration) -> Self {
152        self.max_age = max_age;
153        self
154    }
155
156    /// Set the maximum age for an incomplete datagram.
157    pub fn try_max_age(self, max_age: Duration) -> Result<Self> {
158        validate_nonzero_duration("ip.defrag.max_age", max_age)?;
159        Ok(self.max_age(max_age))
160    }
161
162    /// Maximum age for an incomplete datagram.
163    pub const fn max_age_limit(&self) -> Duration {
164        self.max_age
165    }
166
167    /// Set the conflicting-overlap handling policy.
168    pub const fn overlap_policy(mut self, overlap_policy: IpDefragOverlapPolicy) -> Self {
169        self.overlap_policy = overlap_policy;
170        self
171    }
172
173    /// Conflicting-overlap handling policy.
174    pub const fn configured_overlap_policy(&self) -> IpDefragOverlapPolicy {
175        self.overlap_policy
176    }
177
178    /// Set the IPv6 atomic fragment handling policy.
179    pub const fn ipv6_atomic_fragments(
180        mut self,
181        ipv6_atomic_fragment_policy: Ipv6AtomicFragmentPolicy,
182    ) -> Self {
183        self.ipv6_atomic_fragment_policy = ipv6_atomic_fragment_policy;
184        self
185    }
186
187    /// IPv6 atomic fragment handling policy.
188    pub const fn ipv6_atomic_fragment_policy(&self) -> Ipv6AtomicFragmentPolicy {
189        self.ipv6_atomic_fragment_policy
190    }
191
192    /// Configure whether records emitted unchanged receive transform traces.
193    pub const fn trace_passthrough(mut self, trace_passthrough: bool) -> Self {
194        self.trace_passthrough = trace_passthrough;
195        self
196    }
197
198    /// Whether records emitted unchanged receive transform traces.
199    pub const fn traces_passthrough(&self) -> bool {
200        self.trace_passthrough
201    }
202
203    /// Configure whether evictions emit representative trace records.
204    pub const fn trace_evictions(mut self, trace_evictions: bool) -> Self {
205        self.trace_evictions = trace_evictions;
206        self
207    }
208
209    /// Whether evictions emit representative trace records.
210    pub const fn traces_evictions(&self) -> bool {
211        self.trace_evictions
212    }
213
214    /// Validate configuration bounds.
215    pub fn validate(&self) -> Result<()> {
216        validate_nonzero("ip.defrag.max_datagrams", self.max_datagrams)?;
217        validate_nonzero(
218            "ip.defrag.max_bytes_per_datagram",
219            self.max_bytes_per_datagram,
220        )?;
221        validate_nonzero_duration("ip.defrag.max_age", self.max_age)?;
222        Ok(())
223    }
224}
225
226impl Default for IpDefragConfig {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232/// Configuration for the transmit-side IP fragmentation transform.
233#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
234pub struct IpFragmentConfig {
235    mtu: usize,
236    dont_fragment_policy: Ipv4DontFragmentPolicy,
237    ipv4_identification_policy: Ipv4FragmentIdentificationPolicy,
238    ipv6_identification_policy: Ipv6FragmentIdentificationPolicy,
239    ipv6_identification_seed: Option<u64>,
240    trace_passthrough: bool,
241}
242
243impl IpFragmentConfig {
244    /// Create an IP fragmentation configuration with an explicit MTU.
245    pub const fn new(mtu: usize) -> Self {
246        Self {
247            mtu,
248            dont_fragment_policy: Ipv4DontFragmentPolicy::Error,
249            ipv4_identification_policy: Ipv4FragmentIdentificationPolicy::PreserveOrGenerate,
250            ipv6_identification_policy: Ipv6FragmentIdentificationPolicy::Generate,
251            ipv6_identification_seed: None,
252            trace_passthrough: false,
253        }
254    }
255
256    /// Create an IP fragmentation configuration with a validated explicit MTU.
257    pub fn try_new(mtu: usize) -> Result<Self> {
258        validate_mtu(mtu)?;
259        Ok(Self::new(mtu))
260    }
261
262    /// Configured MTU in octets.
263    pub const fn mtu(&self) -> usize {
264        self.mtu
265    }
266
267    /// Replace the configured MTU in octets.
268    pub const fn with_mtu(mut self, mtu: usize) -> Self {
269        self.mtu = mtu;
270        self
271    }
272
273    /// Replace the configured MTU in octets.
274    pub fn try_mtu(self, mtu: usize) -> Result<Self> {
275        validate_mtu(mtu)?;
276        Ok(self.with_mtu(mtu))
277    }
278
279    /// Configure whether IPv4 Don't Fragment is honored.
280    pub const fn honor_dont_fragment(mut self, honor_dont_fragment: bool) -> Self {
281        self.dont_fragment_policy = if honor_dont_fragment {
282            Ipv4DontFragmentPolicy::Error
283        } else {
284            Ipv4DontFragmentPolicy::FragmentAnyway
285        };
286        self
287    }
288
289    /// Whether IPv4 Don't Fragment is honored.
290    pub const fn honors_dont_fragment(&self) -> bool {
291        !matches!(
292            self.dont_fragment_policy,
293            Ipv4DontFragmentPolicy::FragmentAnyway
294        )
295    }
296
297    /// Set the IPv4 Don't Fragment handling policy.
298    pub const fn dont_fragment_policy(
299        mut self,
300        dont_fragment_policy: Ipv4DontFragmentPolicy,
301    ) -> Self {
302        self.dont_fragment_policy = dont_fragment_policy;
303        self
304    }
305
306    /// IPv4 Don't Fragment handling policy.
307    pub const fn configured_dont_fragment_policy(&self) -> Ipv4DontFragmentPolicy {
308        self.dont_fragment_policy
309    }
310
311    /// Set the IPv4 Identification handling policy.
312    pub const fn ipv4_identification_policy(
313        mut self,
314        ipv4_identification_policy: Ipv4FragmentIdentificationPolicy,
315    ) -> Self {
316        self.ipv4_identification_policy = ipv4_identification_policy;
317        self
318    }
319
320    /// IPv4 Identification handling policy.
321    pub const fn configured_ipv4_identification_policy(&self) -> Ipv4FragmentIdentificationPolicy {
322        self.ipv4_identification_policy
323    }
324
325    /// Set the IPv6 Fragment Identification handling policy.
326    pub const fn ipv6_identification_policy(
327        mut self,
328        ipv6_identification_policy: Ipv6FragmentIdentificationPolicy,
329    ) -> Self {
330        self.ipv6_identification_policy = ipv6_identification_policy;
331        self
332    }
333
334    /// Set one explicit IPv6 Fragment Header Identification value.
335    pub const fn ipv6_identification(mut self, identification: u32) -> Self {
336        self.ipv6_identification_policy = Ipv6FragmentIdentificationPolicy::Fixed(identification);
337        self
338    }
339
340    /// IPv6 Fragment Identification handling policy.
341    pub const fn configured_ipv6_identification_policy(&self) -> Ipv6FragmentIdentificationPolicy {
342        self.ipv6_identification_policy
343    }
344
345    /// Seed generated IPv6 Fragment Header Identification values deterministically.
346    pub const fn ipv6_identification_seed(mut self, seed: u64) -> Self {
347        self.ipv6_identification_seed = Some(seed);
348        self
349    }
350
351    /// Clear the deterministic IPv6 Fragment Header Identification seed.
352    pub const fn clear_ipv6_identification_seed(mut self) -> Self {
353        self.ipv6_identification_seed = None;
354        self
355    }
356
357    /// Configured deterministic IPv6 Fragment Header Identification seed.
358    pub const fn configured_ipv6_identification_seed(&self) -> Option<u64> {
359        self.ipv6_identification_seed
360    }
361
362    /// Configure whether records emitted unchanged receive transform traces.
363    pub const fn trace_passthrough(mut self, trace_passthrough: bool) -> Self {
364        self.trace_passthrough = trace_passthrough;
365        self
366    }
367
368    /// Whether records emitted unchanged receive transform traces.
369    pub const fn traces_passthrough(&self) -> bool {
370        self.trace_passthrough
371    }
372
373    /// Validate configuration bounds.
374    pub fn validate(&self) -> Result<()> {
375        validate_mtu(self.mtu)
376    }
377}
378
379fn validate_nonzero(field: &'static str, value: usize) -> Result<()> {
380    if value == 0 {
381        return Err(CrafterError::invalid_field_value(
382            field,
383            "must be greater than zero",
384        ));
385    }
386
387    Ok(())
388}
389
390fn validate_nonzero_duration(field: &'static str, value: Duration) -> Result<()> {
391    if value.is_zero() {
392        return Err(CrafterError::invalid_field_value(
393            field,
394            "must be greater than zero",
395        ));
396    }
397
398    Ok(())
399}
400
401fn validate_mtu(mtu: usize) -> Result<()> {
402    if mtu < IP_FRAGMENT_MIN_MTU {
403        return Err(CrafterError::invalid_field_value(
404            "ip.fragment.mtu",
405            "must fit the minimum IPv4 header and one 8-byte fragment unit",
406        ));
407    }
408
409    Ok(())
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415    use crate::wire::ip::{IpDefrag, IpFragment};
416    use crate::wire::record::{BackendKind, PacketOrigin, PacketRecord};
417    use crate::Raw;
418
419    fn assert_invalid_field(error: CrafterError, expected_field: &'static str) {
420        match error {
421            CrafterError::InvalidFieldValue { field, .. } => assert_eq!(field, expected_field),
422            other => panic!("expected InvalidFieldValue, got {other:?}"),
423        }
424    }
425
426    fn record(payload: &'static str) -> PacketRecord {
427        PacketRecord::new(Raw::from(payload))
428            .with_origin(PacketOrigin::Generated)
429            .with_backend(BackendKind::Memory)
430    }
431
432    #[test]
433    fn ip_defrag_config_builders_expose_bounds_and_policies() {
434        let config = IpDefragConfig::new()
435            .pass_non_fragments(false)
436            .max_datagrams(64)
437            .max_bytes_per_datagram(8192)
438            .max_age(Duration::from_secs(30))
439            .overlap_policy(IpDefragOverlapPolicy::DropConflicting)
440            .ipv6_atomic_fragments(Ipv6AtomicFragmentPolicy::Normalize)
441            .trace_passthrough(true)
442            .trace_evictions(true);
443
444        assert!(!config.emits_non_fragments());
445        assert_eq!(config.max_datagrams_limit(), 64);
446        assert_eq!(config.max_bytes_per_datagram_limit(), 8192);
447        assert_eq!(config.max_age_limit(), Duration::from_secs(30));
448        assert_eq!(
449            config.configured_overlap_policy(),
450            IpDefragOverlapPolicy::DropConflicting
451        );
452        assert_eq!(
453            config.ipv6_atomic_fragment_policy(),
454            Ipv6AtomicFragmentPolicy::Normalize
455        );
456        assert!(config.traces_passthrough());
457        assert!(config.traces_evictions());
458        config.validate().unwrap();
459    }
460
461    #[test]
462    fn ip_defrag_config_try_builders_reject_unbounded_settings() {
463        let max_datagrams_error = IpDefragConfig::new().try_max_datagrams(0).unwrap_err();
464        assert_invalid_field(max_datagrams_error, "ip.defrag.max_datagrams");
465
466        let max_bytes_error = IpDefragConfig::new()
467            .try_max_bytes_per_datagram(0)
468            .unwrap_err();
469        assert_invalid_field(max_bytes_error, "ip.defrag.max_bytes_per_datagram");
470
471        let max_age_error = IpDefragConfig::new()
472            .try_max_age(Duration::from_secs(0))
473            .unwrap_err();
474        assert_invalid_field(max_age_error, "ip.defrag.max_age");
475    }
476
477    #[test]
478    fn ip_fragment_config_builders_expose_mtu_df_ids_and_trace() {
479        let config = IpFragmentConfig::new(1500)
480            .with_mtu(1280)
481            .dont_fragment_policy(Ipv4DontFragmentPolicy::PassThrough)
482            .ipv4_identification_policy(Ipv4FragmentIdentificationPolicy::Fixed(0x1234))
483            .ipv6_identification(0xfeed_beef)
484            .ipv6_identification_seed(0x3100_0000_0000_0000)
485            .trace_passthrough(true);
486
487        assert_eq!(config.mtu(), 1280);
488        assert!(config.honors_dont_fragment());
489        assert_eq!(
490            config.configured_dont_fragment_policy(),
491            Ipv4DontFragmentPolicy::PassThrough
492        );
493        assert_eq!(
494            config.configured_ipv4_identification_policy(),
495            Ipv4FragmentIdentificationPolicy::Fixed(0x1234)
496        );
497        assert_eq!(
498            config.configured_ipv6_identification_policy(),
499            Ipv6FragmentIdentificationPolicy::Fixed(0xfeed_beef)
500        );
501        assert_eq!(
502            config.configured_ipv6_identification_seed(),
503            Some(0x3100_0000_0000_0000)
504        );
505        assert!(config.traces_passthrough());
506        config.validate().unwrap();
507    }
508
509    #[test]
510    fn ip_fragment_config_can_clear_ipv6_identification_seed() {
511        let config = IpFragmentConfig::new(1280)
512            .ipv6_identification_seed(31)
513            .clear_ipv6_identification_seed();
514
515        assert_eq!(config.configured_ipv6_identification_seed(), None);
516    }
517
518    #[test]
519    fn ip_fragment_config_keeps_legacy_df_bool_builder() {
520        let ignore_df = IpFragmentConfig::new(1500).honor_dont_fragment(false);
521        assert!(!ignore_df.honors_dont_fragment());
522        assert_eq!(
523            ignore_df.configured_dont_fragment_policy(),
524            Ipv4DontFragmentPolicy::FragmentAnyway
525        );
526
527        let honor_df = ignore_df.honor_dont_fragment(true);
528        assert!(honor_df.honors_dont_fragment());
529        assert_eq!(
530            honor_df.configured_dont_fragment_policy(),
531            Ipv4DontFragmentPolicy::Error
532        );
533    }
534
535    #[test]
536    fn ip_fragment_config_try_builders_reject_too_small_mtu() {
537        let new_error = IpFragmentConfig::try_new(IP_FRAGMENT_MIN_MTU - 1).unwrap_err();
538        assert_invalid_field(new_error, "ip.fragment.mtu");
539
540        let mtu_error = IpFragmentConfig::new(1500)
541            .try_mtu(IP_FRAGMENT_MIN_MTU - 1)
542            .unwrap_err();
543        assert_invalid_field(mtu_error, "ip.fragment.mtu");
544    }
545
546    #[test]
547    fn trace_passthrough_adds_transform_trace_to_unchanged_records() {
548        let mut defrag = IpDefrag::new().with_config(IpDefragConfig::new().trace_passthrough(true));
549        let defrag_output = defrag.defrag_record(record("defrag")).unwrap();
550        let defrag_traces = defrag_output.records()[0].metadata().transforms();
551        assert_eq!(defrag_traces.len(), 1);
552        assert_eq!(defrag_traces[0].name(), "ip-defrag");
553        assert_eq!(defrag_traces[0].note(), Some("passthrough"));
554
555        let mut fragment =
556            IpFragment::with_config(IpFragmentConfig::new(1280).trace_passthrough(true));
557        let fragment_output = fragment.fragment_record(record("fragment")).unwrap();
558        let fragment_traces = fragment_output.records()[0].metadata().transforms();
559        assert_eq!(fragment_traces.len(), 1);
560        assert_eq!(fragment_traces[0].name(), "ip-fragment");
561        assert_eq!(fragment_traces[0].note(), Some("passthrough"));
562    }
563}