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 ((data[ts + 12] >> 4) * 4) as usize
546 }
547 PROTO_UDP => 8,
548 _ => return (origlen, false),
549 };
550 (proto, ts, th)
551 }
552 ET_IPV6 => {
553 if data.len() < ip_off + 40 {
554 return (origlen, false);
555 }
556 let proto = data[ip_off + 6]; let ts = ip_off + 40;
558 let th = match proto {
559 PROTO_TCP => {
560 if data.len() < ts + 20 {
561 return (origlen, false);
562 }
563 ((data[ts + 12] >> 4) * 4) as usize
564 }
565 PROTO_UDP => 8,
566 _ => return (origlen, false),
567 };
568 (proto, ts, th)
569 }
570 _ => return (origlen, false),
571 };
572
573 let payload_start = transport_start + transport_hdr_len;
574 let max_total = payload_start + max_bytes as usize;
575
576 if data.len() <= max_total {
577 return (origlen, false); }
579
580 data.truncate(max_total);
581 let new_len = data.len();
582
583 if et == ET_IPV4 {
585 let new_ip_total = (new_len - ip_off) as u16;
586 data[ip_off + 2..ip_off + 4].copy_from_slice(&new_ip_total.to_be_bytes());
587 } else {
588 let new_plen = (new_len - ip_off - 40) as u16;
590 data[ip_off + 4..ip_off + 6].copy_from_slice(&new_plen.to_be_bytes());
591 }
592
593 if proto == PROTO_UDP {
595 let new_udp_len = (new_len - transport_start) as u16;
596 data[transport_start + 4..transport_start + 6].copy_from_slice(&new_udp_len.to_be_bytes());
597 }
598
599 (new_len as u32, true)
600}
601
602fn recalculate_checksums(data: &mut [u8]) {
606 let Some((ip_off, et)) = find_ip(data) else {
607 return;
608 };
609 match et {
610 ET_IPV4 => recalc_ipv4(data, ip_off),
611 ET_IPV6 => recalc_ipv6(data, ip_off),
612 _ => {}
613 }
614}
615
616fn recalc_ipv4(data: &mut [u8], ip_off: usize) {
617 if data.len() < ip_off + 20 {
618 return;
619 }
620 let ihl = ((data[ip_off] & 0x0F) * 4) as usize;
621 if data.len() < ip_off + ihl {
622 return;
623 }
624
625 data[ip_off + 10] = 0;
627 data[ip_off + 11] = 0;
628 let csum = internet_checksum(&data[ip_off..ip_off + ihl]);
629 data[ip_off + 10..ip_off + 12].copy_from_slice(&csum.to_be_bytes());
630
631 let proto = data[ip_off + 9];
632 let ts = ip_off + ihl;
633 if proto == PROTO_TCP || proto == PROTO_UDP {
634 recalc_transport_v4(data, ip_off, ts, proto);
635 }
636}
637
638fn recalc_transport_v4(data: &mut [u8], ip_off: usize, ts: usize, proto: u8) {
639 let csum_off = if proto == PROTO_TCP { ts + 16 } else { ts + 6 };
640 if data.len() < csum_off + 2 {
641 return;
642 }
643 let src: [u8; 4] = data[ip_off + 12..ip_off + 16].try_into().unwrap();
644 let dst: [u8; 4] = data[ip_off + 16..ip_off + 20].try_into().unwrap();
645 data[csum_off] = 0;
646 data[csum_off + 1] = 0;
647 let csum = transport_checksum_v4(src, dst, proto, &data[ts..]);
648 data[csum_off..csum_off + 2].copy_from_slice(&csum.to_be_bytes());
649}
650
651fn recalc_ipv6(data: &mut [u8], ip_off: usize) {
652 if data.len() < ip_off + 40 {
653 return;
654 }
655 let proto = data[ip_off + 6]; let ts = ip_off + 40;
657 if proto == PROTO_TCP || proto == PROTO_UDP {
658 recalc_transport_v6(data, ip_off, ts, proto);
659 }
660}
661
662fn recalc_transport_v6(data: &mut [u8], ip_off: usize, ts: usize, proto: u8) {
663 let csum_off = if proto == PROTO_TCP { ts + 16 } else { ts + 6 };
664 if data.len() < csum_off + 2 {
665 return;
666 }
667 let src: [u8; 16] = data[ip_off + 8..ip_off + 24].try_into().unwrap();
668 let dst: [u8; 16] = data[ip_off + 24..ip_off + 40].try_into().unwrap();
669 data[csum_off] = 0;
670 data[csum_off + 1] = 0;
671 let csum = transport_checksum_v6(src, dst, proto, &data[ts..]);
672 data[csum_off..csum_off + 2].copy_from_slice(&csum.to_be_bytes());
673}
674
675fn internet_checksum(data: &[u8]) -> u16 {
679 let mut sum: u32 = 0;
680 let mut iter = data.chunks_exact(2);
681 for chunk in &mut iter {
682 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
683 }
684 if let [byte] = iter.remainder() {
685 sum += (*byte as u32) << 8;
686 }
687 while sum >> 16 != 0 {
688 sum = (sum & 0xFFFF) + (sum >> 16);
689 }
690 !(sum as u16)
691}
692
693fn transport_checksum_v4(src: [u8; 4], dst: [u8; 4], proto: u8, segment: &[u8]) -> u16 {
695 let len = segment.len() as u32;
696 let mut sum: u32 = 0;
697 sum += u16::from_be_bytes([src[0], src[1]]) as u32;
699 sum += u16::from_be_bytes([src[2], src[3]]) as u32;
700 sum += u16::from_be_bytes([dst[0], dst[1]]) as u32;
701 sum += u16::from_be_bytes([dst[2], dst[3]]) as u32;
702 sum += proto as u32;
703 sum += len & 0xFFFF;
704 let mut iter = segment.chunks_exact(2);
705 for chunk in &mut iter {
706 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
707 }
708 if let [byte] = iter.remainder() {
709 sum += (*byte as u32) << 8;
710 }
711 while sum >> 16 != 0 {
712 sum = (sum & 0xFFFF) + (sum >> 16);
713 }
714 !(sum as u16)
715}
716
717fn transport_checksum_v6(src: [u8; 16], dst: [u8; 16], proto: u8, segment: &[u8]) -> u16 {
719 let len = segment.len() as u32;
720 let mut sum: u32 = 0;
721 for i in (0..16).step_by(2) {
723 sum += u16::from_be_bytes([src[i], src[i + 1]]) as u32;
724 }
725 for i in (0..16).step_by(2) {
726 sum += u16::from_be_bytes([dst[i], dst[i + 1]]) as u32;
727 }
728 sum += len >> 16;
730 sum += len & 0xFFFF;
731 sum += proto as u32;
733 let mut iter = segment.chunks_exact(2);
734 for chunk in &mut iter {
735 sum += u16::from_be_bytes([chunk[0], chunk[1]]) as u32;
736 }
737 if let [byte] = iter.remainder() {
738 sum += (*byte as u32) << 8;
739 }
740 while sum >> 16 != 0 {
741 sum = (sum & 0xFFFF) + (sum >> 16);
742 }
743 !(sum as u16)
744}
745
746#[cfg(test)]
749mod tests {
750 use super::*;
751
752 fn eth_ipv4_udp(src: [u8; 4], dst: [u8; 4], sport: u16, dport: u16, payload: &[u8]) -> Vec<u8> {
755 let udp_len = (8 + payload.len()) as u16;
756 let ip_total = 20 + udp_len;
757 let mut f = Vec::new();
758 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());
764 f.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]); f.push(64);
766 f.push(17); f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(&src);
769 f.extend_from_slice(&dst);
770 f.extend_from_slice(&sport.to_be_bytes());
771 f.extend_from_slice(&dport.to_be_bytes());
772 f.extend_from_slice(&udp_len.to_be_bytes());
773 f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(payload);
775 f
776 }
777
778 fn eth_ipv4_tcp(src: [u8; 4], dst: [u8; 4], sport: u16, dport: u16, payload: &[u8]) -> Vec<u8> {
779 let ip_total = (20 + 20 + payload.len()) as u16;
780 let mut f = Vec::new();
781 f.extend_from_slice(&[0xFF; 6]);
782 f.extend_from_slice(&[0x00; 6]);
783 f.extend_from_slice(&[0x08, 0x00]);
784 f.push(0x45);
785 f.push(0x00);
786 f.extend_from_slice(&ip_total.to_be_bytes());
787 f.extend_from_slice(&[0x00, 0x01, 0x00, 0x00]);
788 f.push(64);
789 f.push(6); f.extend_from_slice(&[0x00, 0x00]);
791 f.extend_from_slice(&src);
792 f.extend_from_slice(&dst);
793 f.extend_from_slice(&sport.to_be_bytes());
795 f.extend_from_slice(&dport.to_be_bytes());
796 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);
804 f
805 }
806
807 fn eth_ipv6_udp(
809 src: [u8; 16],
810 dst: [u8; 16],
811 sport: u16,
812 dport: u16,
813 payload: &[u8],
814 ) -> Vec<u8> {
815 let udp_len = (8 + payload.len()) as u16;
816 let payload_len = udp_len; let mut f = Vec::new();
818 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());
823 f.push(17); f.push(64); f.extend_from_slice(&src);
826 f.extend_from_slice(&dst);
827 f.extend_from_slice(&sport.to_be_bytes());
828 f.extend_from_slice(&dport.to_be_bytes());
829 f.extend_from_slice(&udp_len.to_be_bytes());
830 f.extend_from_slice(&[0x00, 0x00]); f.extend_from_slice(payload);
832 f
833 }
834
835 #[test]
838 fn test_parse_ip_mapping_valid_v4() {
839 let m = parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap();
840 assert_eq!(m.old, "10.0.0.1".parse::<IpAddr>().unwrap());
841 assert_eq!(m.new, "192.168.1.1".parse::<IpAddr>().unwrap());
842 }
843
844 #[test]
845 fn test_parse_ip_mapping_valid_v6() {
846 let m = parse_ip_mapping("::1=::2").unwrap();
847 assert_eq!(m.old, "::1".parse::<IpAddr>().unwrap());
848 assert_eq!(m.new, "::2".parse::<IpAddr>().unwrap());
849 }
850
851 #[test]
852 fn test_parse_ip_mapping_cross_family_v4_to_v6() {
853 let m = parse_ip_mapping("10.0.0.1=::1").unwrap();
855 assert_eq!(m.old, "10.0.0.1".parse::<IpAddr>().unwrap());
856 assert_eq!(m.new, "::1".parse::<IpAddr>().unwrap());
857 }
858
859 #[test]
860 fn test_parse_ip_mapping_cross_family_v6_to_v4() {
861 let m = parse_ip_mapping("2001:db8::1=192.168.1.1").unwrap();
862 assert_eq!(m.old, "2001:db8::1".parse::<IpAddr>().unwrap());
863 assert_eq!(m.new, "192.168.1.1".parse::<IpAddr>().unwrap());
864 }
865
866 #[test]
867 fn test_parse_ip_mapping_no_equals() {
868 assert!(parse_ip_mapping("10.0.0.1").is_err());
869 }
870
871 #[test]
872 fn test_parse_ip_mapping_invalid_ip() {
873 assert!(parse_ip_mapping("notanip=192.168.1.1").is_err());
874 }
875
876 #[test]
879 fn test_internet_checksum_all_zeros() {
880 assert_eq!(internet_checksum(&[0u8; 20]), 0xFFFF);
882 }
883
884 #[test]
885 fn test_internet_checksum_verify_roundtrip() {
886 let mut header: [u8; 20] = [
890 0x45, 0x00, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x01, 0x08, 0x08, 0x08, 0x08, ];
896 let csum = internet_checksum(&header);
897 header[10] = (csum >> 8) as u8;
898 header[11] = (csum & 0xFF) as u8;
899 assert_eq!(internet_checksum(&header), 0x0000);
901 }
902
903 #[test]
906 fn test_timestamp_shift_positive() {
907 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[]);
908 let origlen = data.len() as u32;
909 let (new_ts, _, _) = apply(
910 &mut data,
911 1_000_000_000,
912 500_000_000,
913 origlen,
914 &TransformOptions::default(),
915 );
916 assert_eq!(new_ts, 1_500_000_000);
917 }
918
919 #[test]
920 fn test_timestamp_shift_clamped_to_zero() {
921 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[]);
922 let origlen = data.len() as u32;
923 let (new_ts, _, _) = apply(&mut data, 100, -200, origlen, &TransformOptions::default());
925 assert_eq!(new_ts, 0);
926 }
927
928 #[test]
931 fn test_no_transform_leaves_data_unchanged() {
932 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[0xAA; 10]);
933 let original = data.clone();
934 let origlen = data.len() as u32;
935 let (new_ts, new_caplen, new_origlen) =
936 apply(&mut data, 42, 0, origlen, &TransformOptions::default());
937 assert_eq!(new_ts, 42);
938 assert_eq!(new_caplen, origlen);
939 assert_eq!(new_origlen, origlen);
940 assert_eq!(data, original);
941 }
942
943 #[test]
946 fn test_ip_mapping_replaces_src_ip() {
947 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0u8; 4]);
948 let origlen = data.len() as u32;
949 let opts = TransformOptions {
950 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap()],
951 ..Default::default()
952 };
953 apply(&mut data, 0, 0, origlen, &opts);
954 assert_eq!(&data[26..30], &[192, 168, 1, 1]);
956 assert_eq!(&data[30..34], &[8, 8, 8, 8]); }
958
959 #[test]
960 fn test_ip_mapping_replaces_dst_ip() {
961 let mut data = eth_ipv4_udp([1, 2, 3, 4], [10, 0, 0, 2], 1234, 53, &[0u8; 4]);
962 let origlen = data.len() as u32;
963 let opts = TransformOptions {
964 ip_map: vec![parse_ip_mapping("10.0.0.2=172.16.0.1").unwrap()],
965 ..Default::default()
966 };
967 apply(&mut data, 0, 0, origlen, &opts);
968 assert_eq!(&data[30..34], &[172, 16, 0, 1]);
969 }
970
971 #[test]
972 fn test_ip_mapping_updates_ipv4_header_checksum() {
973 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0u8; 4]);
974 let origlen = data.len() as u32;
975 let opts = TransformOptions {
976 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.1.1").unwrap()],
977 ..Default::default()
978 };
979 apply(&mut data, 0, 0, origlen, &opts);
980 let ihl = ((data[14] & 0x0F) * 4) as usize;
982 assert_eq!(
983 internet_checksum(&data[14..14 + ihl]),
984 0x0000,
985 "IPv4 header checksum must be valid after IP mapping"
986 );
987 }
988
989 #[test]
990 fn test_ip_mapping_no_match_leaves_data_unchanged() {
991 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &[0xBB; 4]);
992 let original = data.clone();
993 let origlen = data.len() as u32;
994 let opts = TransformOptions {
996 ip_map: vec![parse_ip_mapping("10.0.0.99=10.0.0.1").unwrap()],
997 ..Default::default()
998 };
999 apply(&mut data, 0, 0, origlen, &opts);
1000 assert_eq!(data, original);
1001 }
1002
1003 #[test]
1006 fn test_cross_family_ipv4_to_ipv6_src_mapped() {
1007 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0xAB; 4]);
1011 let origlen = data.len() as u32;
1012 let opts = TransformOptions {
1013 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1014 ..Default::default()
1015 };
1016 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1017
1018 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1020 assert_eq!(data[14] >> 4, 6);
1022 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1024 assert_eq!(src_v6, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
1025 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1027 assert_eq!(dst_v6, "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap());
1028 assert_eq!(&data[data.len() - 4..], &[0xAB; 4]);
1030 assert_eq!(new_caplen, data.len() as u32);
1032 assert_eq!(new_origlen, data.len() as u32);
1033 }
1034
1035 #[test]
1036 fn test_cross_family_ipv4_to_ipv6_dst_mapped() {
1037 let mut data = eth_ipv4_udp([1, 2, 3, 4], [10, 0, 0, 2], 5000, 80, &[]);
1041 let origlen = data.len() as u32;
1042 let opts = TransformOptions {
1043 ip_map: vec![parse_ip_mapping("10.0.0.2=::1").unwrap()],
1044 ..Default::default()
1045 };
1046 apply(&mut data, 0, 0, origlen, &opts);
1047
1048 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1049 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1050 assert_eq!(src_v6, "::ffff:1.2.3.4".parse::<Ipv6Addr>().unwrap());
1051 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1052 assert_eq!(dst_v6, "::1".parse::<Ipv6Addr>().unwrap());
1053 }
1054
1055 #[test]
1056 fn test_cross_family_ipv4_to_ipv6_both_mapped() {
1057 let mut data = eth_ipv4_udp([10, 0, 0, 1], [10, 0, 0, 2], 1000, 2000, &[0xCC; 8]);
1059 let origlen = data.len() as u32;
1060 let opts = TransformOptions {
1061 ip_map: vec![
1062 parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap(),
1063 parse_ip_mapping("10.0.0.2=2001:db8::2").unwrap(),
1064 ],
1065 ..Default::default()
1066 };
1067 apply(&mut data, 0, 0, origlen, &opts);
1068
1069 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1070 let src_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[22..38]).unwrap());
1071 let dst_v6: Ipv6Addr = Ipv6Addr::from(<[u8; 16]>::try_from(&data[38..54]).unwrap());
1072 assert_eq!(src_v6, "2001:db8::1".parse::<Ipv6Addr>().unwrap());
1073 assert_eq!(dst_v6, "2001:db8::2".parse::<Ipv6Addr>().unwrap());
1074 }
1075
1076 #[test]
1077 fn test_cross_family_ipv4_to_ipv6_checksum_valid() {
1078 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &[0xDE; 16]);
1079 let origlen = data.len() as u32;
1080 let opts = TransformOptions {
1081 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1082 ..Default::default()
1083 };
1084 apply(&mut data, 0, 0, origlen, &opts);
1085
1086 let udp_csum = u16::from_be_bytes([data[54 + 6], data[54 + 7]]);
1089 assert_ne!(udp_csum, 0, "UDP checksum must be set in IPv6 packet");
1090 }
1091
1092 #[test]
1093 fn test_cross_family_ipv4_to_ipv6_size_change() {
1094 let payload = [0u8; 10];
1096 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1000, 2000, &payload);
1097 let ipv4_len = data.len();
1098 let origlen = data.len() as u32;
1099 let opts = TransformOptions {
1100 ip_map: vec![parse_ip_mapping("10.0.0.1=::1").unwrap()],
1101 ..Default::default()
1102 };
1103 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1104
1105 assert_eq!(data.len(), ipv4_len + 20);
1108 assert_eq!(new_caplen, data.len() as u32);
1109 assert_eq!(new_origlen, data.len() as u32);
1110 }
1111
1112 #[test]
1115 fn test_cross_family_ipv6_to_ipv4_src_mapped() {
1116 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1119 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1120 let mut data = eth_ipv6_udp(src_v6, dst_v6, 5000, 80, &[0xBB; 4]);
1121 let origlen = data.len() as u32;
1122 let opts = TransformOptions {
1123 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1124 ..Default::default()
1125 };
1126 apply(&mut data, 0, 0, origlen, &opts);
1127
1128 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1130 assert_eq!(data[14] >> 4, 4);
1131 assert_eq!(&data[26..30], &[10, 0, 0, 1]);
1133 assert_eq!(&data[30..34], &[8, 8, 8, 8]);
1135 assert_eq!(&data[data.len() - 4..], &[0xBB; 4]);
1137 }
1138
1139 #[test]
1140 fn test_cross_family_ipv6_to_ipv4_dst_mapped() {
1141 let src_v6: [u8; 16] = "::ffff:1.2.3.4".parse::<Ipv6Addr>().unwrap().octets();
1144 let dst_v6: [u8; 16] = "2001:db8::2".parse::<Ipv6Addr>().unwrap().octets();
1145 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1234, 443, &[]);
1146 let origlen = data.len() as u32;
1147 let opts = TransformOptions {
1148 ip_map: vec![parse_ip_mapping("2001:db8::2=192.168.1.2").unwrap()],
1149 ..Default::default()
1150 };
1151 apply(&mut data, 0, 0, origlen, &opts);
1152
1153 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1154 assert_eq!(&data[26..30], &[1, 2, 3, 4]); assert_eq!(&data[30..34], &[192, 168, 1, 2]);
1156 }
1157
1158 #[test]
1159 fn test_cross_family_ipv6_to_ipv4_skipped_when_non_mapped_addr() {
1160 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1163 let dst_v6: [u8; 16] = "2001:db8::2".parse::<Ipv6Addr>().unwrap().octets();
1164 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1000, 2000, &[0xFF; 4]);
1165 let original = data.clone();
1166 let origlen = data.len() as u32;
1167 let opts = TransformOptions {
1169 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1170 ..Default::default()
1171 };
1172 apply(&mut data, 0, 0, origlen, &opts);
1173 assert_eq!(data, original);
1175 }
1176
1177 #[test]
1178 fn test_cross_family_ipv6_to_ipv4_checksum_valid() {
1179 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1180 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1181 let mut data = eth_ipv6_udp(src_v6, dst_v6, 5000, 53, &[0xDE; 8]);
1182 let origlen = data.len() as u32;
1183 let opts = TransformOptions {
1184 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1185 ..Default::default()
1186 };
1187 apply(&mut data, 0, 0, origlen, &opts);
1188
1189 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV4);
1190 let ihl = ((data[14] & 0x0F) * 4) as usize;
1191 assert_eq!(
1192 internet_checksum(&data[14..14 + ihl]),
1193 0x0000,
1194 "IPv4 header checksum must be valid after v6→v4 reframe"
1195 );
1196 }
1197
1198 #[test]
1199 fn test_cross_family_ipv6_to_ipv4_size_change() {
1200 let src_v6: [u8; 16] = "2001:db8::1".parse::<Ipv6Addr>().unwrap().octets();
1202 let dst_v6: [u8; 16] = "::ffff:8.8.8.8".parse::<Ipv6Addr>().unwrap().octets();
1203 let mut data = eth_ipv6_udp(src_v6, dst_v6, 1000, 2000, &[0u8; 10]);
1204 let ipv6_len = data.len();
1205 let origlen = data.len() as u32;
1206 let opts = TransformOptions {
1207 ip_map: vec![parse_ip_mapping("2001:db8::1=10.0.0.1").unwrap()],
1208 ..Default::default()
1209 };
1210 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, origlen, &opts);
1211 assert_eq!(data.len(), ipv6_len - 20);
1212 assert_eq!(new_caplen, data.len() as u32);
1213 assert_eq!(new_origlen, data.len() as u32);
1214 }
1215
1216 #[test]
1219 fn test_truncation_udp_updates_lengths() {
1220 let payload = vec![0xBB; 100];
1221 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1222 let orig = data.len() as u32;
1223 let opts = TransformOptions {
1224 max_payload_bytes: Some(10),
1225 ..Default::default()
1226 };
1227 let (_, new_caplen, new_origlen) = apply(&mut data, 0, 0, orig, &opts);
1228
1229 assert_eq!(data.len(), 52);
1231 assert_eq!(new_caplen, 52);
1232 assert_eq!(new_origlen, 52);
1233
1234 let ip_total = u16::from_be_bytes([data[16], data[17]]);
1236 assert_eq!(ip_total, 38, "IPv4 total length not updated");
1237
1238 let udp_len = u16::from_be_bytes([data[38], data[39]]);
1241 assert_eq!(udp_len, 18, "UDP length not updated");
1242 }
1243
1244 #[test]
1245 fn test_truncation_tcp_updates_ip_length() {
1246 let payload = vec![0xCC; 50];
1247 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &payload);
1248 let orig = data.len() as u32;
1249 let opts = TransformOptions {
1250 max_payload_bytes: Some(5),
1251 ..Default::default()
1252 };
1253 apply(&mut data, 0, 0, orig, &opts);
1254
1255 assert_eq!(data.len(), 59);
1257 let ip_total = u16::from_be_bytes([data[16], data[17]]);
1259 assert_eq!(ip_total, 45);
1260 }
1261
1262 #[test]
1263 fn test_truncation_noop_when_short_enough() {
1264 let payload = vec![0xAA; 5];
1265 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1266 let original = data.clone();
1267 let orig = data.len() as u32;
1268 let opts = TransformOptions {
1269 max_payload_bytes: Some(100),
1270 ..Default::default()
1271 };
1272 apply(&mut data, 0, 0, orig, &opts);
1273 assert_eq!(data, original);
1274 }
1275
1276 #[test]
1277 fn test_truncation_checksums_valid() {
1278 let payload = vec![0xDE; 50];
1279 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1280 let orig = data.len() as u32;
1281 let opts = TransformOptions {
1282 max_payload_bytes: Some(8),
1283 ..Default::default()
1284 };
1285 apply(&mut data, 0, 0, orig, &opts);
1286 let ihl = ((data[14] & 0x0F) * 4) as usize;
1287 assert_eq!(
1288 internet_checksum(&data[14..14 + ihl]),
1289 0x0000,
1290 "IPv4 header checksum must be valid after truncation"
1291 );
1292 }
1293
1294 #[test]
1295 fn test_truncation_zero_payload_bytes() {
1296 let payload = vec![0xFF; 20];
1298 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 200, &payload);
1299 let orig = data.len() as u32;
1300 let opts = TransformOptions {
1301 max_payload_bytes: Some(0),
1302 ..Default::default()
1303 };
1304 apply(&mut data, 0, 0, orig, &opts);
1305 assert_eq!(data.len(), 42);
1307 }
1308
1309 #[test]
1312 fn test_proto_truncation_tcp_uses_rule() {
1313 let payload = vec![0xAA; 100];
1314 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 443, &payload);
1315 let orig = data.len() as u32;
1316 let opts = TransformOptions {
1317 proto_truncation: vec![ProtocolTruncation {
1318 proto: PROTO_TCP,
1319 max_payload_bytes: 10,
1320 }],
1321 ..Default::default()
1322 };
1323 apply(&mut data, 0, 0, orig, &opts);
1324 assert_eq!(data.len(), 64);
1326 }
1327
1328 #[test]
1329 fn test_proto_truncation_udp_uses_rule() {
1330 let payload = vec![0xBB; 80];
1331 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1332 let orig = data.len() as u32;
1333 let opts = TransformOptions {
1334 proto_truncation: vec![ProtocolTruncation {
1335 proto: PROTO_UDP,
1336 max_payload_bytes: 8,
1337 }],
1338 ..Default::default()
1339 };
1340 apply(&mut data, 0, 0, orig, &opts);
1341 assert_eq!(data.len(), 50);
1343 }
1344
1345 #[test]
1346 fn test_proto_truncation_overrides_global() {
1347 let payload = vec![0xCC; 100];
1349 let mut data = eth_ipv4_tcp([1, 2, 3, 4], [5, 6, 7, 8], 100, 80, &payload);
1350 let orig = data.len() as u32;
1351 let opts = TransformOptions {
1352 max_payload_bytes: Some(50),
1353 proto_truncation: vec![ProtocolTruncation {
1354 proto: PROTO_TCP,
1355 max_payload_bytes: 10,
1356 }],
1357 ..Default::default()
1358 };
1359 apply(&mut data, 0, 0, orig, &opts);
1360 assert_eq!(data.len(), 64);
1362 }
1363
1364 #[test]
1365 fn test_proto_truncation_fallback_to_global() {
1366 let payload = vec![0xDD; 80];
1368 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1369 let orig = data.len() as u32;
1370 let opts = TransformOptions {
1371 max_payload_bytes: Some(20),
1372 proto_truncation: vec![ProtocolTruncation {
1373 proto: PROTO_TCP, max_payload_bytes: 5,
1375 }],
1376 ..Default::default()
1377 };
1378 apply(&mut data, 0, 0, orig, &opts);
1379 assert_eq!(data.len(), 62);
1381 }
1382
1383 #[test]
1384 fn test_proto_truncation_no_match_no_global_no_truncation() {
1385 let payload = vec![0xEE; 50];
1387 let mut data = eth_ipv4_udp([1, 2, 3, 4], [5, 6, 7, 8], 100, 53, &payload);
1388 let original = data.clone();
1389 let orig = data.len() as u32;
1390 let opts = TransformOptions {
1391 proto_truncation: vec![ProtocolTruncation {
1392 proto: PROTO_TCP,
1393 max_payload_bytes: 10,
1394 }],
1395 ..Default::default()
1396 };
1397 apply(&mut data, 0, 0, orig, &opts);
1398 assert_eq!(data, original);
1399 }
1400
1401 #[test]
1404 fn test_ip_mapping_and_truncation_combined() {
1405 let payload = vec![0xAB; 80];
1406 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1407 let orig = data.len() as u32;
1408 let opts = TransformOptions {
1409 ip_map: vec![parse_ip_mapping("10.0.0.1=192.168.99.1").unwrap()],
1410 max_payload_bytes: Some(16),
1411 ..Default::default()
1412 };
1413 apply(&mut data, 0, 0, orig, &opts);
1414
1415 assert_eq!(&data[26..30], &[192, 168, 99, 1]);
1417 assert_eq!(data.len(), 58);
1419 let ihl = ((data[14] & 0x0F) * 4) as usize;
1421 assert_eq!(internet_checksum(&data[14..14 + ihl]), 0x0000);
1422 }
1423
1424 #[test]
1425 fn test_cross_family_and_truncation_combined() {
1426 let payload = vec![0xCD; 100];
1428 let mut data = eth_ipv4_udp([10, 0, 0, 1], [8, 8, 8, 8], 1234, 53, &payload);
1429 let orig = data.len() as u32;
1430 let opts = TransformOptions {
1431 ip_map: vec![parse_ip_mapping("10.0.0.1=2001:db8::1").unwrap()],
1432 max_payload_bytes: Some(10),
1433 ..Default::default()
1434 };
1435 apply(&mut data, 0, 0, orig, &opts);
1436
1437 assert_eq!(u16::from_be_bytes([data[12], data[13]]), ET_IPV6);
1439 assert_eq!(data.len(), 72);
1441 }
1442}