1use core::time::Duration;
4
5use crate::{CrafterError, Result};
6
7pub const IP_DEFRAG_DEFAULT_MAX_DATAGRAMS: usize = 1024;
9
10pub const IP_DEFRAG_DEFAULT_MAX_BYTES_PER_DATAGRAM: usize = 65_535;
12
13pub const IP_DEFRAG_DEFAULT_MAX_AGE: Duration = Duration::from_secs(60);
15
16pub const IP_FRAGMENT_MIN_MTU: usize = 28;
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
21pub enum IpDefragOverlapPolicy {
22 #[default]
24 RejectConflicting,
25 DropConflicting,
27 PassThroughConflicting,
29}
30
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
33pub enum Ipv6AtomicFragmentPolicy {
34 PassThrough,
36 #[default]
38 Normalize,
39 Drop,
41}
42
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
45pub enum Ipv4DontFragmentPolicy {
46 #[default]
48 Error,
49 PassThrough,
51 FragmentAnyway,
53}
54
55#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
57pub enum Ipv4FragmentIdentificationPolicy {
58 #[default]
60 PreserveOrGenerate,
61 PreserveOnly,
63 Fixed(u16),
65}
66
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
69pub enum Ipv6FragmentIdentificationPolicy {
70 #[default]
72 Generate,
73 Fixed(u32),
75}
76
77#[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 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 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 pub const fn emits_non_fragments(&self) -> bool {
113 self.pass_non_fragments
114 }
115
116 pub const fn max_datagrams(mut self, max_datagrams: usize) -> Self {
118 self.max_datagrams = max_datagrams;
119 self
120 }
121
122 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 pub const fn max_datagrams_limit(&self) -> usize {
130 self.max_datagrams
131 }
132
133 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 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 pub const fn max_bytes_per_datagram_limit(&self) -> usize {
147 self.max_bytes_per_datagram
148 }
149
150 pub const fn max_age(mut self, max_age: Duration) -> Self {
152 self.max_age = max_age;
153 self
154 }
155
156 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 pub const fn max_age_limit(&self) -> Duration {
164 self.max_age
165 }
166
167 pub const fn overlap_policy(mut self, overlap_policy: IpDefragOverlapPolicy) -> Self {
169 self.overlap_policy = overlap_policy;
170 self
171 }
172
173 pub const fn configured_overlap_policy(&self) -> IpDefragOverlapPolicy {
175 self.overlap_policy
176 }
177
178 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 pub const fn ipv6_atomic_fragment_policy(&self) -> Ipv6AtomicFragmentPolicy {
189 self.ipv6_atomic_fragment_policy
190 }
191
192 pub const fn trace_passthrough(mut self, trace_passthrough: bool) -> Self {
194 self.trace_passthrough = trace_passthrough;
195 self
196 }
197
198 pub const fn traces_passthrough(&self) -> bool {
200 self.trace_passthrough
201 }
202
203 pub const fn trace_evictions(mut self, trace_evictions: bool) -> Self {
205 self.trace_evictions = trace_evictions;
206 self
207 }
208
209 pub const fn traces_evictions(&self) -> bool {
211 self.trace_evictions
212 }
213
214 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#[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 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 pub fn try_new(mtu: usize) -> Result<Self> {
258 validate_mtu(mtu)?;
259 Ok(Self::new(mtu))
260 }
261
262 pub const fn mtu(&self) -> usize {
264 self.mtu
265 }
266
267 pub const fn with_mtu(mut self, mtu: usize) -> Self {
269 self.mtu = mtu;
270 self
271 }
272
273 pub fn try_mtu(self, mtu: usize) -> Result<Self> {
275 validate_mtu(mtu)?;
276 Ok(self.with_mtu(mtu))
277 }
278
279 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 pub const fn honors_dont_fragment(&self) -> bool {
291 !matches!(
292 self.dont_fragment_policy,
293 Ipv4DontFragmentPolicy::FragmentAnyway
294 )
295 }
296
297 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 pub const fn configured_dont_fragment_policy(&self) -> Ipv4DontFragmentPolicy {
308 self.dont_fragment_policy
309 }
310
311 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 pub const fn configured_ipv4_identification_policy(&self) -> Ipv4FragmentIdentificationPolicy {
322 self.ipv4_identification_policy
323 }
324
325 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 pub const fn ipv6_identification(mut self, identification: u32) -> Self {
336 self.ipv6_identification_policy = Ipv6FragmentIdentificationPolicy::Fixed(identification);
337 self
338 }
339
340 pub const fn configured_ipv6_identification_policy(&self) -> Ipv6FragmentIdentificationPolicy {
342 self.ipv6_identification_policy
343 }
344
345 pub const fn ipv6_identification_seed(mut self, seed: u64) -> Self {
347 self.ipv6_identification_seed = Some(seed);
348 self
349 }
350
351 pub const fn clear_ipv6_identification_seed(mut self) -> Self {
353 self.ipv6_identification_seed = None;
354 self
355 }
356
357 pub const fn configured_ipv6_identification_seed(&self) -> Option<u64> {
359 self.ipv6_identification_seed
360 }
361
362 pub const fn trace_passthrough(mut self, trace_passthrough: bool) -> Self {
364 self.trace_passthrough = trace_passthrough;
365 self
366 }
367
368 pub const fn traces_passthrough(&self) -> bool {
370 self.trace_passthrough
371 }
372
373 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}