xref: /webrtc/util/src/vnet/nat/nat_test.rs (revision 83f2d1bb)
1 use super::*;
2 use crate::vnet::chunk::ChunkUdp;
3 use std::net::SocketAddr;
4 use std::str::FromStr;
5 
6 // oic: outbound internal chunk
7 // oec: outbound external chunk
8 // iic: inbound internal chunk
9 // iec: inbound external chunk
10 
11 const DEMO_IP: &str = "1.2.3.4";
12 
13 #[test]
test_nat_type_default() -> Result<()>14 fn test_nat_type_default() -> Result<()> {
15     let nat = NetworkAddressTranslator::new(NatConfig {
16         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
17         ..Default::default()
18     })?;
19 
20     assert_eq!(
21         nat.nat_type.mapping_behavior,
22         EndpointDependencyType::EndpointIndependent,
23         "should match"
24     );
25     assert_eq!(
26         nat.nat_type.filtering_behavior,
27         EndpointDependencyType::EndpointIndependent,
28         "should match"
29     );
30     assert!(!nat.nat_type.hair_pining, "should be false");
31     assert!(!nat.nat_type.port_preservation, "should be false");
32     assert_eq!(
33         nat.nat_type.mapping_life_time, DEFAULT_NAT_MAPPING_LIFE_TIME,
34         "should be false"
35     );
36 
37     Ok(())
38 }
39 
40 #[tokio::test]
test_nat_mapping_behavior_full_cone_nat() -> Result<()>41 async fn test_nat_mapping_behavior_full_cone_nat() -> Result<()> {
42     let nat = NetworkAddressTranslator::new(NatConfig {
43         nat_type: NatType {
44             mapping_behavior: EndpointDependencyType::EndpointIndependent,
45             filtering_behavior: EndpointDependencyType::EndpointIndependent,
46             hair_pining: false,
47             mapping_life_time: Duration::from_secs(30),
48             ..Default::default()
49         },
50         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
51         ..Default::default()
52     })?;
53 
54     let src = SocketAddr::from_str("192.168.0.2:1234")?;
55     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
56 
57     let oic = ChunkUdp::new(src, dst);
58 
59     let oec = nat.translate_outbound(&oic).await?.unwrap();
60     assert_eq!(nat.outbound_map_len().await, 1, "should match");
61     assert_eq!(nat.inbound_map_len().await, 1, "should match");
62 
63     log::debug!("o-original  : {}", oic);
64     log::debug!("o-translated: {}", oec);
65 
66     let iec = ChunkUdp::new(
67         SocketAddr::new(dst.ip(), dst.port()),
68         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
69     );
70 
71     log::debug!("i-original  : {}", iec);
72 
73     let iic = nat.translate_inbound(&iec).await?.unwrap();
74 
75     log::debug!("i-translated: {}", iic);
76 
77     assert_eq!(oic.source_addr(), iic.destination_addr(), "should match");
78 
79     // packet with dest addr that does not exist in the mapping table
80     // will be dropped
81     let iec = ChunkUdp::new(
82         SocketAddr::new(dst.ip(), dst.port()),
83         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port() + 1),
84     );
85 
86     let result = nat.translate_inbound(&iec).await;
87     assert!(result.is_err(), "should fail (dropped)");
88 
89     // packet from any addr will be accepted (full-cone)
90     let iec = ChunkUdp::new(
91         SocketAddr::new(dst.ip(), 7777),
92         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
93     );
94 
95     let result = nat.translate_inbound(&iec).await;
96     assert!(result.is_ok(), "should succeed");
97 
98     Ok(())
99 }
100 
101 #[tokio::test]
test_nat_mapping_behavior_addr_restricted_cone_nat() -> Result<()>102 async fn test_nat_mapping_behavior_addr_restricted_cone_nat() -> Result<()> {
103     let nat = NetworkAddressTranslator::new(NatConfig {
104         nat_type: NatType {
105             mapping_behavior: EndpointDependencyType::EndpointIndependent,
106             filtering_behavior: EndpointDependencyType::EndpointAddrDependent,
107             hair_pining: false,
108             mapping_life_time: Duration::from_secs(30),
109             ..Default::default()
110         },
111         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
112         ..Default::default()
113     })?;
114 
115     let src = SocketAddr::from_str("192.168.0.2:1234")?;
116     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
117 
118     let oic = ChunkUdp::new(src, dst);
119     log::debug!("o-original  : {}", oic);
120 
121     let oec = nat.translate_outbound(&oic).await?.unwrap();
122     assert_eq!(nat.outbound_map_len().await, 1, "should match");
123     assert_eq!(nat.inbound_map_len().await, 1, "should match");
124     log::debug!("o-translated: {}", oec);
125 
126     // sending different (IP: 5.6.7.9) won't create a new mapping
127     let oic2 = ChunkUdp::new(
128         SocketAddr::from_str("192.168.0.2:1234")?,
129         SocketAddr::from_str("5.6.7.9:9000")?,
130     );
131     let oec2 = nat.translate_outbound(&oic2).await?.unwrap();
132     assert_eq!(nat.outbound_map_len().await, 1, "should match");
133     assert_eq!(nat.inbound_map_len().await, 1, "should match");
134     log::debug!("o-translated: {}", oec2);
135 
136     let iec = ChunkUdp::new(
137         SocketAddr::new(dst.ip(), dst.port()),
138         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
139     );
140 
141     log::debug!("i-original  : {}", iec);
142 
143     let iic = nat.translate_inbound(&iec).await?.unwrap();
144 
145     log::debug!("i-translated: {}", iic);
146 
147     assert_eq!(oic.source_addr(), iic.destination_addr(), "should match");
148 
149     // packet with dest addr that does not exist in the mapping table
150     // will be dropped
151     let iec = ChunkUdp::new(
152         SocketAddr::new(dst.ip(), dst.port()),
153         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port() + 1),
154     );
155 
156     let result = nat.translate_inbound(&iec).await;
157     assert!(result.is_err(), "should fail (dropped)");
158 
159     // packet from any port will be accepted (restricted-cone)
160     let iec = ChunkUdp::new(
161         SocketAddr::new(dst.ip(), 7777),
162         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
163     );
164 
165     let result = nat.translate_inbound(&iec).await;
166     assert!(result.is_ok(), "should succeed");
167 
168     // packet from different addr will be droped (restricted-cone)
169     let iec = ChunkUdp::new(
170         SocketAddr::from_str(&format!("{}:{}", "6.6.6.6", dst.port()))?,
171         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
172     );
173 
174     let result = nat.translate_inbound(&iec).await;
175     assert!(result.is_err(), "should fail (dropped)");
176 
177     Ok(())
178 }
179 
180 #[tokio::test]
test_nat_mapping_behavior_port_restricted_cone_nat() -> Result<()>181 async fn test_nat_mapping_behavior_port_restricted_cone_nat() -> Result<()> {
182     let nat = NetworkAddressTranslator::new(NatConfig {
183         nat_type: NatType {
184             mapping_behavior: EndpointDependencyType::EndpointIndependent,
185             filtering_behavior: EndpointDependencyType::EndpointAddrPortDependent,
186             hair_pining: false,
187             mapping_life_time: Duration::from_secs(30),
188             ..Default::default()
189         },
190         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
191         ..Default::default()
192     })?;
193 
194     let src = SocketAddr::from_str("192.168.0.2:1234")?;
195     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
196 
197     let oic = ChunkUdp::new(src, dst);
198     log::debug!("o-original  : {}", oic);
199 
200     let oec = nat.translate_outbound(&oic).await?.unwrap();
201     assert_eq!(nat.outbound_map_len().await, 1, "should match");
202     assert_eq!(nat.inbound_map_len().await, 1, "should match");
203     log::debug!("o-translated: {}", oec);
204 
205     // sending different (IP: 5.6.7.9) won't create a new mapping
206     let oic2 = ChunkUdp::new(
207         SocketAddr::from_str("192.168.0.2:1234")?,
208         SocketAddr::from_str("5.6.7.9:9000")?,
209     );
210     let oec2 = nat.translate_outbound(&oic2).await?.unwrap();
211     assert_eq!(nat.outbound_map_len().await, 1, "should match");
212     assert_eq!(nat.inbound_map_len().await, 1, "should match");
213     log::debug!("o-translated: {}", oec2);
214 
215     let iec = ChunkUdp::new(
216         SocketAddr::new(dst.ip(), dst.port()),
217         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
218     );
219 
220     log::debug!("i-original  : {}", iec);
221 
222     let iic = nat.translate_inbound(&iec).await?.unwrap();
223 
224     log::debug!("i-translated: {}", iic);
225 
226     assert_eq!(oic.source_addr(), iic.destination_addr(), "should match");
227 
228     // packet with dest addr that does not exist in the mapping table
229     // will be dropped
230     let iec = ChunkUdp::new(
231         SocketAddr::new(dst.ip(), dst.port()),
232         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port() + 1),
233     );
234 
235     let result = nat.translate_inbound(&iec).await;
236     assert!(result.is_err(), "should fail (dropped)");
237 
238     // packet from different port will be dropped (port-restricted-cone)
239     let iec = ChunkUdp::new(
240         SocketAddr::new(dst.ip(), 7777),
241         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
242     );
243 
244     let result = nat.translate_inbound(&iec).await;
245     assert!(result.is_err(), "should fail (dropped)");
246 
247     // packet from different addr will be droped (restricted-cone)
248     let iec = ChunkUdp::new(
249         SocketAddr::from_str(&format!("{}:{}", "6.6.6.6", dst.port()))?,
250         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
251     );
252 
253     let result = nat.translate_inbound(&iec).await;
254     assert!(result.is_err(), "should fail (dropped)");
255 
256     Ok(())
257 }
258 
259 #[tokio::test]
test_nat_mapping_behavior_symmetric_nat_addr_dependent_mapping() -> Result<()>260 async fn test_nat_mapping_behavior_symmetric_nat_addr_dependent_mapping() -> Result<()> {
261     let nat = NetworkAddressTranslator::new(NatConfig {
262         nat_type: NatType {
263             mapping_behavior: EndpointDependencyType::EndpointAddrDependent,
264             filtering_behavior: EndpointDependencyType::EndpointAddrDependent,
265             hair_pining: false,
266             mapping_life_time: Duration::from_secs(30),
267             ..Default::default()
268         },
269         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
270         ..Default::default()
271     })?;
272 
273     let src = SocketAddr::from_str("192.168.0.2:1234")?;
274     let dst1 = SocketAddr::from_str("5.6.7.8:5678")?;
275     let dst2 = SocketAddr::from_str("5.6.7.100:5678")?;
276     let dst3 = SocketAddr::from_str("5.6.7.8:6000")?;
277 
278     let oic1 = ChunkUdp::new(src, dst1);
279     let oic2 = ChunkUdp::new(src, dst2);
280     let oic3 = ChunkUdp::new(src, dst3);
281 
282     log::debug!("o-original  : {}", oic1);
283     log::debug!("o-original  : {}", oic2);
284     log::debug!("o-original  : {}", oic3);
285 
286     let oec1 = nat.translate_outbound(&oic1).await?.unwrap();
287     let oec2 = nat.translate_outbound(&oic2).await?.unwrap();
288     let oec3 = nat.translate_outbound(&oic3).await?.unwrap();
289 
290     assert_eq!(nat.outbound_map_len().await, 2, "should match");
291     assert_eq!(nat.inbound_map_len().await, 2, "should match");
292 
293     log::debug!("o-translated: {}", oec1);
294     log::debug!("o-translated: {}", oec2);
295     log::debug!("o-translated: {}", oec3);
296 
297     assert_ne!(
298         oec1.source_addr().port(),
299         oec2.source_addr().port(),
300         "should not match"
301     );
302     assert_eq!(
303         oec1.source_addr().port(),
304         oec3.source_addr().port(),
305         "should match"
306     );
307 
308     Ok(())
309 }
310 
311 #[tokio::test]
test_nat_mapping_behavior_symmetric_nat_port_dependent_mapping() -> Result<()>312 async fn test_nat_mapping_behavior_symmetric_nat_port_dependent_mapping() -> Result<()> {
313     let nat = NetworkAddressTranslator::new(NatConfig {
314         nat_type: NatType {
315             mapping_behavior: EndpointDependencyType::EndpointAddrPortDependent,
316             filtering_behavior: EndpointDependencyType::EndpointAddrPortDependent,
317             hair_pining: false,
318             mapping_life_time: Duration::from_secs(30),
319             ..Default::default()
320         },
321         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
322         ..Default::default()
323     })?;
324 
325     let src = SocketAddr::from_str("192.168.0.2:1234")?;
326     let dst1 = SocketAddr::from_str("5.6.7.8:5678")?;
327     let dst2 = SocketAddr::from_str("5.6.7.100:5678")?;
328     let dst3 = SocketAddr::from_str("5.6.7.8:6000")?;
329 
330     let oic1 = ChunkUdp::new(src, dst1);
331     let oic2 = ChunkUdp::new(src, dst2);
332     let oic3 = ChunkUdp::new(src, dst3);
333 
334     log::debug!("o-original  : {}", oic1);
335     log::debug!("o-original  : {}", oic2);
336     log::debug!("o-original  : {}", oic3);
337 
338     let oec1 = nat.translate_outbound(&oic1).await?.unwrap();
339     let oec2 = nat.translate_outbound(&oic2).await?.unwrap();
340     let oec3 = nat.translate_outbound(&oic3).await?.unwrap();
341 
342     assert_eq!(nat.outbound_map_len().await, 3, "should match");
343     assert_eq!(nat.inbound_map_len().await, 3, "should match");
344 
345     log::debug!("o-translated: {}", oec1);
346     log::debug!("o-translated: {}", oec2);
347     log::debug!("o-translated: {}", oec3);
348 
349     assert_ne!(
350         oec1.source_addr().port(),
351         oec2.source_addr().port(),
352         "should not match"
353     );
354     assert_ne!(
355         oec1.source_addr().port(),
356         oec3.source_addr().port(),
357         "should match"
358     );
359 
360     Ok(())
361 }
362 
363 #[tokio::test]
test_nat_mapping_timeout_refresh_on_outbound() -> Result<()>364 async fn test_nat_mapping_timeout_refresh_on_outbound() -> Result<()> {
365     let nat = NetworkAddressTranslator::new(NatConfig {
366         nat_type: NatType {
367             mapping_behavior: EndpointDependencyType::EndpointIndependent,
368             filtering_behavior: EndpointDependencyType::EndpointIndependent,
369             hair_pining: false,
370             mapping_life_time: Duration::from_millis(200),
371             ..Default::default()
372         },
373         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
374         ..Default::default()
375     })?;
376 
377     let src = SocketAddr::from_str("192.168.0.2:1234")?;
378     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
379 
380     let oic = ChunkUdp::new(src, dst);
381 
382     let oec = nat.translate_outbound(&oic).await?.unwrap();
383     assert_eq!(nat.outbound_map_len().await, 1, "should match");
384     assert_eq!(nat.inbound_map_len().await, 1, "should match");
385 
386     log::debug!("o-original  : {}", oic);
387     log::debug!("o-translated: {}", oec);
388 
389     // record mapped addr
390     let mapped = oec.source_addr().to_string();
391 
392     tokio::time::sleep(Duration::from_millis(5)).await;
393 
394     // refresh
395     let oec = nat.translate_outbound(&oic).await?.unwrap();
396     assert_eq!(nat.outbound_map_len().await, 1, "should match");
397     assert_eq!(nat.inbound_map_len().await, 1, "should match");
398 
399     log::debug!("o-original  : {}", oic);
400     log::debug!("o-translated: {}", oec);
401 
402     assert_eq!(
403         mapped,
404         oec.source_addr().to_string(),
405         "mapped addr should match"
406     );
407 
408     // sleep long enough for the mapping to expire
409     tokio::time::sleep(Duration::from_millis(225)).await;
410 
411     // refresh after expiration
412     let oec = nat.translate_outbound(&oic).await?.unwrap();
413     assert_eq!(nat.outbound_map_len().await, 1, "should match");
414     assert_eq!(nat.inbound_map_len().await, 1, "should match");
415 
416     log::debug!("o-original  : {}", oic);
417     log::debug!("o-translated: {}", oec);
418 
419     assert_ne!(
420         oec.source_addr().to_string(),
421         mapped,
422         "mapped addr should not match"
423     );
424 
425     Ok(())
426 }
427 
428 #[tokio::test]
test_nat_mapping_timeout_outbound_detects_timeout() -> Result<()>429 async fn test_nat_mapping_timeout_outbound_detects_timeout() -> Result<()> {
430     let nat = NetworkAddressTranslator::new(NatConfig {
431         nat_type: NatType {
432             mapping_behavior: EndpointDependencyType::EndpointIndependent,
433             filtering_behavior: EndpointDependencyType::EndpointIndependent,
434             hair_pining: false,
435             mapping_life_time: Duration::from_millis(100),
436             ..Default::default()
437         },
438         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
439         ..Default::default()
440     })?;
441 
442     let src = SocketAddr::from_str("192.168.0.2:1234")?;
443     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
444 
445     let oic = ChunkUdp::new(src, dst);
446 
447     let oec = nat.translate_outbound(&oic).await?.unwrap();
448     assert_eq!(nat.outbound_map_len().await, 1, "should match");
449     assert_eq!(nat.inbound_map_len().await, 1, "should match");
450 
451     log::debug!("o-original  : {}", oic);
452     log::debug!("o-translated: {}", oec);
453 
454     // sleep long enough for the mapping to expire
455     tokio::time::sleep(Duration::from_millis(125)).await;
456 
457     let iec = ChunkUdp::new(
458         SocketAddr::new(dst.ip(), dst.port()),
459         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
460     );
461 
462     log::debug!("i-original  : {}", iec);
463 
464     let result = nat.translate_inbound(&iec).await;
465     assert!(result.is_err(), "should drop");
466     assert_eq!(nat.outbound_map_len().await, 0, "should match");
467     assert_eq!(nat.inbound_map_len().await, 0, "should match");
468 
469     Ok(())
470 }
471 
472 #[tokio::test]
test_nat1to1_bahavior_one_mapping() -> Result<()>473 async fn test_nat1to1_bahavior_one_mapping() -> Result<()> {
474     let nat = NetworkAddressTranslator::new(NatConfig {
475         nat_type: NatType {
476             mode: NatMode::Nat1To1,
477             ..Default::default()
478         },
479         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
480         local_ips: vec![IpAddr::from_str("10.0.0.1")?],
481         ..Default::default()
482     })?;
483 
484     let src = SocketAddr::from_str("10.0.0.1:1234")?;
485     let dst = SocketAddr::from_str("5.6.7.8:5678")?;
486 
487     let oic = ChunkUdp::new(src, dst);
488 
489     let oec = nat.translate_outbound(&oic).await?.unwrap();
490     assert_eq!(nat.outbound_map_len().await, 0, "should match");
491     assert_eq!(nat.inbound_map_len().await, 0, "should match");
492 
493     log::debug!("o-original  : {}", oic);
494     log::debug!("o-translated: {}", oec);
495 
496     assert_eq!(
497         "1.2.3.4:1234",
498         oec.source_addr().to_string(),
499         "should match"
500     );
501 
502     let iec = ChunkUdp::new(
503         SocketAddr::new(dst.ip(), dst.port()),
504         SocketAddr::new(oec.source_addr().ip(), oec.source_addr().port()),
505     );
506 
507     log::debug!("i-original  : {}", iec);
508 
509     let iic = nat.translate_inbound(&iec).await?.unwrap();
510 
511     log::debug!("i-translated: {}", iic);
512 
513     assert_eq!(oic.source_addr(), iic.destination_addr(), "should match");
514 
515     Ok(())
516 }
517 
518 #[tokio::test]
test_nat1to1_bahavior_more_mapping() -> Result<()>519 async fn test_nat1to1_bahavior_more_mapping() -> Result<()> {
520     let nat = NetworkAddressTranslator::new(NatConfig {
521         nat_type: NatType {
522             mode: NatMode::Nat1To1,
523             ..Default::default()
524         },
525         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?, IpAddr::from_str("1.2.3.5")?],
526         local_ips: vec![IpAddr::from_str("10.0.0.1")?, IpAddr::from_str("10.0.0.2")?],
527         ..Default::default()
528     })?;
529 
530     // outbound translation
531 
532     let before = ChunkUdp::new(
533         SocketAddr::from_str("10.0.0.1:1234")?,
534         SocketAddr::from_str("5.6.7.8:5678")?,
535     );
536 
537     let after = nat.translate_outbound(&before).await?.unwrap();
538     assert_eq!(
539         after.source_addr().to_string(),
540         "1.2.3.4:1234",
541         "should match"
542     );
543 
544     let before = ChunkUdp::new(
545         SocketAddr::from_str("10.0.0.2:1234")?,
546         SocketAddr::from_str("5.6.7.8:5678")?,
547     );
548 
549     let after = nat.translate_outbound(&before).await?.unwrap();
550     assert_eq!(
551         after.source_addr().to_string(),
552         "1.2.3.5:1234",
553         "should match"
554     );
555 
556     // inbound translation
557 
558     let before = ChunkUdp::new(
559         SocketAddr::from_str("5.6.7.8:5678")?,
560         SocketAddr::from_str(&format!("{}:{}", DEMO_IP, 2525))?,
561     );
562 
563     let after = nat.translate_inbound(&before).await?.unwrap();
564     assert_eq!(
565         after.destination_addr().to_string(),
566         "10.0.0.1:2525",
567         "should match"
568     );
569 
570     let before = ChunkUdp::new(
571         SocketAddr::from_str("5.6.7.8:5678")?,
572         SocketAddr::from_str("1.2.3.5:9847")?,
573     );
574 
575     let after = nat.translate_inbound(&before).await?.unwrap();
576     assert_eq!(
577         after.destination_addr().to_string(),
578         "10.0.0.2:9847",
579         "should match"
580     );
581 
582     Ok(())
583 }
584 
585 #[tokio::test]
test_nat1to1_bahavior_failure() -> Result<()>586 async fn test_nat1to1_bahavior_failure() -> Result<()> {
587     // 1:1 NAT requires more than one mapping
588     let result = NetworkAddressTranslator::new(NatConfig {
589         nat_type: NatType {
590             mode: NatMode::Nat1To1,
591             ..Default::default()
592         },
593         ..Default::default()
594     });
595     assert!(result.is_err(), "should fail");
596 
597     // 1:1 NAT requires the same number of mappedIPs and localIPs
598     let result = NetworkAddressTranslator::new(NatConfig {
599         nat_type: NatType {
600             mode: NatMode::Nat1To1,
601             ..Default::default()
602         },
603         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?, IpAddr::from_str("1.2.3.5")?],
604         local_ips: vec![IpAddr::from_str("10.0.0.1")?],
605         ..Default::default()
606     });
607     assert!(result.is_err(), "should fail");
608 
609     // drop outbound or inbound chunk with no route in 1:1 NAT
610     let nat = NetworkAddressTranslator::new(NatConfig {
611         nat_type: NatType {
612             mode: NatMode::Nat1To1,
613             ..Default::default()
614         },
615         mapped_ips: vec![IpAddr::from_str(DEMO_IP)?],
616         local_ips: vec![IpAddr::from_str("10.0.0.1")?],
617         ..Default::default()
618     })?;
619 
620     let before = ChunkUdp::new(
621         SocketAddr::from_str("10.0.0.2:1234")?, // no external mapping for this
622         SocketAddr::from_str("5.6.7.8:5678")?,
623     );
624 
625     let after = nat.translate_outbound(&before).await?;
626     assert!(after.is_none(), "should be nil");
627 
628     let before = ChunkUdp::new(
629         SocketAddr::from_str("5.6.7.8:5678")?,
630         SocketAddr::from_str("10.0.0.2:1234")?, // no local mapping for this
631     );
632 
633     let result = nat.translate_inbound(&before).await;
634     assert!(result.is_err(), "should fail");
635 
636     Ok(())
637 }
638