1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
33
34use thiserror::Error;
35
36const ET_IPV4: u16 = 0x0800;
39const ET_IPV6: u16 = 0x86DD;
40const ET_VLAN: u16 = 0x8100;
41
42const PROTO_TCP: u8 = 6;
43const PROTO_UDP: u8 = 17;
44
45#[derive(Debug, Error)]
49pub enum TransformError {
50 #[error("invalid IP mapping '{0}': expected OLD_IP=NEW_IP")]
51 InvalidMapping(String),
52}
53
54#[derive(Debug, Clone)]
56pub struct IpMapping {
57 pub old: IpAddr,
59 pub new: IpAddr,
61}
62
63#[derive(Debug, Clone)]
69pub struct ProtocolTruncation {
70 pub proto: u8,
72 pub max_payload_bytes: u32,
74}
75
76#[derive(Debug, Default, Clone)]
78pub struct TransformOptions {
79 pub max_payload_bytes: Option<u32>,
84 pub timestamp_start_ns: Option<u64>,
89 pub ip_map: Vec<IpMapping>,
91 pub proto_truncation: Vec<ProtocolTruncation>,
97}
98
99impl TransformOptions {
100 pub fn is_empty(&self) -> bool {
102 self.max_payload_bytes.is_none()
103 && self.timestamp_start_ns.is_none()
104 && self.ip_map.is_empty()
105 && self.proto_truncation.is_empty()
106 }
107}
108
109pub fn parse_ip_mapping(s: &str) -> Result<IpMapping, TransformError> {
120 let (old_s, new_s) = s
121 .split_once('=')
122 .ok_or_else(|| TransformError::InvalidMapping(s.to_owned()))?;
123
124 let old: IpAddr = old_s
125 .trim()
126 .parse()
127 .map_err(|_| TransformError::InvalidMapping(s.to_owned()))?;
128 let new: IpAddr = new_s
129 .trim()
130 .parse()
131 .map_err(|_| TransformError::InvalidMapping(s.to_owned()))?;
132
133 Ok(IpMapping { old, new })
134}
135
136pub fn apply(
145 data: &mut Vec<u8>,
146 timestamp_ns: u64,
147 ts_delta: i64,
148 origlen: u32,
149 opts: &TransformOptions,
150) -> (u64, u32, u32) {
151 let new_ts = if ts_delta != 0 {
152 (timestamp_ns as i64).saturating_add(ts_delta).max(0) as u64
153 } else {
154 timestamp_ns
155 };
156
157 let (ip_changed, reframed) = if !opts.ip_map.is_empty() {
158 apply_ip_map(data, &opts.ip_map)
159 } else {
160 (false, false)
161 };
162
163 let origlen = if reframed { data.len() as u32 } else { origlen };
166
167 let (new_origlen, truncated) = match effective_truncation_limit(data, opts) {
168 Some(max_bytes) => do_truncate(data, max_bytes, origlen),
169 None => (origlen, false),
170 };
171
172 if ip_changed || truncated {
173 recalculate_checksums(data);
174 }
175
176 let new_caplen = data.len() as u32;
177 (new_ts, new_caplen, new_origlen)
178}
179
180fn detect_protocol(data: &[u8]) -> Option<u8> {
184 let (ip_off, et) = find_ip(data)?;
185 match et {
186 ET_IPV4 => {
187 if data.len() >= ip_off + 20 {
188 Some(data[ip_off + 9])
189 } else {
190 None
191 }
192 }
193 ET_IPV6 => {
194 if data.len() >= ip_off + 40 {
195 Some(data[ip_off + 6])
196 } else {
197 None
198 }
199 }
200 _ => None,
201 }
202}
203
204fn effective_truncation_limit(data: &[u8], opts: &TransformOptions) -> Option<u32> {
209 if !opts.proto_truncation.is_empty()
210 && let Some(proto) = detect_protocol(data)
211 {
212 for rule in &opts.proto_truncation {
213 if rule.proto == proto {
214 return Some(rule.max_payload_bytes);
215 }
216 }
217 }
218 opts.max_payload_bytes
219}
220
221fn find_ip(data: &[u8]) -> Option<(usize, u16)> {
228 if data.len() < 14 {
229 return None;
230 }
231 let et = u16::from_be_bytes([data[12], data[13]]);
232 if et == ET_VLAN {
233 if data.len() < 18 {
234 return None;
235 }
236 let inner = u16::from_be_bytes([data[16], data[17]]);
237 if inner == ET_IPV4 || inner == ET_IPV6 {
238 Some((18, inner))
239 } else {
240 None
241 }
242 } else if et == ET_IPV4 || et == ET_IPV6 {
243 Some((14, et))
244 } else {
245 None
246 }
247}
248
249fn apply_ip_map(data: &mut Vec<u8>, mappings: &[IpMapping]) -> (bool, bool) {
258 let Some((ip_off, et)) = find_ip(data) else {
259 return (false, false);
260 };
261
262 let has_cross = mappings.iter().any(|m| {
264 matches!(
265 (&m.old, &m.new),
266 (IpAddr::V4(_), IpAddr::V6(_)) | (IpAddr::V6(_), IpAddr::V4(_))
267 )
268 });
269
270 if has_cross {
271 if et == ET_IPV4 && needs_v4_to_v6_reframe(data, ip_off, mappings) {
272 if let Some(new_data) = reframe_ipv4_to_ipv6(data, ip_off, mappings) {
273 *data = new_data;
274 return (true, true);
275 }
276 } else if et == ET_IPV6
277 && needs_v6_to_v4_reframe(data, ip_off, mappings)
278 && let Some(new_data) = reframe_ipv6_to_ipv4(data, ip_off, mappings)
279 {
280 *data = new_data;
281 return (true, true);
282 }
283 }
284
285 let mut changed = false;
287
288 if et == ET_IPV4 {
289 if data.len() < ip_off + 20 {
290 return (false, false);
291 }
292 for m in mappings {
293 if let (IpAddr::V4(old_v4), IpAddr::V4(new_v4)) = (&m.old, &m.new) {
294 let old_b = old_v4.octets();
295 let new_b = new_v4.octets();
296 if data[ip_off + 12..ip_off + 16] == old_b {
298 data[ip_off + 12..ip_off + 16].copy_from_slice(&new_b);
299 changed = true;
300 }
301 if data[ip_off + 16..ip_off + 20] == old_b {
303 data[ip_off + 16..ip_off + 20].copy_from_slice(&new_b);
304 changed = true;
305 }
306 }
307 }
308 } else if et == ET_IPV6 {
309 if data.len() < ip_off + 40 {
310 return (false, false);
311 }
312 for m in mappings {
313 if let (IpAddr::V6(old_v6), IpAddr::V6(new_v6)) = (&m.old, &m.new) {
314 let old_b = old_v6.octets();
315 let new_b = new_v6.octets();
316 if data[ip_off + 8..ip_off + 24] == old_b {
318 data[ip_off + 8..ip_off + 24].copy_from_slice(&new_b);
319 changed = true;
320 }
321 if data[ip_off + 24..ip_off + 40] == old_b {
323 data[ip_off + 24..ip_off + 40].copy_from_slice(&new_b);
324 changed = true;
325 }
326 }
327 }
328 }
329
330 (changed, false)
331}
332
333fn needs_v4_to_v6_reframe(data: &[u8], ip_off: usize, mappings: &[IpMapping]) -> bool {
337 if data.len() < ip_off + 20 {
338 return false;
339 }
340 let src = &data[ip_off + 12..ip_off + 16];
341 let dst = &data[ip_off + 16..ip_off + 20];
342 mappings.iter().any(|m| {
343 if let (IpAddr::V4(old), IpAddr::V6(_)) = (&m.old, &m.new) {
344 let b = old.octets();
345 src == b || dst == b
346 } else {
347 false
348 }
349 })
350}
351
352fn needs_v6_to_v4_reframe(data: &[u8], ip_off: usize, mappings: &[IpMapping]) -> bool {
354 if data.len() < ip_off + 40 {
355 return false;
356 }
357 let src = &data[ip_off + 8..ip_off + 24];
358 let dst = &data[ip_off + 24..ip_off + 40];
359 mappings.iter().any(|m| {
360 if let (IpAddr::V6(old), IpAddr::V4(_)) = (&m.old, &m.new) {
361 let b = old.octets();
362 src == b || dst == b
363 } else {
364 false
365 }
366 })
367}
368
369fn reframe_ipv4_to_ipv6(data: &[u8], ip_off: usize, mappings: &[IpMapping]) -> Option<Vec<u8>> {
376 if data.len() < ip_off + 20 {
377 return None;
378 }
379 let ihl = ((data[ip_off] & 0x0F) * 4) as usize;
380 if data.len() < ip_off + ihl {
381 return None;
382 }
383
384 let ttl = data[ip_off + 8];
385 let proto = data[ip_off + 9];
386 let src_v4 = Ipv4Addr::from(<[u8; 4]>::try_from(&data[ip_off + 12..ip_off + 16]).ok()?);
387 let dst_v4 = Ipv4Addr::from(<[u8; 4]>::try_from(&data[ip_off + 16..ip_off + 20]).ok()?);
388
389 let src_v6 = resolve_ipv4_to_ipv6(src_v4, mappings);
390 let dst_v6 = resolve_ipv4_to_ipv6(dst_v4, mappings);
391
392 let transport = &data[ip_off + ihl..];
393 let payload_len = transport.len() as u16;
394
395 let mut out = Vec::with_capacity(ip_off + 40 + transport.len());
396 write_ethernet_preamble(&mut out, data, ip_off, ET_IPV6);
397
398 out.extend_from_slice(&[0x60, 0x00, 0x00, 0x00]); out.extend_from_slice(&payload_len.to_be_bytes());
401 out.push(proto); out.push(ttl); out.extend_from_slice(&src_v6);
404 out.extend_from_slice(&dst_v6);
405
406 out.extend_from_slice(transport);
408
409 Some(out)
410}
411
412fn reframe_ipv6_to_ipv4(data: &[u8], ip_off: usize, mappings: &[IpMapping]) -> Option<Vec<u8>> {
420 if data.len() < ip_off + 40 {
421 return None;
422 }
423
424 let hop_limit = data[ip_off + 7]; let proto = data[ip_off + 6]; let src_v6 = Ipv6Addr::from(<[u8; 16]>::try_from(&data[ip_off + 8..ip_off + 24]).ok()?);
427 let dst_v6 = Ipv6Addr::from(<[u8; 16]>::try_from(&data[ip_off + 24..ip_off + 40]).ok()?);
428
429 let src_v4 = resolve_ipv6_to_ipv4(src_v6, mappings)?;
431 let dst_v4 = resolve_ipv6_to_ipv4(dst_v6, mappings)?;
432
433 let transport = &data[ip_off + 40..];
434 let ip_total = (20u16).saturating_add(transport.len() as u16);
435
436 let mut out = Vec::with_capacity(ip_off + 20 + transport.len());
437 write_ethernet_preamble(&mut out, data, ip_off, ET_IPV4);
438
439 out.push(0x45); out.push(0x00); out.extend_from_slice(&ip_total.to_be_bytes());
443 out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&[0x40, 0x00]); out.push(hop_limit); out.push(proto); out.extend_from_slice(&[0x00, 0x00]); out.extend_from_slice(&src_v4);
449 out.extend_from_slice(&dst_v4);
450
451 out.extend_from_slice(transport);
453
454 Some(out)
455}
456
457fn write_ethernet_preamble(out: &mut Vec<u8>, src: &[u8], ip_off: usize, ethertype: u16) {
461 out.extend_from_slice(&src[0..12]); if ip_off == 18 {
463 out.extend_from_slice(&src[12..16]); out.extend_from_slice(ðertype.to_be_bytes()); } else {
466 out.extend_from_slice(ðertype.to_be_bytes());
467 }
468}
469
470fn resolve_ipv4_to_ipv6(addr: Ipv4Addr, mappings: &[IpMapping]) -> [u8; 16] {
477 for m in mappings {
478 match (&m.old, &m.new) {
479 (IpAddr::V4(old), IpAddr::V6(new)) if *old == addr => return new.octets(),
480 (IpAddr::V4(old), IpAddr::V4(new)) if *old == addr => {
481 return ipv4_mapped_to_ipv6(new.octets());
482 }
483 _ => {}
484 }
485 }
486 ipv4_mapped_to_ipv6(addr.octets())
487}
488
489fn resolve_ipv6_to_ipv4(addr: Ipv6Addr, mappings: &[IpMapping]) -> Option<[u8; 4]> {
498 for m in mappings {
499 match (&m.old, &m.new) {
500 (IpAddr::V6(old), IpAddr::V4(new)) if *old == addr => return Some(new.octets()),
501 (IpAddr::V6(old), IpAddr::V6(new)) if *old == addr => {
502 return new.to_ipv4_mapped().map(|v4| v4.octets());
503 }
504 _ => {}
505 }
506 }
507 addr.to_ipv4_mapped().map(|v4| v4.octets())
508}
509
510fn ipv4_mapped_to_ipv6(v4: [u8; 4]) -> [u8; 16] {
512 let mut v6 = [0u8; 16];
513 v6[10] = 0xFF;
514 v6[11] = 0xFF;
515 v6[12..16].copy_from_slice(&v4);
516 v6
517}
518
519fn do_truncate(data: &mut Vec<u8>, max_bytes: u32, origlen: u32) -> (u32, bool) {
525 let Some((ip_off, et)) = find_ip(data) else {
526 return (origlen, false);
527 };
528
529 let (proto, transport_start, transport_hdr_len) = match et {
530 ET_IPV4 => {
531 if data.len() < ip_off + 20 {
532 return (origlen, false);
533 }
534 let ihl = ((data[ip_off] & 0x0F) * 4) as usize;
535 if data.len() < ip_off + ihl {
536 return (origlen, false);
537 }
538 let proto = data[ip_off + 9];
539 let ts = ip_off + ihl;
540 let th = match proto {
541 PROTO_TCP => {
542 if data.len() < ts + 20 {
543 return (origlen, false);
544 }
545 let th = ((data[ts + 12] >> 4) * 4) as usize;
546 if th < 20 {
547 return (origlen, false);
548 }
549 th
550 }
551 PROTO_UDP => 8,
552 _ => return (origlen, false),
553 };
554 (proto, ts, th)
555 }
556 ET_IPV6 => {
557 if data.len() < ip_off + 40 {
558 return (origlen, false);
559 }
560 let proto = data[ip_off + 6]; let ts = ip_off + 40;
562 let th = match proto {
563 PROTO_TCP => {
564 if data.len() < ts + 20 {
565 return (origlen, false);
566 }
567 let th = ((data[ts + 12] >> 4) * 4) as usize;
568 if th < 20 {
569 return (origlen, false);
570 }
571 th
572 }
573 PROTO_UDP => 8,
574 _ => return (origlen, false),
575 };
576 (proto, ts, th)
577 }
578 _ => return (origlen, false),
579 };
580
581 let payload_start = transport_start + transport_hdr_len;
582 let max_total = payload_start + max_bytes as usize;
583
584 if data.len() <= max_total {
585 return (origlen, false); }
587
588 data.truncate(max_total);
589 let new_len = data.len();
590
591 if et == ET_IPV4 {
593 let new_ip_total = (new_len - ip_off) as u16;
594 data[ip_off + 2..ip_off + 4].copy_from_slice(&new_ip_total.to_be_bytes());
595 } else {
596 let new_plen = (new_len - ip_off - 40) as u16;
598 data[ip_off + 4..ip_off + 6].copy_from_slice(&new_plen.to_be_bytes());
599 }
600
601 if proto == PROTO_UDP {
603 let new_udp_len = (new_len - transport_start) as u16;
604 data[transport_start + 4..transport_start + 6].copy_from_slice(&new_udp_len.to_be_bytes());
605 }
606
607 (new_len as u32, true)
608}
609
610fn recalculate_checksums(data: &mut [u8]) {
614 let Some((ip_off, et)) = find_ip(data) else {
615 return;
616 };
617 match et {
618 ET_IPV4 => recalc_ipv4(data, ip_off),
619 ET_IPV6 => recalc_ipv6(data, ip_off),
620 _ => {}
621 }
622}
623
624fn recalc_ipv4(data: &mut [u8], ip_off: usize) {
625 if data.len() < ip_off + 20 {
626 return;
627 }
628 let ihl = ((data[ip_off] & 0x0F) * 4) as usize;
629 if data.len() < ip_off + ihl {
630 return;
631 }
632
633 data[ip_off + 10] = 0;
635 data[ip_off + 11] = 0;
636 let csum = internet_checksum(&data[ip_off..ip_off + ihl]);
637 data[ip_off + 10..ip_off + 12].copy_from_slice(&csum.to_be_bytes());
638
639 let proto = data[ip_off + 9];
640 let ts = ip_off + ihl;
641 if proto == PROTO_TCP || proto == PROTO_UDP {
642 recalc_transport_v4(data, ip_off, ts, proto);
643 }
644}
645
646fn recalc_transport_v4(data: &mut [u8], ip_off: usize, ts: usize, proto: u8) {
647 let csum_off = if proto == PROTO_TCP { ts + 16 } else { ts + 6 };
648 if data.len() < csum_off + 2 {
649 return;
650 }
651 let src: [u8; 4] = data[ip_off + 12..ip_off + 16].try_into().unwrap();
652 let dst: [u8; 4] = data[ip_off + 16..ip_off + 20].try_into().unwrap();
653 data[csum_off] = 0;
654 data[csum_off + 1] = 0;
655 let csum = transport_checksum_v4(src, dst, proto, &data[ts..]);
656 data[csum_off..csum_off + 2].copy_from_slice(&csum.to_be_bytes());
657}
658
659fn recalc_ipv6(data: &mut [u8], ip_off: usize) {
660 if data.len() < ip_off + 40 {
661 return;
662 }
663 let proto = data[ip_off + 6]; let ts = ip_off + 40;
665 if proto == PROTO_TCP || proto == PROTO_UDP {
666 recalc_transport_v6(data, ip_off, ts, proto);
667 }
668}
669
670fn recalc_transport_v6(data: &mut [u8], ip_off: usize, ts: usize, proto: u8) {
671 let csum_off = if proto == PROTO_TCP { ts + 16 } else { ts + 6 };
672 if data.len() < csum_off + 2 {
673 return;
674 }
675 let src: [u8; 16] = data[ip_off + 8..ip_off + 24].try_into().unwrap();
676 let dst: [u8; 16] = data[ip_off + 24..ip_off + 40].try_into().unwrap();
677 data[csum_off] = 0;
678 data[csum_off + 1] = 0;
679 let csum = transport_checksum_v6(src, dst, proto, &data[ts..]);
680 data[csum_off..csum_off + 2].copy_from_slice(&csum.to_be_bytes());
681}
682
683fn internet_checksum(data: &[u8]) -> u16 {
687 let mut sum: u32 = 0;
688 let mut iter = data.chunks_exact(2);
689 for chunk in &mut iter {
690 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
691 }
692 if let [byte] = iter.remainder() {
693 sum += (*byte as u32) << 8;
694 }
695 while sum >> 16 != 0 {
696 sum = (sum & 0xFFFF) + (sum >> 16);
697 }
698 !(sum as u16)
699}
700
701fn transport_checksum_v4(src: [u8; 4], dst: [u8; 4], proto: u8, segment: &[u8]) -> u16 {
703 let len = segment.len() as u32;
704 let mut sum: u32 = 0;
705 sum += u16::from_be_bytes([src[0], src[1]]) as u32;
707 sum += u16::from_be_bytes([src[2], src[3]]) as u32;
708 sum += u16::from_be_bytes([dst[0], dst[1]]) as u32;
709 sum += u16::from_be_bytes([dst[2], dst[3]]) as u32;
710 sum += proto as u32;
711 sum += len & 0xFFFF;
712 let mut iter = segment.chunks_exact(2);
713 for chunk in &mut iter {
714 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
715 }
716 if let [byte] = iter.remainder() {
717 sum += (*byte as u32) << 8;
718 }
719 while sum >> 16 != 0 {
720 sum = (sum & 0xFFFF) + (sum >> 16);
721 }
722 !(sum as u16)
723}
724
725fn transport_checksum_v6(src: [u8; 16], dst: [u8; 16], proto: u8, segment: &[u8]) -> u16 {
727 let len = segment.len() as u32;
728 let mut sum: u32 = 0;
729 for i in (0..16).step_by(2) {
731 sum += u16::from_be_bytes([src[i], src[i + 1]]) as u32;
732 }
733 for i in (0..16).step_by(2) {
734 sum += u16::from_be_bytes([dst[i], dst[i + 1]]) as u32;
735 }
736 sum += len >> 16;
738 sum += len & 0xFFFF;
739 sum += proto as u32;
741 let mut iter = segment.chunks_exact(2);
742 for chunk in &mut iter {
743 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
744 }
745 if let [byte] = iter.remainder() {
746 sum += (*byte as u32) << 8;
747 }
748 while sum >> 16 != 0 {
749 sum = (sum & 0xFFFF) + (sum >> 16);
750 }
751 !(sum as u16)
752}
753
754#[cfg(test)]
757mod tests {
758 use super::*;
759
760 fn eth_ipv4_udp(src: [u8; 4], dst: [u8; 4], sport: u16, dport: u16, payload: &[u8]) -> Vec<u8> {
763 let udp_len = (8 + payload.len()) as u16;
764 let ip_total = 20 + udp_len;
765 let mut f = Vec::new();
766 f.extend_from_slice(&[0xFF; 6]); f.extend_from_slice(&[0x00; 6]); f.extend_from_slice(&[0x08, 0x00]); f.push(0x45); f.push(0x00); f.extend_from_slice(&ip_total.to_be_bytes());
772 f.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]); f.push(64);
774 f.push(17); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(&src);
777 f.extend_from_slice(&dst);
778 f.extend_from_slice(&sport.to_be_bytes());
779 f.extend_from_slice(&dport.to_be_bytes());
780 f.extend_from_slice(&udp_len.to_be_bytes());
781 f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(payload);
783 f
784 }
785
786 fn eth_ipv4_tcp(src: [u8; 4], dst: [u8; 4], sport: u16, dport: u16, payload: &[u8]) -> Vec<u8> {
787 let ip_total = (20 + 20 + payload.len()) as u16;
788 let mut f = Vec::new();
789 f.extend_from_slice(&[0xFF; 6]);
790 f.extend_from_slice(&[0x00; 6]);
791 f.extend_from_slice(&[0x08, 0x00]);
792 f.push(0x45);
793 f.push(0x00);
794 f.extend_from_slice(&ip_total.to_be_bytes());
795 f.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]);
796 f.push(64);
797 f.push(6); f.extend_from_slice(&[0x00, 0x00]);
799 f.extend_from_slice(&src);
800 f.extend_from_slice(&dst);
801 f.extend_from_slice(&sport.to_be_bytes());
803 f.extend_from_slice(&dport.to_be_bytes());
804 f.extend_from_slice(&[0x00; 4]); f.extend_from_slice(&[0x00; 4]); f.push(0x50); f.push(0x02); f.extend_from_slice(&[0xFF, 0xFF]); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(payload);
812 f
813 }
814
815 fn eth_ipv6_udp(
817 src: [u8; 16],
818 dst: [u8; 16],
819 sport: u16,
820 dport: u16,
821 payload: &[u8],
822 ) -> Vec<u8> {
823 let udp_len = (8 + payload.len()) as u16;
824 let payload_len = udp_len; let mut f = Vec::new();
826 f.extend_from_slice(&[0xFF; 6]); f.extend_from_slice(&[0x00; 6]); f.extend_from_slice(&[0x86, 0xDD]); f.extend_from_slice(&[0x60, 0x00, 0x00, 0x00]); f.extend_from_slice(&payload_len.to_be_bytes());
831 f.push(17); f.push(64); f.extend_from_slice(&src);
834 f.extend_from_slice(&dst);
835 f.extend_from_slice(&sport.to_be_bytes());
836 f.extend_from_slice(&dport.to_be_bytes());
837 f.extend_from_slice(&udp_len.to_be_bytes());
838 f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(payload);
840 f
841 }
842
843 #[test]
846 fn test_parse_ip_mapping_valid_v4() {
847 let m = parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap();
848 assert_eq!(m.old, "10.0.0.1".parse::<IpAddr>().unwrap());
849 assert_eq!(m.new, "192.168.1.1".parse::<IpAddr>().unwrap());
850 }
851
852 #[test]
853 fn test_parse_ip_mapping_valid_v6() {
854 let m = parse_ip_mapping("::1=::2").unwrap();
855 assert_eq!(m.old, "::1".parse::<IpAddr>().unwrap());
856 assert_eq!(m.new, "::2".parse::<IpAddr>().unwrap());
857 }
858
859 #[test]
860 fn test_parse_ip_mapping_cross_family_v4_to_v6() {
861 let m = parse_ip_mapping("10.0.0.1=::1").unwrap();
863 assert_eq!(m.old, "10.0.0.1".parse::<IpAddr>().unwrap());
864 assert_eq!(m.new, "::1".parse::<IpAddr>().unwrap());
865 }
866
867 #[test]
868 fn test_parse_ip_mapping_cross_family_v6_to_v4() {
869 let m = parse_ip_mapping("2001:db8::1=192.168.1.1").unwrap();
870 assert_eq!(m.old, "2001:db8::1".parse::<IpAddr>().unwrap());
871 assert_eq!(m.new, "192.168.1.1".parse::<IpAddr>().unwrap());
872 }
873
874 #[test]
875 fn test_parse_ip_mapping_no_equals() {
876 assert!(parse_ip_mapping("10.0.0.1").is_err());
877 }
878
879 #[test]
880 fn test_parse_ip_mapping_invalid_ip() {
881 assert!(parse_ip_mapping("notanip=192.168.1.1").is_err());
882 }
883
884 #[test]
887 fn test_internet_checksum_all_zeros() {
888 assert_eq!(internet_checksum(&[0u8; 20]), 0xFFFF);
890 }
891
892 #[test]
893 fn test_internet_checksum_verify_roundtrip() {
894 let mut header: [u8; 20] = [
898 0x45, 0x00, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x08, 0x08, 0x08, 0x08, ];
904 let csum = internet_checksum(&header);
905 header[10] = (csum >> 8) as u8;
906 header[11] = (csum & 0xFF) as u8;
907 assert_eq!(internet_checksum(&header), 0x0000);
909 }
910
911 #[test]
914 fn test_timestamp_shift_positive() {
915 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[]);
916 let origlen = data.len() as u32;
917 let (new_ts, _, _) = apply(
918 &mut data,
919 1_000_000_000,
920 500_000_000,
921 origlen,
922 &TransformOptions::default(),
923 );
924 assert_eq!(new_ts, 1_500_000_000);
925 }
926
927 #[test]
928 fn test_timestamp_shift_clamped_to_zero() {
929 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[]);
930 let origlen = data.len() as u32;
931 let (new_ts, _, _) = apply(&mut data, 100, -200, origlen, &TransformOptions::default());
933 assert_eq!(new_ts, 0);
934 }
935
936 #[test]
939 fn test_no_transform_leaves_data_unchanged() {
940 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[0xAA; 10]);
941 let original = data.clone();
942 let origlen = data.len() as u32;
943 let (new_ts, new_caplen, new_origlen) =
944 apply(&mut data, 42, 0, origlen, &TransformOptions::default());
945 assert_eq!(new_ts, 42);
946 assert_eq!(new_caplen, origlen);
947 assert_eq!(new_origlen, origlen);
948 assert_eq!(data, original);
949 }
950
951 #[test]
954 fn test_ip_mapping_replaces_src_ip() {
955 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0u8; 4]);
956 let origlen = data.len() as u32;
957 let opts = TransformOptions {
958 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap()],
959 ..Default::default()
960 };
961 apply(&mut data, 0, 0, origlen, &opts);
962 assert_eq!(&data[26..30], &[192, 168, 1, 1]);
964 assert_eq!(&data[30..34], &[8, 8, 8, 8]); }
966
967 #[test]
968 fn test_ip_mapping_replaces_dst_ip() {
969 let mut data = eth_ipv4_udp([1, 2, 3, 4], [10, 0, 0, 2], 1234, 53, &[0u8; 4]);
970 let origlen = data.len() as u32;
971 let opts = TransformOptions {
972 ip_map: vec![parse_ip_mapping("10.0.0.2=172.16.0.1").unwrap()],
973 ..Default::default()
974 };
975 apply(&mut data, 0, 0, origlen, &opts);
976 assert_eq!(&data[30..34], &[172, 16, 0, 1]);
977 }
978
979 #[test]
980 fn test_ip_mapping_updates_ipv4_header_checksum() {
981 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0u8; 4]);
982 let origlen = data.len() as u32;
983 let opts = TransformOptions {
984 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap()],
985 ..Default::default()
986 };
987 apply(&mut data, 0, 0, origlen, &opts);
988 let ihl = ((data[14] & 0x0F) * 4) as usize;
990 assert_eq!(
991 internet_checksum(&data[14..14 + ihl]),
992 0x0000,
993 "IPv4 header checksum must be valid after IP mapping"
994 );
995 }
996
997 #[test]
998 fn test_ip_mapping_no_match_leaves_data_unchanged() {
999 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[0xBB; 4]);
1000 let original = data.clone();
1001 let origlen = data.len() as u32;
1002 let opts = TransformOptions {
1004 ip_map: vec![parse_ip_mapping("10.0.0.99=10.0.0.1").unwrap()],
1005 ..Default::default()
1006 };
1007 apply(&mut data, 0, 0, origlen, &opts);
1008 assert_eq!(data, original);
1009 }
1010
1011 #[test]
1014 fn test_cross_family_ipv4_to_ipv6_src_mapped() {
1015 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0xAB; 4]);
1019 let origlen = data.len() as u32;
1020 let opts = TransformOptions {
1021 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1022 ..Default::default()
1023 };
1024 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1025
1026 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1028 assert_eq!(data[14] >> 4, 6);
1030 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1032 assert_eq!(src_v6, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
1033 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1035 assert_eq!(dst_v6, "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap());
1036 assert_eq!(&data[data.len() - 4..], &[0xAB; 4]);
1038 assert_eq!(new_caplen, data.len() as u32);
1040 assert_eq!(new_origlen, data.len() as u32);
1041 }
1042
1043 #[test]
1044 fn test_cross_family_ipv4_to_ipv6_dst_mapped() {
1045 let mut data = eth_ipv4_udp([1, 2, 3, 4], [10, 0, 0, 2], 5000, 80, &[]);
1049 let origlen = data.len() as u32;
1050 let opts = TransformOptions {
1051 ip_map: vec![parse_ip_mapping("10.0.0.2=::1").unwrap()],
1052 ..Default::default()
1053 };
1054 apply(&mut data, 0, 0, origlen, &opts);
1055
1056 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1057 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1058 assert_eq!(src_v6, "::ffff:1.2.3.4".parse::<Ipv6Addr>().unwrap());
1059 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1060 assert_eq!(dst_v6, "::1".parse::<Ipv6Addr>().unwrap());
1061 }
1062
1063 #[test]
1064 fn test_cross_family_ipv4_to_ipv6_both_mapped() {
1065 let mut data = eth_ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 1000, 2000, &[0xCC; 8]);
1067 let origlen = data.len() as u32;
1068 let opts = TransformOptions {
1069 ip_map: vec![
1070 parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap(),
1071 parse_ip_mapping("10.0.0.2=2001:db8::2").unwrap(),
1072 ],
1073 ..Default::default()
1074 };
1075 apply(&mut data, 0, 0, origlen, &opts);
1076
1077 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1078 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1079 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1080 assert_eq!(src_v6, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
1081 assert_eq!(dst_v6, "2001:db8::2".parse::<Ipv6Addr>().unwrap());
1082 }
1083
1084 #[test]
1085 fn test_cross_family_ipv4_to_ipv6_checksum_valid() {
1086 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0xDE; 16]);
1087 let origlen = data.len() as u32;
1088 let opts = TransformOptions {
1089 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1090 ..Default::default()
1091 };
1092 apply(&mut data, 0, 0, origlen, &opts);
1093
1094 let udp_csum = u16::from_be_bytes([data[54 + 6], data[54 + 7]]);
1097 assert_ne!(udp_csum, 0, "UDP checksum must be set in IPv6 packet");
1098 }
1099
1100 #[test]
1101 fn test_cross_family_ipv4_to_ipv6_size_change() {
1102 let payload = [0u8; 10];
1104 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1000, 2000, &payload);
1105 let ipv4_len = data.len();
1106 let origlen = data.len() as u32;
1107 let opts = TransformOptions {
1108 ip_map: vec![parse_ip_mapping("10.0.0.1=::1").unwrap()],
1109 ..Default::default()
1110 };
1111 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1112
1113 assert_eq!(data.len(), ipv4_len + 20);
1116 assert_eq!(new_caplen, data.len() as u32);
1117 assert_eq!(new_origlen, data.len() as u32);
1118 }
1119
1120 #[test]
1123 fn test_cross_family_ipv6_to_ipv4_src_mapped() {
1124 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1127 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1128 let mut data = eth_ipv6_udp(src_v6, dst_v6, 5000, 80, &[0xBB; 4]);
1129 let origlen = data.len() as u32;
1130 let opts = TransformOptions {
1131 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1132 ..Default::default()
1133 };
1134 apply(&mut data, 0, 0, origlen, &opts);
1135
1136 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1138 assert_eq!(data[14] >> 4, 4);
1139 assert_eq!(&data[26..30], &[10, 0, 0, 1]);
1141 assert_eq!(&data[30..34], &[8, 8, 8, 8]);
1143 assert_eq!(&data[data.len() - 4..], &[0xBB; 4]);
1145 }
1146
1147 #[test]
1148 fn test_cross_family_ipv6_to_ipv4_dst_mapped() {
1149 let src_v6: [u8; 16] = "::ffff:1.2.3.4".parse::<Ipv6Addr>().unwrap().octets();
1152 let dst_v6: [u8; 16] = "2001:db8::2".parse::<Ipv6Addr>().unwrap().octets();
1153 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1234, 443, &[]);
1154 let origlen = data.len() as u32;
1155 let opts = TransformOptions {
1156 ip_map: vec![parse_ip_mapping("2001:db8::2=192.168.1.2").unwrap()],
1157 ..Default::default()
1158 };
1159 apply(&mut data, 0, 0, origlen, &opts);
1160
1161 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1162 assert_eq!(&data[26..30], &[1, 2, 3, 4]); assert_eq!(&data[30..34], &[192, 168, 1, 2]);
1164 }
1165
1166 #[test]
1167 fn test_cross_family_ipv6_to_ipv4_skipped_when_non_mapped_addr() {
1168 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1171 let dst_v6: [u8; 16] = "2001:db8::2".parse::<Ipv6Addr>().unwrap().octets();
1172 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1000, 2000, &[0xFF; 4]);
1173 let original = data.clone();
1174 let origlen = data.len() as u32;
1175 let opts = TransformOptions {
1177 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1178 ..Default::default()
1179 };
1180 apply(&mut data, 0, 0, origlen, &opts);
1181 assert_eq!(data, original);
1183 }
1184
1185 #[test]
1186 fn test_cross_family_ipv6_to_ipv4_checksum_valid() {
1187 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1188 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1189 let mut data = eth_ipv6_udp(src_v6, dst_v6, 5000, 53, &[0xDE; 8]);
1190 let origlen = data.len() as u32;
1191 let opts = TransformOptions {
1192 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1193 ..Default::default()
1194 };
1195 apply(&mut data, 0, 0, origlen, &opts);
1196
1197 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1198 let ihl = ((data[14] & 0x0F) * 4) as usize;
1199 assert_eq!(
1200 internet_checksum(&data[14..14 + ihl]),
1201 0x0000,
1202 "IPv4 header checksum must be valid after v6→v4 reframe"
1203 );
1204 }
1205
1206 #[test]
1207 fn test_cross_family_ipv6_to_ipv4_size_change() {
1208 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1210 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1211 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1000, 2000, &[0u8; 10]);
1212 let ipv6_len = data.len();
1213 let origlen = data.len() as u32;
1214 let opts = TransformOptions {
1215 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1216 ..Default::default()
1217 };
1218 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1219 assert_eq!(data.len(), ipv6_len - 20);
1220 assert_eq!(new_caplen, data.len() as u32);
1221 assert_eq!(new_origlen, data.len() as u32);
1222 }
1223
1224 #[test]
1227 fn test_truncation_udp_updates_lengths() {
1228 let payload = vec![0xBB; 100];
1229 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1230 let orig = data.len() as u32;
1231 let opts = TransformOptions {
1232 max_payload_bytes: Some(10),
1233 ..Default::default()
1234 };
1235 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, orig, &opts);
1236
1237 assert_eq!(data.len(), 52);
1239 assert_eq!(new_caplen, 52);
1240 assert_eq!(new_origlen, 52);
1241
1242 let ip_total = u16::from_be_bytes([data[16], data[17]]);
1244 assert_eq!(ip_total, 38, "IPv4 total length not updated");
1245
1246 let udp_len = u16::from_be_bytes([data[38], data[39]]);
1249 assert_eq!(udp_len, 18, "UDP length not updated");
1250 }
1251
1252 #[test]
1253 fn test_truncation_tcp_updates_ip_length() {
1254 let payload = vec![0xCC; 50];
1255 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &payload);
1256 let orig = data.len() as u32;
1257 let opts = TransformOptions {
1258 max_payload_bytes: Some(5),
1259 ..Default::default()
1260 };
1261 apply(&mut data, 0, 0, orig, &opts);
1262
1263 assert_eq!(data.len(), 59);
1265 let ip_total = u16::from_be_bytes([data[16], data[17]]);
1267 assert_eq!(ip_total, 45);
1268 }
1269
1270 #[test]
1271 fn test_truncation_noop_when_short_enough() {
1272 let payload = vec![0xAA; 5];
1273 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1274 let original = data.clone();
1275 let orig = data.len() as u32;
1276 let opts = TransformOptions {
1277 max_payload_bytes: Some(100),
1278 ..Default::default()
1279 };
1280 apply(&mut data, 0, 0, orig, &opts);
1281 assert_eq!(data, original);
1282 }
1283
1284 #[test]
1285 fn test_truncation_checksums_valid() {
1286 let payload = vec![0xDE; 50];
1287 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1288 let orig = data.len() as u32;
1289 let opts = TransformOptions {
1290 max_payload_bytes: Some(8),
1291 ..Default::default()
1292 };
1293 apply(&mut data, 0, 0, orig, &opts);
1294 let ihl = ((data[14] & 0x0F) * 4) as usize;
1295 assert_eq!(
1296 internet_checksum(&data[14..14 + ihl]),
1297 0x0000,
1298 "IPv4 header checksum must be valid after truncation"
1299 );
1300 }
1301
1302 #[test]
1303 fn test_truncation_zero_payload_bytes() {
1304 let payload = vec![0xFF; 20];
1306 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1307 let orig = data.len() as u32;
1308 let opts = TransformOptions {
1309 max_payload_bytes: Some(0),
1310 ..Default::default()
1311 };
1312 apply(&mut data, 0, 0, orig, &opts);
1313 assert_eq!(data.len(), 42);
1315 }
1316
1317 #[test]
1320 fn test_proto_truncation_tcp_uses_rule() {
1321 let payload = vec![0xAA; 100];
1322 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &payload);
1323 let orig = data.len() as u32;
1324 let opts = TransformOptions {
1325 proto_truncation: vec![ProtocolTruncation {
1326 proto: PROTO_TCP,
1327 max_payload_bytes: 10,
1328 }],
1329 ..Default::default()
1330 };
1331 apply(&mut data, 0, 0, orig, &opts);
1332 assert_eq!(data.len(), 64);
1334 }
1335
1336 #[test]
1337 fn test_proto_truncation_udp_uses_rule() {
1338 let payload = vec![0xBB; 80];
1339 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1340 let orig = data.len() as u32;
1341 let opts = TransformOptions {
1342 proto_truncation: vec![ProtocolTruncation {
1343 proto: PROTO_UDP,
1344 max_payload_bytes: 8,
1345 }],
1346 ..Default::default()
1347 };
1348 apply(&mut data, 0, 0, orig, &opts);
1349 assert_eq!(data.len(), 50);
1351 }
1352
1353 #[test]
1354 fn test_proto_truncation_overrides_global() {
1355 let payload = vec![0xCC; 100];
1357 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 80, &payload);
1358 let orig = data.len() as u32;
1359 let opts = TransformOptions {
1360 max_payload_bytes: Some(50),
1361 proto_truncation: vec![ProtocolTruncation {
1362 proto: PROTO_TCP,
1363 max_payload_bytes: 10,
1364 }],
1365 ..Default::default()
1366 };
1367 apply(&mut data, 0, 0, orig, &opts);
1368 assert_eq!(data.len(), 64);
1370 }
1371
1372 #[test]
1373 fn test_proto_truncation_fallback_to_global() {
1374 let payload = vec![0xDD; 80];
1376 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1377 let orig = data.len() as u32;
1378 let opts = TransformOptions {
1379 max_payload_bytes: Some(20),
1380 proto_truncation: vec![ProtocolTruncation {
1381 proto: PROTO_TCP, max_payload_bytes: 5,
1383 }],
1384 ..Default::default()
1385 };
1386 apply(&mut data, 0, 0, orig, &opts);
1387 assert_eq!(data.len(), 62);
1389 }
1390
1391 #[test]
1392 fn test_proto_truncation_no_match_no_global_no_truncation() {
1393 let payload = vec![0xEE; 50];
1395 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1396 let original = data.clone();
1397 let orig = data.len() as u32;
1398 let opts = TransformOptions {
1399 proto_truncation: vec![ProtocolTruncation {
1400 proto: PROTO_TCP,
1401 max_payload_bytes: 10,
1402 }],
1403 ..Default::default()
1404 };
1405 apply(&mut data, 0, 0, orig, &opts);
1406 assert_eq!(data, original);
1407 }
1408
1409 #[test]
1412 fn test_ip_mapping_and_truncation_combined() {
1413 let payload = vec![0xAB; 80];
1414 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1415 let orig = data.len() as u32;
1416 let opts = TransformOptions {
1417 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.99.1").unwrap()],
1418 max_payload_bytes: Some(16),
1419 ..Default::default()
1420 };
1421 apply(&mut data, 0, 0, orig, &opts);
1422
1423 assert_eq!(&data[26..30], &[192, 168, 99, 1]);
1425 assert_eq!(data.len(), 58);
1427 let ihl = ((data[14] & 0x0F) * 4) as usize;
1429 assert_eq!(internet_checksum(&data[14..14 + ihl]), 0x0000);
1430 }
1431
1432 #[test]
1433 fn test_cross_family_and_truncation_combined() {
1434 let payload = vec![0xCD; 100];
1436 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1437 let orig = data.len() as u32;
1438 let opts = TransformOptions {
1439 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1440 max_payload_bytes: Some(10),
1441 ..Default::default()
1442 };
1443 apply(&mut data, 0, 0, orig, &opts);
1444
1445 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1447 assert_eq!(data.len(), 72);
1449 }
1450
1451 #[test]
1454 fn test_truncation_tcp_corrupt_data_offset_ipv4_rejected() {
1455 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &[0xAA; 30]);
1458 data[46] = 0x40;
1461 let original = data.clone();
1462 let orig = data.len() as u32;
1463 let opts = TransformOptions {
1464 max_payload_bytes: Some(0),
1465 ..Default::default()
1466 };
1467 apply(&mut data, 0, 0, orig, &opts);
1468 assert_eq!(
1469 data, original,
1470 "corrupt data-offset must leave packet unchanged"
1471 );
1472 }
1473
1474 #[test]
1475 fn test_truncation_tcp_corrupt_data_offset_ipv6_rejected() {
1476 let payload = [0xBB; 30];
1479 let tcp_len = (20 + payload.len()) as u16;
1480 let mut f = Vec::new();
1481 f.extend_from_slice(&[0xFF; 6]); f.extend_from_slice(&[0x00; 6]); f.extend_from_slice(&[0x86, 0xDD]); f.extend_from_slice(&[0x60, 0x00, 0x00, 0x00]); f.extend_from_slice(&tcp_len.to_be_bytes()); f.push(6); f.push(64); f.extend_from_slice(&[0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); f.extend_from_slice(&[0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]); f.extend_from_slice(&100u16.to_be_bytes()); f.extend_from_slice(&443u16.to_be_bytes()); f.extend_from_slice(&[0x00; 4]); f.extend_from_slice(&[0x00; 4]); f.push(0x50); f.push(0x02); f.extend_from_slice(&[0xFF, 0xFF]); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(&payload);
1501 f[66] = 0x30; let original = f.clone();
1504 let orig = f.len() as u32;
1505 let opts = TransformOptions {
1506 max_payload_bytes: Some(0),
1507 ..Default::default()
1508 };
1509 apply(&mut f, 0, 0, orig, &opts);
1510 assert_eq!(
1511 f, original,
1512 "corrupt data-offset must leave packet unchanged"
1513 );
1514 }
1515
1516 #[test]
1517 fn test_truncation_tcp_zero_data_offset_rejected() {
1518 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 80, &[0xCC; 10]);
1520 data[46] = 0x00;
1521 let original = data.clone();
1522 let orig = data.len() as u32;
1523 let opts = TransformOptions {
1524 max_payload_bytes: Some(0),
1525 ..Default::default()
1526 };
1527 apply(&mut data, 0, 0, orig, &opts);
1528 assert_eq!(data, original);
1529 }
1530
1531 #[test]
1532 fn test_truncation_tcp_min_valid_data_offset_accepted() {
1533 let payload = vec![0xDD; 50];
1536 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &payload);
1537 let orig = data.len() as u32;
1539 let opts = TransformOptions {
1540 max_payload_bytes: Some(10),
1541 ..Default::default()
1542 };
1543 apply(&mut data, 0, 0, orig, &opts);
1544 assert_eq!(data.len(), 64);
1546 }
1547}