1#![warn(missing_docs, unreachable_patterns)]
6
7pub mod guest;
11
12use fuchsia_sync::Mutex;
13use std::borrow::Cow;
14use std::collections::HashSet;
15use std::num::NonZeroU64;
16use std::ops::DerefMut as _;
17use std::path::Path;
18use std::pin::pin;
19use std::sync::Arc;
20use zx::AsHandleRef;
21
22use fidl::endpoints::{ProtocolMarker, Proxy as _};
23use fidl_fuchsia_net_dhcp_ext::{self as fnet_dhcp_ext, ClientProviderExt};
24use fidl_fuchsia_net_ext::{self as fnet_ext};
25use fidl_fuchsia_net_interfaces_ext::admin::Control;
26use fidl_fuchsia_net_interfaces_ext::{self as fnet_interfaces_ext};
27use fnet_ext::{FromExt as _, IntoExt as _};
28
29use {
30 fidl_fuchsia_hardware_network as fnetwork, fidl_fuchsia_io as fio, fidl_fuchsia_net as fnet,
31 fidl_fuchsia_net_dhcp as fnet_dhcp, fidl_fuchsia_net_interfaces as fnet_interfaces,
32 fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin,
33 fidl_fuchsia_net_neighbor as fnet_neighbor, fidl_fuchsia_net_resources as fnet_resources,
34 fidl_fuchsia_net_root as fnet_root, fidl_fuchsia_net_routes as fnet_routes,
35 fidl_fuchsia_net_routes_admin as fnet_routes_admin,
36 fidl_fuchsia_net_routes_ext as fnet_routes_ext, fidl_fuchsia_net_stack as fnet_stack,
37 fidl_fuchsia_netemul as fnetemul, fidl_fuchsia_netemul_network as fnetemul_network,
38 fidl_fuchsia_posix_socket as fposix_socket, fidl_fuchsia_posix_socket_ext as fposix_socket_ext,
39 fidl_fuchsia_posix_socket_packet as fposix_socket_packet,
40 fidl_fuchsia_posix_socket_raw as fposix_socket_raw,
41};
42
43use anyhow::{Context as _, anyhow};
44use futures::future::{FutureExt as _, LocalBoxFuture, TryFutureExt as _};
45use futures::{SinkExt as _, TryStreamExt as _};
46use net_types::SpecifiedAddr;
47use net_types::ip::{GenericOverIp, Ip, Ipv4, Ipv6, Subnet};
48
49type Result<T = ()> = std::result::Result<T, anyhow::Error>;
50
51pub const DEFAULT_MTU: u16 = 1500;
53
54pub const NETDEVICE_DEVFS_PATH: &'static str = "class/network";
56
57pub fn devfs_device_path(node_name: &str) -> std::path::PathBuf {
59 std::path::Path::new(NETDEVICE_DEVFS_PATH).join(node_name)
60}
61
62pub fn new_endpoint_config(
64 mtu: u16,
65 mac: Option<fnet::MacAddress>,
66) -> fnetemul_network::EndpointConfig {
67 fnetemul_network::EndpointConfig {
68 mtu,
69 mac: mac.map(Box::new),
70 port_class: fnetwork::PortClass::Virtual,
71 }
72}
73
74#[must_use]
81pub struct TestSandbox {
82 sandbox: fnetemul::SandboxProxy,
83}
84
85impl TestSandbox {
86 pub fn new() -> Result<TestSandbox> {
88 fuchsia_component::client::connect_to_protocol::<fnetemul::SandboxMarker>()
89 .context("failed to connect to sandbox protocol")
90 .map(|sandbox| TestSandbox { sandbox })
91 }
92
93 pub fn create_realm<'a, I>(
95 &'a self,
96 name: impl Into<Cow<'a, str>>,
97 children: I,
98 ) -> Result<TestRealm<'a>>
99 where
100 I: IntoIterator,
101 I::Item: Into<fnetemul::ChildDef>,
102 {
103 let (realm, server) = fidl::endpoints::create_proxy::<fnetemul::ManagedRealmMarker>();
104 let name = name.into();
105 self.sandbox.create_realm(
106 server,
107 fnetemul::RealmOptions {
108 name: Some(name.clone().into_owned()),
109 children: Some(children.into_iter().map(Into::into).collect()),
110 ..Default::default()
111 },
112 )?;
113 Ok(TestRealm(Arc::new(TestRealmInner {
114 realm,
115 name,
116 _sandbox: self,
117 shutdown_on_drop: Mutex::new(ShutdownOnDropConfig {
118 enabled: true,
119 ignore_monikers: HashSet::new(),
120 }),
121 })))
122 }
123
124 pub fn create_empty_realm<'a>(
126 &'a self,
127 name: impl Into<Cow<'a, str>>,
128 ) -> Result<TestRealm<'a>> {
129 self.create_realm(name, std::iter::empty::<fnetemul::ChildDef>())
130 }
131
132 fn get_network_context(&self) -> Result<fnetemul_network::NetworkContextProxy> {
134 let (ctx, server) =
135 fidl::endpoints::create_proxy::<fnetemul_network::NetworkContextMarker>();
136 self.sandbox.get_network_context(server)?;
137 Ok(ctx)
138 }
139
140 pub fn get_network_manager(&self) -> Result<fnetemul_network::NetworkManagerProxy> {
142 let ctx = self.get_network_context()?;
143 let (network_manager, server) =
144 fidl::endpoints::create_proxy::<fnetemul_network::NetworkManagerMarker>();
145 ctx.get_network_manager(server)?;
146 Ok(network_manager)
147 }
148
149 pub fn get_endpoint_manager(&self) -> Result<fnetemul_network::EndpointManagerProxy> {
151 let ctx = self.get_network_context()?;
152 let (ep_manager, server) =
153 fidl::endpoints::create_proxy::<fnetemul_network::EndpointManagerMarker>();
154 ctx.get_endpoint_manager(server)?;
155 Ok(ep_manager)
156 }
157
158 pub async fn create_network<'a>(
160 &'a self,
161 name: impl Into<Cow<'a, str>>,
162 ) -> Result<TestNetwork<'a>> {
163 let name = name.into();
164 let netm = self.get_network_manager()?;
165 let (status, network) = netm
166 .create_network(
167 &name,
168 &fnetemul_network::NetworkConfig {
169 latency: None,
170 packet_loss: None,
171 reorder: None,
172 ..Default::default()
173 },
174 )
175 .await
176 .context("create_network FIDL error")?;
177 zx::Status::ok(status).context("create_network failed")?;
178 let network = network
179 .ok_or_else(|| anyhow::anyhow!("create_network didn't return a valid network"))?
180 .into_proxy();
181 Ok(TestNetwork { network, name, sandbox: self })
182 }
183
184 pub async fn setup_networks<'a>(
186 &'a self,
187 networks: Vec<fnetemul_network::NetworkSetup>,
188 ) -> Result<TestNetworkSetup<'a>> {
189 let ctx = self.get_network_context()?;
190 let (status, handle) = ctx.setup(&networks).await.context("setup FIDL error")?;
191 zx::Status::ok(status).context("setup failed")?;
192 let handle = handle
193 .ok_or_else(|| anyhow::anyhow!("setup didn't return a valid handle"))?
194 .into_proxy();
195 Ok(TestNetworkSetup { _setup: handle, _sandbox: self })
196 }
197
198 pub async fn create_endpoint<'a, S>(&'a self, name: S) -> Result<TestEndpoint<'a>>
202 where
203 S: Into<Cow<'a, str>>,
204 {
205 self.create_endpoint_with(name, new_endpoint_config(DEFAULT_MTU, None)).await
206 }
207
208 pub async fn create_endpoint_with<'a>(
212 &'a self,
213 name: impl Into<Cow<'a, str>>,
214 config: fnetemul_network::EndpointConfig,
215 ) -> Result<TestEndpoint<'a>> {
216 let name = name.into();
217 let epm = self.get_endpoint_manager()?;
218 let (status, endpoint) =
219 epm.create_endpoint(&name, &config).await.context("create_endpoint FIDL error")?;
220 zx::Status::ok(status).context("create_endpoint failed")?;
221 let endpoint = endpoint
222 .ok_or_else(|| anyhow::anyhow!("create_endpoint didn't return a valid endpoint"))?
223 .into_proxy();
224 Ok(TestEndpoint { endpoint, name, _sandbox: self })
225 }
226}
227
228#[must_use]
232pub struct TestNetworkSetup<'a> {
233 _setup: fnetemul_network::SetupHandleProxy,
234 _sandbox: &'a TestSandbox,
235}
236
237impl TestNetworkSetup<'_> {
238 pub fn into_proxy(self) -> fnetemul_network::SetupHandleProxy {
245 let Self { _setup, _sandbox: _ } = self;
246 _setup
247 }
248}
249
250#[derive(Default)]
252pub struct InterfaceConfig<'a> {
253 pub name: Option<Cow<'a, str>>,
255 pub metric: Option<u32>,
257 pub ipv4_dad_transmits: Option<u16>,
260 pub ipv6_dad_transmits: Option<u16>,
263 pub temporary_addresses: Option<bool>,
268 pub netstack_managed_routes_designation:
273 Option<fnet_interfaces_admin::NetstackManagedRoutesDesignation>,
274}
275
276impl InterfaceConfig<'_> {
277 pub fn use_local_table() -> Self {
279 Self {
280 netstack_managed_routes_designation: Some(
281 fnet_interfaces_admin::NetstackManagedRoutesDesignation::InterfaceLocal(
282 fnet_interfaces_admin::Empty,
283 ),
284 ),
285 ..Default::default()
286 }
287 }
288}
289
290#[derive(Debug)]
291struct ShutdownOnDropConfig {
292 enabled: bool,
293 ignore_monikers: HashSet<String>,
294}
295
296struct TestRealmInner<'a> {
297 realm: fnetemul::ManagedRealmProxy,
298 name: Cow<'a, str>,
299 _sandbox: &'a TestSandbox,
300 shutdown_on_drop: Mutex<ShutdownOnDropConfig>,
301}
302
303impl Drop for TestRealmInner<'_> {
304 fn drop(&mut self) {
305 let ShutdownOnDropConfig { enabled, ignore_monikers } = self.shutdown_on_drop.get_mut();
306 if !*enabled {
307 return;
308 }
309 let ignore_monikers = std::mem::take(ignore_monikers);
310 let mut crashed = match self.shutdown_sync() {
311 Ok(crashed) => crashed,
312 Err(e) => {
313 if !e.is_closed() {
317 panic!("error verifying clean shutdown on test realm {}: {:?}", self.name, e);
318 }
319 return;
320 }
321 };
322
323 crashed.retain(|m| !ignore_monikers.contains(m));
324 if !crashed.is_empty() {
325 panic!(
326 "TestRealm {} found unclean component stops with monikers: {:?}",
327 self.name, crashed
328 );
329 }
330 }
331}
332
333impl TestRealmInner<'_> {
334 fn shutdown_sync(&self) -> std::result::Result<Vec<String>, fidl::Error> {
335 let (listener, server_end) = fidl::endpoints::create_sync_proxy();
336 self.realm.get_crash_listener(server_end)?;
337 self.realm.shutdown()?;
338 let _: zx::Signals = self
340 .realm
341 .as_channel()
342 .as_handle_ref()
343 .wait_one(zx::Signals::CHANNEL_PEER_CLOSED, zx::MonotonicInstant::INFINITE)
344 .to_result()
345 .expect("wait channel closed");
346 let mut unclean_stop = Vec::new();
349 while let Some(unclean) =
350 listener.next(zx::MonotonicInstant::INFINITE).map(|v| (!v.is_empty()).then_some(v))?
351 {
352 unclean_stop.extend(unclean);
353 }
354 Ok(unclean_stop)
355 }
356}
357
358#[must_use]
365#[derive(Clone)]
366pub struct TestRealm<'a>(Arc<TestRealmInner<'a>>);
367
368impl<'a> std::fmt::Debug for TestRealm<'a> {
369 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
370 let Self(inner) = self;
371 let TestRealmInner { realm: _, name, _sandbox, shutdown_on_drop } = &**inner;
372 f.debug_struct("TestRealm")
373 .field("name", name)
374 .field("shutdown_on_drop", shutdown_on_drop)
375 .finish_non_exhaustive()
376 }
377}
378
379impl<'a> TestRealm<'a> {
380 fn realm(&self) -> &fnetemul::ManagedRealmProxy {
381 let Self(inner) = self;
382 &inner.realm
383 }
384
385 pub fn name(&self) -> &str {
387 let Self(inner) = self;
388 &inner.name
389 }
390
391 pub fn set_checked_shutdown_on_drop(&self, shutdown_on_drop: bool) {
398 let Self(inner) = self;
399 inner.shutdown_on_drop.lock().enabled = shutdown_on_drop;
400 }
401
402 pub fn ignore_checked_shutdown_monikers(
408 &self,
409 monikers: impl IntoIterator<Item: Into<String>>,
410 ) {
411 let Self(inner) = self;
412 inner
413 .shutdown_on_drop
414 .lock()
415 .ignore_monikers
416 .extend(monikers.into_iter().map(|m| m.into()));
417 }
418
419 pub fn connect_to_protocol<S>(&self) -> Result<S::Proxy>
421 where
422 S: fidl::endpoints::DiscoverableProtocolMarker,
423 {
424 (|| {
425 let (proxy, server_end) = fidl::endpoints::create_proxy::<S>();
426 self.connect_to_protocol_with_server_end(server_end)
427 .context("connect to protocol name with server end")?;
428 Result::Ok(proxy)
429 })()
430 .context(S::DEBUG_NAME)
431 }
432
433 pub fn connect_to_protocol_from_child<S>(&self, child: &str) -> Result<S::Proxy>
435 where
436 S: fidl::endpoints::DiscoverableProtocolMarker,
437 {
438 (|| {
439 let (proxy, server_end) = fidl::endpoints::create_proxy::<S>();
440 self.connect_to_protocol_from_child_at_path_with_server_end(
441 S::PROTOCOL_NAME,
442 child,
443 server_end,
444 )
445 .context("connect to protocol name with server end")?;
446 Result::Ok(proxy)
447 })()
448 .with_context(|| format!("{} from {child}", S::DEBUG_NAME))
449 }
450
451 pub fn open_diagnostics_directory(&self, child_name: &str) -> Result<fio::DirectoryProxy> {
453 let (proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
454 self.realm()
455 .open_diagnostics_directory(child_name, server_end)
456 .context("open diagnostics dir")?;
457 Ok(proxy)
458 }
459
460 pub fn connect_to_protocol_with_server_end<S: fidl::endpoints::DiscoverableProtocolMarker>(
462 &self,
463 server_end: fidl::endpoints::ServerEnd<S>,
464 ) -> Result {
465 self.realm()
466 .connect_to_protocol(S::PROTOCOL_NAME, None, server_end.into_channel())
467 .context("connect to protocol")
468 }
469
470 pub fn connect_to_protocol_from_child_at_path_with_server_end<
472 S: fidl::endpoints::DiscoverableProtocolMarker,
473 >(
474 &self,
475 protocol_path: &str,
476 child: &str,
477 server_end: fidl::endpoints::ServerEnd<S>,
478 ) -> Result {
479 self.realm()
480 .connect_to_protocol(protocol_path, Some(child), server_end.into_channel())
481 .context("connect to protocol")
482 }
483
484 pub async fn get_moniker(&self) -> Result<String> {
486 self.realm().get_moniker().await.context("failed to call get moniker")
487 }
488
489 pub async fn start_child_component(&self, child_name: &str) -> Result {
491 self.realm()
492 .start_child_component(child_name)
493 .await?
494 .map_err(zx::Status::from_raw)
495 .with_context(|| format!("failed to start child component '{}'", child_name))
496 }
497
498 pub async fn stop_child_component(&self, child_name: &str) -> Result {
500 self.realm()
501 .stop_child_component(child_name)
502 .await?
503 .map_err(zx::Status::from_raw)
504 .with_context(|| format!("failed to stop child component '{}'", child_name))
505 }
506
507 pub async fn join_network<S>(
513 &self,
514 network: &TestNetwork<'a>,
515 ep_name: S,
516 ) -> Result<TestInterface<'a>>
517 where
518 S: Into<Cow<'a, str>>,
519 {
520 self.join_network_with_if_config(network, ep_name, Default::default()).await
521 }
522
523 pub async fn join_network_with_if_config<S>(
529 &self,
530 network: &TestNetwork<'a>,
531 ep_name: S,
532 if_config: InterfaceConfig<'a>,
533 ) -> Result<TestInterface<'a>>
534 where
535 S: Into<Cow<'a, str>>,
536 {
537 let endpoint =
538 network.create_endpoint(ep_name).await.context("failed to create endpoint")?;
539 self.install_endpoint(endpoint, if_config).await
540 }
541
542 pub async fn join_network_with(
553 &self,
554 network: &TestNetwork<'a>,
555 ep_name: impl Into<Cow<'a, str>>,
556 ep_config: fnetemul_network::EndpointConfig,
557 if_config: InterfaceConfig<'a>,
558 ) -> Result<TestInterface<'a>> {
559 let installer = self
560 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
561 .context("failed to connect to fuchsia.net.interfaces.admin.Installer")?;
562 let interface_state = self
563 .connect_to_protocol::<fnet_interfaces::StateMarker>()
564 .context("failed to connect to fuchsia.net.interfaces.State")?;
565 let (endpoint, id, control, device_control) = self
566 .join_network_with_installer(
567 network,
568 installer,
569 interface_state,
570 ep_name,
571 ep_config,
572 if_config,
573 )
574 .await?;
575
576 Ok(TestInterface {
577 endpoint,
578 id,
579 realm: self.clone(),
580 control,
581 device_control: Some(device_control),
582 dhcp_client_task: futures::lock::Mutex::default(),
583 })
584 }
585
586 pub async fn join_network_with_installer(
597 &self,
598 network: &TestNetwork<'a>,
599 installer: fnet_interfaces_admin::InstallerProxy,
600 interface_state: fnet_interfaces::StateProxy,
601 ep_name: impl Into<Cow<'a, str>>,
602 ep_config: fnetemul_network::EndpointConfig,
603 if_config: InterfaceConfig<'a>,
604 ) -> Result<(TestEndpoint<'a>, u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
605 let endpoint = network
606 .create_endpoint_with(ep_name, ep_config)
607 .await
608 .context("failed to create endpoint")?;
609 let (id, control, device_control) = self
610 .install_endpoint_with_installer(installer, interface_state, &endpoint, if_config)
611 .await?;
612 Ok((endpoint, id, control, device_control))
613 }
614
615 pub async fn install_endpoint_with_installer(
623 &self,
624 installer: fnet_interfaces_admin::InstallerProxy,
625 interface_state: fnet_interfaces::StateProxy,
626 endpoint: &TestEndpoint<'a>,
627 if_config: InterfaceConfig<'a>,
628 ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
629 let (id, control, device_control) =
630 endpoint.install(installer, if_config).await.context("failed to add endpoint")?;
631
632 endpoint.set_link_up(true).await.context("failed to start endpoint")?;
633 let _did_enable: bool = control
634 .enable()
635 .await
636 .map_err(anyhow::Error::new)
637 .and_then(|res| {
638 res.map_err(|e: fnet_interfaces_admin::ControlEnableError| {
639 anyhow::anyhow!("{:?}", e)
640 })
641 })
642 .context("failed to enable interface")?;
643
644 fnet_interfaces_ext::wait_interface_with_id(
647 fnet_interfaces_ext::event_stream_from_state::<fnet_interfaces_ext::DefaultInterest>(
648 &interface_state,
649 Default::default(),
650 )?,
651 &mut fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(id),
652 |properties_and_state| properties_and_state.properties.online.then_some(()),
653 )
654 .await
655 .context("failed to observe interface up")?;
656
657 Ok((id, control, device_control))
658 }
659
660 pub async fn install_endpoint(
664 &self,
665 endpoint: TestEndpoint<'a>,
666 if_config: InterfaceConfig<'a>,
667 ) -> Result<TestInterface<'a>> {
668 let installer = self
669 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
670 .context("failed to connect to fuchsia.net.interfaces.admin.Installer")?;
671 let interface_state = self
672 .connect_to_protocol::<fnet_interfaces::StateMarker>()
673 .context("failed to connect to fuchsia.net.interfaces.State")?;
674 let (id, control, device_control) = self
675 .install_endpoint_with_installer(installer, interface_state, &endpoint, if_config)
676 .await?;
677 Ok(TestInterface {
678 endpoint,
679 id,
680 realm: self.clone(),
681 control,
682 device_control: Some(device_control),
683 dhcp_client_task: futures::lock::Mutex::default(),
684 })
685 }
686
687 pub async fn add_raw_device(
689 &self,
690 path: &Path,
691 device: fidl::endpoints::ClientEnd<fnetemul_network::DeviceProxy_Marker>,
692 ) -> Result {
693 let path = path.to_str().with_context(|| format!("convert {} to str", path.display()))?;
694 self.realm()
695 .add_device(path, device)
696 .await
697 .context("add device")?
698 .map_err(zx::Status::from_raw)
699 .context("add device error")
700 }
701
702 pub async fn add_virtual_device(&self, e: &TestEndpoint<'_>, path: &Path) -> Result {
704 let (device, device_server_end) =
705 fidl::endpoints::create_endpoints::<fnetemul_network::DeviceProxy_Marker>();
706 e.get_proxy_(device_server_end).context("get proxy")?;
707
708 self.add_raw_device(path, device).await
709 }
710
711 pub async fn remove_virtual_device(&self, path: &Path) -> Result {
713 let path = path.to_str().with_context(|| format!("convert {} to str", path.display()))?;
714 self.realm()
715 .remove_device(path)
716 .await
717 .context("remove device")?
718 .map_err(zx::Status::from_raw)
719 .context("remove device error")
720 }
721
722 pub async fn datagram_socket(
725 &self,
726 domain: fposix_socket::Domain,
727 proto: fposix_socket::DatagramSocketProtocol,
728 ) -> Result<socket2::Socket> {
729 let socket_provider = self
730 .connect_to_protocol::<fposix_socket::ProviderMarker>()
731 .context("failed to connect to socket provider")?;
732
733 fposix_socket_ext::datagram_socket(&socket_provider, domain, proto)
734 .await
735 .context("failed to call socket")?
736 .context("failed to create socket")
737 }
738
739 pub async fn datagram_socket_with_options(
743 &self,
744 domain: fposix_socket::Domain,
745 proto: fposix_socket::DatagramSocketProtocol,
746 options: fposix_socket::SocketCreationOptions,
747 ) -> Result<socket2::Socket> {
748 let socket_provider = self
749 .connect_to_protocol::<fposix_socket::ProviderMarker>()
750 .context("failed to connect to socket provider")?;
751
752 fposix_socket_ext::datagram_socket_with_options(&socket_provider, domain, proto, options)
753 .await
754 .context("failed to call socket")?
755 .context("failed to create socket")
756 }
757
758 pub async fn raw_socket(
761 &self,
762 domain: fposix_socket::Domain,
763 association: fposix_socket_raw::ProtocolAssociation,
764 ) -> Result<socket2::Socket> {
765 let socket_provider = self
766 .connect_to_protocol::<fposix_socket_raw::ProviderMarker>()
767 .context("failed to connect to socket provider")?;
768 let sock = socket_provider
769 .socket(domain, &association)
770 .await
771 .context("failed to call socket")?
772 .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
773 .context("failed to create socket")?;
774
775 Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
776 }
777
778 pub async fn packet_socket(&self, kind: fposix_socket_packet::Kind) -> Result<socket2::Socket> {
783 let socket_provider = self
784 .connect_to_protocol::<fposix_socket_packet::ProviderMarker>()
785 .context("failed to connect to socket provider")?;
786
787 fposix_socket_ext::packet_socket(&socket_provider, kind)
788 .await
789 .context("failed to call socket")?
790 .context("failed to create socket")
791 }
792
793 pub async fn stream_socket(
796 &self,
797 domain: fposix_socket::Domain,
798 proto: fposix_socket::StreamSocketProtocol,
799 ) -> Result<socket2::Socket> {
800 let socket_provider = self
801 .connect_to_protocol::<fposix_socket::ProviderMarker>()
802 .context("failed to connect to socket provider")?;
803 let sock = socket_provider
804 .stream_socket(domain, proto)
805 .await
806 .context("failed to call socket")?
807 .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
808 .context("failed to create socket")?;
809
810 Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
811 }
812
813 pub async fn stream_socket_with_options(
817 &self,
818 domain: fposix_socket::Domain,
819 proto: fposix_socket::StreamSocketProtocol,
820 options: fposix_socket::SocketCreationOptions,
821 ) -> Result<socket2::Socket> {
822 let socket_provider = self
823 .connect_to_protocol::<fposix_socket::ProviderMarker>()
824 .context("failed to connect to socket provider")?;
825 let sock = socket_provider
826 .stream_socket_with_options(domain, proto, options)
827 .await
828 .context("failed to call socket")?
829 .map_err(|e| std::io::Error::from_raw_os_error(e.into_primitive()))
830 .context("failed to create socket")?;
831
832 Ok(fdio::create_fd(sock.into()).context("failed to create fd")?.into())
833 }
834 pub async fn shutdown(&self) -> Result {
841 self.realm().shutdown().context("call shutdown")?;
842 self.set_checked_shutdown_on_drop(false);
845 let events = self
846 .realm()
847 .take_event_stream()
848 .try_collect::<Vec<_>>()
849 .await
850 .context("error on realm event stream")?;
851 assert_matches::assert_matches!(events[..], [fnetemul::ManagedRealmEvent::OnShutdown {}]);
853 Ok(())
854 }
855
856 pub async fn get_crash_stream(&self) -> Result<impl futures::Stream<Item = Result<String>>> {
858 let (listener, server_end) = fidl::endpoints::create_proxy();
859 self.realm().get_crash_listener(server_end).context("creating CrashListener")?;
860 Ok(futures::stream::try_unfold(listener, |listener| async move {
861 let next = listener.next().await.context("listener fetch next moniker")?;
862 Result::Ok(if next.is_empty() {
863 None
864 } else {
865 Some((futures::stream::iter(next.into_iter().map(Ok)), listener))
866 })
867 })
868 .try_flatten())
869 }
870
871 pub async fn icmp_socket<Ip: ping::FuchsiaIpExt>(
873 &self,
874 ) -> Result<fuchsia_async::net::DatagramSocket> {
875 let sock = self
876 .datagram_socket(Ip::DOMAIN_FIDL, fposix_socket::DatagramSocketProtocol::IcmpEcho)
877 .await
878 .context("failed to create ICMP datagram socket")?;
879 fuchsia_async::net::DatagramSocket::new_from_socket(sock)
880 .context("failed to create async ICMP datagram socket")
881 }
882
883 pub async fn ping_once<Ip: ping::FuchsiaIpExt>(&self, addr: Ip::SockAddr, seq: u16) -> Result {
885 let icmp_sock = self.icmp_socket::<Ip>().await?;
886
887 const MESSAGE: &'static str = "hello, world";
888 let (mut sink, mut stream) = ping::new_unicast_sink_and_stream::<
889 Ip,
890 _,
891 { MESSAGE.len() + ping::ICMP_HEADER_LEN },
892 >(&icmp_sock, &addr, MESSAGE.as_bytes());
893
894 let send_fut = sink.send(seq).map_err(anyhow::Error::new);
895 let recv_fut = stream.try_next().map(|r| match r {
896 Ok(Some(got)) if got == seq => Ok(()),
897 Ok(Some(got)) => Err(anyhow!("unexpected echo reply; got: {}, want: {}", got, seq)),
898 Ok(None) => Err(anyhow!("echo reply stream ended unexpectedly")),
899 Err(e) => Err(anyhow::Error::from(e)),
900 });
901
902 let ((), ()) = futures::future::try_join(send_fut, recv_fut)
903 .await
904 .with_context(|| format!("failed to ping from {} to {}", self.name(), addr,))?;
905 Ok(())
906 }
907
908 pub async fn add_neighbor_entry(
912 &self,
913 interface: u64,
914 addr: fnet::IpAddress,
915 mac: fnet::MacAddress,
916 ) -> Result {
917 let controller = self
918 .connect_to_protocol::<fnet_neighbor::ControllerMarker>()
919 .context("connect to protocol")?;
920 controller
921 .add_entry(interface, &addr, &mac)
922 .await
923 .context("add_entry")?
924 .map_err(|e| anyhow::anyhow!("add_entry failed: {e:?}"))
925 }
926
927 pub fn get_interface_event_stream(
930 &self,
931 ) -> Result<
932 impl futures::Stream<
933 Item = std::result::Result<
934 fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
935 fidl::Error,
936 >,
937 >,
938 > {
939 self.get_interface_event_stream_with_interest::<fnet_interfaces_ext::DefaultInterest>()
940 }
941
942 pub fn get_interface_event_stream_with_interest<I: fnet_interfaces_ext::FieldInterests>(
945 &self,
946 ) -> Result<
947 impl futures::Stream<
948 Item = std::result::Result<fnet_interfaces_ext::EventWithInterest<I>, fidl::Error>,
949 >,
950 > {
951 let interface_state = self
952 .connect_to_protocol::<fnet_interfaces::StateMarker>()
953 .context("connect to protocol")?;
954 fnet_interfaces_ext::event_stream_from_state::<I>(&interface_state, Default::default())
955 .context("get interface event stream")
956 }
957
958 pub async fn main_table_id<
960 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
961 >(
962 &self,
963 ) -> u32 {
964 let main_route_table = self
965 .connect_to_protocol::<I::RouteTableMarker>()
966 .expect("failed to connect to main route table");
967 fnet_routes_ext::admin::get_table_id::<I>(&main_route_table)
968 .await
969 .expect("failed to get_table_id")
970 .get()
971 }
972}
973
974#[must_use]
979pub struct TestNetwork<'a> {
980 network: fnetemul_network::NetworkProxy,
981 name: Cow<'a, str>,
982 sandbox: &'a TestSandbox,
983}
984
985impl<'a> std::fmt::Debug for TestNetwork<'a> {
986 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
987 let Self { name, network: _, sandbox: _ } = self;
988 f.debug_struct("TestNetwork").field("name", name).finish_non_exhaustive()
989 }
990}
991
992impl<'a> TestNetwork<'a> {
993 pub fn into_proxy(self) -> fnetemul_network::NetworkProxy {
1000 let Self { network, name: _, sandbox: _ } = self;
1001 network
1002 }
1003
1004 async fn get_client_end_clone(
1006 &self,
1007 ) -> Result<fidl::endpoints::ClientEnd<fnetemul_network::NetworkMarker>> {
1008 let network_manager =
1009 self.sandbox.get_network_manager().context("get_network_manager failed")?;
1010 let client = network_manager
1011 .get_network(&self.name)
1012 .await
1013 .context("get_network failed")?
1014 .with_context(|| format!("no network found with name {}", self.name))?;
1015 Ok(client)
1016 }
1017
1018 pub async fn set_config(&self, config: fnetemul_network::NetworkConfig) -> Result<()> {
1020 let status = self.network.set_config(&config).await.context("call set_config")?;
1021 zx::Status::ok(status).context("set config")
1022 }
1023
1024 pub async fn attach_endpoint(&self, ep: &TestEndpoint<'a>) -> Result<()> {
1026 let status =
1027 self.network.attach_endpoint(&ep.name).await.context("attach_endpoint FIDL error")?;
1028 zx::Status::ok(status).context("attach_endpoint failed")?;
1029 Ok(())
1030 }
1031
1032 pub async fn create_endpoint<S>(&self, name: S) -> Result<TestEndpoint<'a>>
1036 where
1037 S: Into<Cow<'a, str>>,
1038 {
1039 let ep = self
1040 .sandbox
1041 .create_endpoint(name)
1042 .await
1043 .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1044 self.attach_endpoint(&ep).await.with_context(|| {
1045 format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1046 })?;
1047 Ok(ep)
1048 }
1049
1050 pub async fn create_endpoint_with(
1054 &self,
1055 name: impl Into<Cow<'a, str>>,
1056 config: fnetemul_network::EndpointConfig,
1057 ) -> Result<TestEndpoint<'a>> {
1058 let ep = self
1059 .sandbox
1060 .create_endpoint_with(name, config)
1061 .await
1062 .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1063 self.attach_endpoint(&ep).await.with_context(|| {
1064 format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1065 })?;
1066 Ok(ep)
1067 }
1068
1069 pub fn create_fake_endpoint(&self) -> Result<TestFakeEndpoint<'a>> {
1071 let (endpoint, server) =
1072 fidl::endpoints::create_proxy::<fnetemul_network::FakeEndpointMarker>();
1073 self.network.create_fake_endpoint(server)?;
1074 return Ok(TestFakeEndpoint { endpoint, _sandbox: self.sandbox });
1075 }
1076
1077 pub async fn start_capture(&self, name: &str) -> Result<PacketCapture> {
1083 let manager = self.sandbox.get_network_manager()?;
1084 let client = manager.get_network(&self.name).await?.expect("network must exist");
1085 zx::ok(self.network.start_capture(name).await?)?;
1086 let sync_proxy = fnetemul_network::NetworkSynchronousProxy::new(client.into_channel());
1087 Ok(PacketCapture { sync_proxy })
1088 }
1089
1090 pub async fn stop_capture(&self) -> Result<()> {
1092 Ok(self.network.stop_capture().await?)
1093 }
1094}
1095
1096pub struct PacketCapture {
1099 sync_proxy: fnetemul_network::NetworkSynchronousProxy,
1100}
1101
1102impl Drop for PacketCapture {
1103 fn drop(&mut self) {
1104 self.sync_proxy
1105 .stop_capture(zx::MonotonicInstant::INFINITE)
1106 .expect("failed to stop packet capture")
1107 }
1108}
1109
1110#[must_use]
1112pub struct TestEndpoint<'a> {
1113 endpoint: fnetemul_network::EndpointProxy,
1114 name: Cow<'a, str>,
1115 _sandbox: &'a TestSandbox,
1116}
1117
1118impl<'a> TestEndpoint<'a> {
1119 pub async fn get_port_identity_koid(&self) -> Result<zx::Koid> {
1122 let (client, server) = fidl::endpoints::create_proxy::<fnetwork::PortMarker>();
1123 self.get_port(server)?;
1124 let identity = client.get_identity().await?;
1125 Ok(identity.koid()?)
1126 }
1127}
1128
1129impl<'a> std::fmt::Debug for TestEndpoint<'a> {
1130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1131 let Self { endpoint: _, name, _sandbox } = self;
1132 f.debug_struct("TestEndpoint").field("name", name).finish_non_exhaustive()
1133 }
1134}
1135
1136impl<'a> std::ops::Deref for TestEndpoint<'a> {
1137 type Target = fnetemul_network::EndpointProxy;
1138
1139 fn deref(&self) -> &Self::Target {
1140 &self.endpoint
1141 }
1142}
1143
1144#[must_use]
1146pub struct TestFakeEndpoint<'a> {
1147 endpoint: fnetemul_network::FakeEndpointProxy,
1148 _sandbox: &'a TestSandbox,
1149}
1150
1151impl<'a> std::ops::Deref for TestFakeEndpoint<'a> {
1152 type Target = fnetemul_network::FakeEndpointProxy;
1153
1154 fn deref(&self) -> &Self::Target {
1155 &self.endpoint
1156 }
1157}
1158
1159impl<'a> TestFakeEndpoint<'a> {
1160 pub fn frame_stream(
1164 &self,
1165 ) -> impl futures::Stream<Item = std::result::Result<(Vec<u8>, u64), fidl::Error>> + '_ {
1166 futures::stream::try_unfold(&self.endpoint, |ep| ep.read().map_ok(move |r| Some((r, ep))))
1167 }
1168}
1169
1170async fn to_netdevice_inner(
1173 port: fidl::endpoints::ClientEnd<fnetwork::PortMarker>,
1174) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1175 let port = port.into_proxy();
1176 let (device, server_end) = fidl::endpoints::create_endpoints::<fnetwork::DeviceMarker>();
1177 port.get_device(server_end)?;
1178 let port_id = port
1179 .get_info()
1180 .await
1181 .context("get port info")?
1182 .id
1183 .ok_or_else(|| anyhow::anyhow!("missing port id"))?;
1184 Ok((device, port_id))
1185}
1186
1187impl<'a> TestEndpoint<'a> {
1188 pub fn into_proxy(self) -> fnetemul_network::EndpointProxy {
1195 let Self { endpoint, name: _, _sandbox: _ } = self;
1196 endpoint
1197 }
1198
1199 pub async fn get_netdevice(
1204 &self,
1205 ) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1206 let (port, server_end) = fidl::endpoints::create_endpoints();
1207 self.get_port(server_end)
1208 .with_context(|| format!("failed to get device connection for {}", self.name))?;
1209 to_netdevice_inner(port).await
1210 }
1211
1212 pub async fn install(
1218 &self,
1219 installer: fnet_interfaces_admin::InstallerProxy,
1220 InterfaceConfig {
1221 name,
1222 metric,
1223 ipv4_dad_transmits,
1224 ipv6_dad_transmits,
1225 temporary_addresses,
1226 netstack_managed_routes_designation,
1227 }: InterfaceConfig<'_>,
1228 ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1229 let name = name.map(|n| {
1230 truncate_dropping_front(n.into(), fnet_interfaces::INTERFACE_NAME_LENGTH.into())
1231 .to_string()
1232 });
1233 let (device, port_id) = self.get_netdevice().await?;
1234 let device_control = {
1235 let (control, server_end) =
1236 fidl::endpoints::create_proxy::<fnet_interfaces_admin::DeviceControlMarker>();
1237 installer.install_device(device, server_end).context("install device")?;
1238 control
1239 };
1240 let (control, server_end) = Control::create_endpoints().context("create endpoints")?;
1241 device_control
1242 .create_interface(
1243 &port_id,
1244 server_end,
1245 fnet_interfaces_admin::Options {
1246 name,
1247 metric,
1248 netstack_managed_routes_designation,
1249 __source_breaking: fidl::marker::SourceBreaking,
1250 },
1251 )
1252 .context("create interface")?;
1253 if let Some(ipv4_dad_transmits) = ipv4_dad_transmits {
1254 let _: Option<u16> = set_ipv4_dad_transmits(&control, ipv4_dad_transmits)
1255 .await
1256 .context("set dad transmits")?;
1257 }
1258 if let Some(ipv6_dad_transmits) = ipv6_dad_transmits {
1259 let _: Option<u16> = set_ipv6_dad_transmits(&control, ipv6_dad_transmits)
1260 .await
1261 .context("set dad transmits")?;
1262 }
1263 if let Some(enabled) = temporary_addresses {
1264 set_temporary_address_generation_enabled(&control, enabled)
1265 .await
1266 .context("set temporary addresses")?;
1267 }
1268
1269 let id = control.get_id().await.context("get id")?;
1270 Ok((id, control, device_control))
1271 }
1272
1273 pub async fn add_to_stack(
1278 &self,
1279 realm: &TestRealm<'a>,
1280 config: InterfaceConfig<'a>,
1281 ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1282 let installer = realm
1283 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1284 .context("connect to protocol")?;
1285
1286 self.install(installer, config).await
1287 }
1288
1289 pub async fn into_interface_in_realm(self, realm: &TestRealm<'a>) -> Result<TestInterface<'a>> {
1291 self.into_interface_in_realm_with_name(realm, Default::default()).await
1292 }
1293
1294 pub async fn into_interface_in_realm_with_name(
1297 self,
1298 realm: &TestRealm<'a>,
1299 config: InterfaceConfig<'a>,
1300 ) -> Result<TestInterface<'a>> {
1301 let installer = realm
1302 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1303 .context("connect to protocol")?;
1304
1305 let (id, control, device_control) =
1306 self.install(installer, config).await.context("failed to install")?;
1307
1308 Ok(TestInterface {
1309 endpoint: self,
1310 id,
1311 realm: realm.clone(),
1312 control,
1313 device_control: Some(device_control),
1314 dhcp_client_task: futures::lock::Mutex::default(),
1315 })
1316 }
1317}
1318
1319#[derive(Copy, Clone, PartialEq, Debug)]
1321pub enum DhcpClientVersion {
1322 InStack,
1324 OutOfStack,
1326}
1327
1328pub trait DhcpClient {
1330 const DHCP_CLIENT_VERSION: DhcpClientVersion;
1332}
1333
1334pub enum InStack {}
1336
1337impl DhcpClient for InStack {
1338 const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::InStack;
1339}
1340
1341pub enum OutOfStack {}
1343
1344impl DhcpClient for OutOfStack {
1345 const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::OutOfStack;
1346}
1347
1348#[must_use]
1354pub struct TestInterface<'a> {
1355 endpoint: TestEndpoint<'a>,
1356 realm: TestRealm<'a>,
1357 id: u64,
1358 control: Control,
1359 device_control: Option<fnet_interfaces_admin::DeviceControlProxy>,
1360 dhcp_client_task: futures::lock::Mutex<Option<fnet_dhcp_ext::testutil::DhcpClientTask>>,
1361}
1362
1363impl<'a> std::fmt::Debug for TestInterface<'a> {
1364 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1365 let Self { endpoint, id, realm: _, control: _, device_control: _, dhcp_client_task: _ } =
1366 self;
1367 f.debug_struct("TestInterface")
1368 .field("endpoint", endpoint)
1369 .field("id", id)
1370 .finish_non_exhaustive()
1371 }
1372}
1373
1374impl<'a> std::ops::Deref for TestInterface<'a> {
1375 type Target = fnetemul_network::EndpointProxy;
1376
1377 fn deref(&self) -> &Self::Target {
1378 &self.endpoint
1379 }
1380}
1381
1382impl<'a> TestInterface<'a> {
1383 pub fn id(&self) -> u64 {
1385 self.id
1386 }
1387
1388 pub fn endpoint(&self) -> &TestEndpoint<'a> {
1390 &self.endpoint
1391 }
1392
1393 pub fn control(&self) -> &Control {
1395 &self.control
1396 }
1397
1398 pub async fn get_authorization(
1400 &self,
1401 ) -> Result<fnet_resources::GrantForInterfaceAuthorization> {
1402 Ok(self.control.get_authorization_for_interface().await?)
1403 }
1404
1405 pub fn connect_stack(&self) -> Result<fnet_stack::StackProxy> {
1407 self.realm.connect_to_protocol::<fnet_stack::StackMarker>()
1408 }
1409
1410 async fn add_route<
1415 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1416 >(
1417 &self,
1418 destination: Subnet<I::Addr>,
1419 next_hop: Option<SpecifiedAddr<I::Addr>>,
1420 metric: fnet_routes::SpecifiedMetric,
1421 ) -> Result<bool> {
1422 let route_set = self.create_authenticated_global_route_set::<I>().await?;
1423 fnet_routes_ext::admin::add_route::<I>(
1424 &route_set,
1425 &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1426 .try_into()
1427 .expect("convert to FIDL should succeed"),
1428 )
1429 .await
1430 .context("FIDL error adding route")?
1431 .map_err(|e| anyhow::anyhow!("error adding route: {e:?}"))
1432 }
1433
1434 pub async fn add_route_either(
1440 &self,
1441 destination: fnet::Subnet,
1442 next_hop: Option<fnet::IpAddress>,
1443 metric: fnet_routes::SpecifiedMetric,
1444 ) -> Result<bool> {
1445 let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1446 match destination_addr {
1447 fnet::IpAddress::Ipv4(destination_addr) => {
1448 let next_hop = match next_hop {
1449 Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1450 SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1451 .ok_or_else(|| {
1452 anyhow::anyhow!("next hop must not be unspecified address")
1453 })?,
1454 ),
1455 Some(fnet::IpAddress::Ipv6(_)) => {
1456 return Err(anyhow::anyhow!(
1457 "next hop must be same IP version as destination"
1458 ));
1459 }
1460 None => None,
1461 };
1462 self.add_route::<Ipv4>(
1463 Subnet::new(destination_addr.into_ext(), prefix_len)
1464 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1465 next_hop,
1466 metric,
1467 )
1468 .await
1469 }
1470 fnet::IpAddress::Ipv6(destination_addr) => {
1471 let next_hop = match next_hop {
1472 Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1473 SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1474 .ok_or_else(|| {
1475 anyhow::anyhow!("next hop must not be unspecified address")
1476 })?,
1477 ),
1478 Some(fnet::IpAddress::Ipv4(_)) => {
1479 return Err(anyhow::anyhow!(
1480 "next hop must be same IP version as destination"
1481 ));
1482 }
1483 None => None,
1484 };
1485 self.add_route::<Ipv6>(
1486 Subnet::new(destination_addr.into_ext(), prefix_len)
1487 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1488 next_hop,
1489 metric,
1490 )
1491 .await
1492 }
1493 }
1494 }
1495
1496 async fn remove_route<
1501 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1502 >(
1503 &self,
1504 destination: Subnet<I::Addr>,
1505 next_hop: Option<SpecifiedAddr<I::Addr>>,
1506 metric: fnet_routes::SpecifiedMetric,
1507 ) -> Result<bool> {
1508 let route_set = self.create_authenticated_global_route_set::<I>().await?;
1509 fnet_routes_ext::admin::remove_route::<I>(
1510 &route_set,
1511 &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1512 .try_into()
1513 .expect("convert to FIDL should succeed"),
1514 )
1515 .await
1516 .context("FIDL error removing route")?
1517 .map_err(|e| anyhow::anyhow!("error removing route: {e:?}"))
1518 }
1519
1520 async fn remove_route_either(
1526 &self,
1527 destination: fnet::Subnet,
1528 next_hop: Option<fnet::IpAddress>,
1529 metric: fnet_routes::SpecifiedMetric,
1530 ) -> Result<bool> {
1531 let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1532 match destination_addr {
1533 fnet::IpAddress::Ipv4(destination_addr) => {
1534 let next_hop = match next_hop {
1535 Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1536 SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1537 .ok_or_else(|| {
1538 anyhow::anyhow!("next hop must not be unspecified address")
1539 })?,
1540 ),
1541 Some(fnet::IpAddress::Ipv6(_)) => {
1542 return Err(anyhow::anyhow!(
1543 "next hop must be same IP version as destination"
1544 ));
1545 }
1546 None => None,
1547 };
1548 self.remove_route::<Ipv4>(
1549 Subnet::new(destination_addr.into_ext(), prefix_len)
1550 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1551 next_hop,
1552 metric,
1553 )
1554 .await
1555 }
1556 fnet::IpAddress::Ipv6(destination_addr) => {
1557 let next_hop = match next_hop {
1558 Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1559 SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1560 .ok_or_else(|| {
1561 anyhow::anyhow!("next hop must not be unspecified address")
1562 })?,
1563 ),
1564 Some(fnet::IpAddress::Ipv4(_)) => {
1565 return Err(anyhow::anyhow!(
1566 "next hop must be same IP version as destination"
1567 ));
1568 }
1569 None => None,
1570 };
1571 self.remove_route::<Ipv6>(
1572 Subnet::new(destination_addr.into_ext(), prefix_len)
1573 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1574 next_hop,
1575 metric,
1576 )
1577 .await
1578 }
1579 }
1580 }
1581
1582 pub async fn add_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1584 let subnet = fnet_ext::apply_subnet_mask(subnet);
1585 let newly_added = self
1586 .add_route_either(
1587 subnet,
1588 None,
1589 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1590 )
1591 .await?;
1592
1593 if !newly_added {
1594 Err(anyhow::anyhow!(
1595 "route to {subnet:?} on {} should not have already existed",
1596 self.id()
1597 ))
1598 } else {
1599 Ok(())
1600 }
1601 }
1602
1603 pub async fn del_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1605 let subnet = fnet_ext::apply_subnet_mask(subnet);
1606 let newly_removed = self
1607 .remove_route_either(
1608 subnet,
1609 None,
1610 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1611 )
1612 .await?;
1613
1614 if !newly_removed {
1615 Err(anyhow::anyhow!(
1616 "route to {subnet:?} on {} should have previously existed before being removed",
1617 self.id()
1618 ))
1619 } else {
1620 Ok(())
1621 }
1622 }
1623
1624 pub async fn add_default_route_with_metric(
1626 &self,
1627 next_hop: fnet::IpAddress,
1628 metric: fnet_routes::SpecifiedMetric,
1629 ) -> Result<()> {
1630 let corresponding_default_subnet = match next_hop {
1631 fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1632 fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1633 };
1634
1635 let newly_added =
1636 self.add_route_either(corresponding_default_subnet, Some(next_hop), metric).await?;
1637
1638 if !newly_added {
1639 Err(anyhow::anyhow!(
1640 "default route through {} via {next_hop:?} already exists",
1641 self.id()
1642 ))
1643 } else {
1644 Ok(())
1645 }
1646 }
1647
1648 pub async fn add_default_route_with_explicit_metric(
1650 &self,
1651 next_hop: fnet::IpAddress,
1652 metric: u32,
1653 ) -> Result<()> {
1654 self.add_default_route_with_metric(
1655 next_hop,
1656 fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1657 )
1658 .await
1659 }
1660
1661 pub async fn add_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1663 self.add_default_route_with_metric(
1664 next_hop,
1665 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1666 )
1667 .await
1668 }
1669
1670 pub async fn remove_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1672 let corresponding_default_subnet = match next_hop {
1673 fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1674 fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1675 };
1676
1677 let newly_removed = self
1678 .remove_route_either(
1679 corresponding_default_subnet,
1680 Some(next_hop),
1681 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1682 )
1683 .await?;
1684
1685 if !newly_removed {
1686 Err(anyhow::anyhow!(
1687 "default route through {} via {next_hop:?} does not exist",
1688 self.id()
1689 ))
1690 } else {
1691 Ok(())
1692 }
1693 }
1694
1695 pub async fn add_gateway_route(
1697 &self,
1698 destination: fnet::Subnet,
1699 next_hop: fnet::IpAddress,
1700 ) -> Result<()> {
1701 let newly_added = self
1702 .add_route_either(
1703 destination,
1704 Some(next_hop),
1705 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1706 )
1707 .await?;
1708
1709 if !newly_added {
1710 Err(anyhow::anyhow!(
1711 "should have newly added route to {destination:?} via {next_hop:?} through {}",
1712 self.id()
1713 ))
1714 } else {
1715 Ok(())
1716 }
1717 }
1718
1719 pub async fn create_authenticated_global_route_set<
1721 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1722 >(
1723 &self,
1724 ) -> Result<<I::RouteSetMarker as ProtocolMarker>::Proxy> {
1725 #[derive(GenericOverIp)]
1726 #[generic_over_ip(I, Ip)]
1727 struct Out<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt>(
1728 LocalBoxFuture<'a, <I::RouteSetMarker as ProtocolMarker>::Proxy>,
1729 );
1730
1731 let Out(proxy_fut) = I::map_ip_out(
1732 self,
1733 |this| {
1734 Out(this
1735 .get_global_route_set_v4()
1736 .map(|result| result.expect("get global route set"))
1737 .boxed_local())
1738 },
1739 |this| {
1740 Out(this
1741 .get_global_route_set_v6()
1742 .map(|result| result.expect("get global route set"))
1743 .boxed_local())
1744 },
1745 );
1746
1747 let route_set = proxy_fut.await;
1748 let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } =
1749 self.get_authorization().await.expect("get interface grant");
1750 fnet_routes_ext::admin::authenticate_for_interface::<I>(
1751 &route_set,
1752 fnet_resources::ProofOfInterfaceAuthorization { interface_id, token },
1753 )
1754 .await
1755 .expect("authentication should not have FIDL error")
1756 .expect("authentication should succeed");
1757 Ok(route_set)
1758 }
1759
1760 async fn get_global_route_set_v4(&self) -> Result<fnet_routes_admin::RouteSetV4Proxy> {
1761 let root_routes = self
1762 .realm
1763 .connect_to_protocol::<fnet_root::RoutesV4Marker>()
1764 .expect("get fuchsia.net.root.RoutesV4");
1765 let (route_set, server_end) =
1766 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1767 root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1768 Ok(route_set)
1769 }
1770
1771 async fn get_global_route_set_v6(&self) -> Result<fnet_routes_admin::RouteSetV6Proxy> {
1772 let root_routes = self
1773 .realm
1774 .connect_to_protocol::<fnet_root::RoutesV6Marker>()
1775 .expect("get fuchsia.net.root.RoutesV6");
1776 let (route_set, server_end) =
1777 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV6Marker>();
1778 root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1779 Ok(route_set)
1780 }
1781
1782 async fn get_properties(
1784 &self,
1785 included_addresses: fnet_interfaces_ext::IncludedAddresses,
1786 ) -> Result<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>> {
1787 let interface_state = self.realm.connect_to_protocol::<fnet_interfaces::StateMarker>()?;
1788 let properties = fnet_interfaces_ext::existing(
1789 fnet_interfaces_ext::event_stream_from_state(
1790 &interface_state,
1791 fnet_interfaces_ext::WatchOptions { included_addresses, ..Default::default() },
1792 )?,
1793 fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(self.id),
1794 )
1795 .await
1796 .context("failed to get existing interfaces")?;
1797 match properties {
1798 fnet_interfaces_ext::InterfaceState::Unknown(id) => Err(anyhow::anyhow!(
1799 "could not find interface {} for endpoint {}",
1800 id,
1801 self.endpoint.name
1802 )),
1803 fnet_interfaces_ext::InterfaceState::Known(
1804 fnet_interfaces_ext::PropertiesAndState { properties, state: () },
1805 ) => Ok(properties),
1806 }
1807 }
1808
1809 pub async fn get_addrs(
1811 &self,
1812 included_addresses: fnet_interfaces_ext::IncludedAddresses,
1813 ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1814 let fnet_interfaces_ext::Properties { addresses, .. } =
1815 self.get_properties(included_addresses).await?;
1816 Ok(addresses)
1817 }
1818
1819 pub async fn get_interface_name(&self) -> Result<String> {
1821 let fnet_interfaces_ext::Properties { name, .. } =
1822 self.get_properties(Default::default()).await?;
1823 Ok(name)
1824 }
1825
1826 pub async fn get_port_class(&self) -> Result<fnet_interfaces_ext::PortClass> {
1828 let fnet_interfaces_ext::Properties { port_class, .. } =
1829 self.get_properties(Default::default()).await?;
1830 Ok(port_class)
1831 }
1832
1833 pub async fn mac(&self) -> fnet::MacAddress {
1835 let (port, server_end) =
1836 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>();
1837 self.get_port(server_end).expect("get_port");
1838 let (mac_addressing, server_end) =
1839 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::MacAddressingMarker>();
1840 port.get_mac(server_end).expect("get_mac");
1841 mac_addressing.get_unicast_address().await.expect("get_unicast_address")
1842 }
1843
1844 async fn set_dhcp_client_enabled(&self, enable: bool) -> Result<()> {
1845 self.connect_stack()
1846 .context("connect stack")?
1847 .set_dhcp_client_enabled(self.id, enable)
1848 .await
1849 .context("failed to call SetDhcpClientEnabled")?
1850 .map_err(|e| anyhow!("{:?}", e))
1851 }
1852
1853 pub async fn start_dhcp<D: DhcpClient>(&self) -> Result<()> {
1855 match D::DHCP_CLIENT_VERSION {
1856 DhcpClientVersion::InStack => self.start_dhcp_in_stack().await,
1857 DhcpClientVersion::OutOfStack => self.start_dhcp_client_out_of_stack().await,
1858 }
1859 }
1860
1861 async fn start_dhcp_in_stack(&self) -> Result<()> {
1862 self.set_dhcp_client_enabled(true).await.context("failed to start dhcp client")
1863 }
1864
1865 async fn start_dhcp_client_out_of_stack(&self) -> Result<()> {
1866 let Self { endpoint: _, realm, id, control, device_control: _, dhcp_client_task } = self;
1867 let id = NonZeroU64::new(*id).expect("interface ID should be nonzero");
1868 let mut dhcp_client_task = dhcp_client_task.lock().await;
1869 let dhcp_client_task = dhcp_client_task.deref_mut();
1870
1871 let provider = realm
1872 .connect_to_protocol::<fnet_dhcp::ClientProviderMarker>()
1873 .expect("get fuchsia.net.dhcp.ClientProvider");
1874
1875 provider.check_presence().await.expect("check presence should succeed");
1876
1877 let client = provider.new_client_ext(id, fnet_dhcp_ext::default_new_client_params());
1878 let control = control.clone();
1879 let route_set_provider = realm
1880 .connect_to_protocol::<fnet_routes_admin::RouteTableV4Marker>()
1881 .expect("get fuchsia.net.routes.RouteTableV4");
1882 let (route_set, server_end) =
1883 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1884 route_set_provider.new_route_set(server_end).expect("calling new_route_set should succeed");
1885 let task = fnet_dhcp_ext::testutil::DhcpClientTask::new(client, id, route_set, control);
1886 *dhcp_client_task = Some(task);
1887 Ok(())
1888 }
1889
1890 pub async fn stop_dhcp<D: DhcpClient>(&self) -> Result<()> {
1892 match D::DHCP_CLIENT_VERSION {
1893 DhcpClientVersion::InStack => self.stop_dhcp_in_stack().await,
1894 DhcpClientVersion::OutOfStack => {
1895 self.stop_dhcp_out_of_stack().await;
1896 Ok(())
1897 }
1898 }
1899 }
1900
1901 async fn stop_dhcp_in_stack(&self) -> Result<()> {
1902 self.set_dhcp_client_enabled(false).await.context("failed to stop dhcp client")
1903 }
1904
1905 async fn stop_dhcp_out_of_stack(&self) {
1906 let Self { endpoint: _, realm: _, id: _, control: _, device_control: _, dhcp_client_task } =
1907 self;
1908 let mut dhcp_client_task = dhcp_client_task.lock().await;
1909 if let Some(task) = dhcp_client_task.deref_mut().take() {
1910 task.shutdown().await.expect("client shutdown should succeed");
1911 }
1912 }
1913
1914 pub async fn add_address_and_wait_until(
1916 &self,
1917 subnet: fnet::Subnet,
1918 state: fnet_interfaces::AddressAssignmentState,
1919 ) -> Result<()> {
1920 let (address_state_provider, server) =
1921 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1922 address_state_provider.detach().context("detach address lifetime")?;
1923 self.control
1924 .add_address(&subnet, &fnet_interfaces_admin::AddressParameters::default(), server)
1925 .context("FIDL error")?;
1926
1927 let mut state_stream =
1928 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1929 fnet_interfaces_ext::admin::wait_assignment_state(&mut state_stream, state).await?;
1930 Ok(())
1931 }
1932
1933 pub async fn add_address(&self, subnet: fnet::Subnet) -> Result<()> {
1936 self.add_address_and_wait_until(subnet, fnet_interfaces::AddressAssignmentState::Assigned)
1937 .await
1938 }
1939
1940 pub async fn add_address_and_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1943 let (address_state_provider, server) =
1944 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1945 address_state_provider.detach().context("detach address lifetime")?;
1946 self.control
1947 .add_address(
1948 &subnet,
1949 &fnet_interfaces_admin::AddressParameters {
1950 add_subnet_route: Some(true),
1951 ..Default::default()
1952 },
1953 server,
1954 )
1955 .context("FIDL error")?;
1956
1957 let state_stream =
1958 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1959 let mut state_stream = pin!(state_stream);
1960
1961 fnet_interfaces_ext::admin::wait_assignment_state(
1962 &mut state_stream,
1963 fnet_interfaces::AddressAssignmentState::Assigned,
1964 )
1965 .await
1966 .context("assignment state")?;
1967 Ok(())
1968 }
1969
1970 pub async fn del_address_and_subnet_route(
1972 &self,
1973 addr_with_prefix: fnet::Subnet,
1974 ) -> Result<bool> {
1975 let did_remove =
1976 self.control.remove_address(&addr_with_prefix).await.context("FIDL error").and_then(
1977 |res| {
1978 res.map_err(|e: fnet_interfaces_admin::ControlRemoveAddressError| {
1979 anyhow::anyhow!("{:?}", e)
1980 })
1981 },
1982 )?;
1983
1984 if did_remove {
1985 let destination = fnet_ext::apply_subnet_mask(addr_with_prefix);
1986 let newly_removed_route = self
1987 .remove_route_either(
1988 destination,
1989 None,
1990 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1991 )
1992 .await?;
1993
1994 let _: bool = newly_removed_route;
1997 }
1998 Ok(did_remove)
1999 }
2000
2001 pub async fn remove_ipv6_linklocal_addresses(
2005 &self,
2006 ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
2007 let mut result = Vec::new();
2008 for address in self.get_addrs(fnet_interfaces_ext::IncludedAddresses::All).await? {
2009 let fnet_interfaces_ext::Address { addr: fnet::Subnet { addr, prefix_len }, .. } =
2010 &address;
2011 match addr {
2012 fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: _ }) => {
2013 continue;
2014 }
2015 fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
2016 let v6_addr = net_types::ip::Ipv6Addr::from_bytes(*addr);
2017 if !v6_addr.is_unicast_link_local() {
2018 continue;
2019 }
2020 }
2021 }
2022 let _newly_removed: bool = self
2023 .del_address_and_subnet_route(fnet::Subnet { addr: *addr, prefix_len: *prefix_len })
2024 .await?;
2025 result.push(address);
2026 }
2027 Ok(result)
2028 }
2029
2030 async fn set_configuration(&self, config: fnet_interfaces_admin::Configuration) -> Result<()> {
2040 let fnet_interfaces_admin::Configuration {
2041 ipv4: previous_ipv4, ipv6: previous_ipv6, ..
2042 } = self
2043 .control()
2044 .set_configuration(&config.clone())
2045 .await
2046 .context("FIDL error")?
2047 .map_err(|e| anyhow!("set configuration error: {:?}", e))?;
2048
2049 fn verify_config_changed<T: Eq>(previous: Option<T>, current: Option<T>) -> Result<()> {
2050 if let Some(current) = current {
2051 let previous = previous.ok_or_else(|| anyhow!("configuration not supported"))?;
2052 if previous == current {
2053 return Err(anyhow!("configuration change is a no-op"));
2054 }
2055 }
2056 Ok(())
2057 }
2058
2059 let fnet_interfaces_admin::Configuration { ipv4, ipv6, .. } = config;
2060 if let Some(fnet_interfaces_admin::Ipv4Configuration {
2061 unicast_forwarding,
2062 multicast_forwarding,
2063 ..
2064 }) = ipv4
2065 {
2066 let fnet_interfaces_admin::Ipv4Configuration {
2067 unicast_forwarding: previous_unicast_forwarding,
2068 multicast_forwarding: previous_multicast_forwarding,
2069 ..
2070 } = previous_ipv4.ok_or_else(|| anyhow!("IPv4 configuration not supported"))?;
2071 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2072 .context("IPv4 unicast forwarding")?;
2073 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2074 .context("IPv4 multicast forwarding")?;
2075 }
2076 if let Some(fnet_interfaces_admin::Ipv6Configuration {
2077 unicast_forwarding,
2078 multicast_forwarding,
2079 ..
2080 }) = ipv6
2081 {
2082 let fnet_interfaces_admin::Ipv6Configuration {
2083 unicast_forwarding: previous_unicast_forwarding,
2084 multicast_forwarding: previous_multicast_forwarding,
2085 ..
2086 } = previous_ipv6.ok_or_else(|| anyhow!("IPv6 configuration not supported"))?;
2087 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2088 .context("IPv6 unicast forwarding")?;
2089 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2090 .context("IPv6 multicast forwarding")?;
2091 }
2092 Ok(())
2093 }
2094
2095 pub async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2097 self.set_configuration(fnet_interfaces_admin::Configuration {
2098 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2099 unicast_forwarding: Some(enabled),
2100 ..Default::default()
2101 }),
2102 ..Default::default()
2103 })
2104 .await
2105 }
2106
2107 pub async fn set_ipv4_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2109 self.set_configuration(fnet_interfaces_admin::Configuration {
2110 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2111 unicast_forwarding: Some(enabled),
2112 ..Default::default()
2113 }),
2114 ..Default::default()
2115 })
2116 .await
2117 }
2118
2119 pub async fn remove(
2122 self,
2123 ) -> Result<(fnetemul_network::EndpointProxy, Option<fnet_interfaces_admin::DeviceControlProxy>)>
2124 {
2125 let Self {
2126 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2127 id: _,
2128 realm: _,
2129 control,
2130 device_control,
2131 dhcp_client_task: _,
2132 } = self;
2133 std::mem::drop(control);
2137 Ok((endpoint, device_control))
2138 }
2139
2140 pub fn remove_device(self) -> (Control, Option<fnet_interfaces_admin::DeviceControlProxy>) {
2144 let Self {
2145 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2146 id: _,
2147 realm: _,
2148 control,
2149 device_control,
2150 dhcp_client_task: _,
2151 } = self;
2152 std::mem::drop(endpoint);
2153 (control, device_control)
2154 }
2155
2156 pub async fn wait_removal(self) -> Result<fnet_interfaces_admin::InterfaceRemovedReason> {
2158 let Self {
2159 endpoint: _endpoint,
2161 id: _,
2162 realm: _,
2163 control,
2164 dhcp_client_task: _,
2165 device_control: _device_control,
2167 } = self;
2168 match control.wait_termination().await {
2169 fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
2170 Err(e).context("waiting interface control termination")
2171 }
2172 fnet_interfaces_ext::admin::TerminalError::Terminal(reason) => Ok(reason),
2173 }
2174 }
2175
2176 pub async fn set_ipv4_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2180 set_ipv4_dad_transmits(self.control(), dad_transmits).await
2181 }
2182
2183 pub async fn set_ipv6_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2187 set_ipv6_dad_transmits(self.control(), dad_transmits).await
2188 }
2189
2190 pub async fn set_temporary_address_generation_enabled(&self, enabled: bool) -> Result<()> {
2193 set_temporary_address_generation_enabled(self.control(), enabled).await
2194 }
2195}
2196
2197async fn set_ipv4_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2198 control
2199 .set_configuration(&fnet_interfaces_admin::Configuration {
2200 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2201 arp: Some(fnet_interfaces_admin::ArpConfiguration {
2202 dad: Some(fnet_interfaces_admin::DadConfiguration {
2203 transmits: Some(dad_transmits),
2204 ..Default::default()
2205 }),
2206 ..Default::default()
2207 }),
2208 ..Default::default()
2209 }),
2210 ..Default::default()
2211 })
2212 .await?
2213 .map(|config| config.ipv4?.arp?.dad?.transmits)
2214 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2215}
2216
2217async fn set_ipv6_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2218 control
2219 .set_configuration(&fnet_interfaces_admin::Configuration {
2220 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2221 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2222 dad: Some(fnet_interfaces_admin::DadConfiguration {
2223 transmits: Some(dad_transmits),
2224 ..Default::default()
2225 }),
2226 ..Default::default()
2227 }),
2228 ..Default::default()
2229 }),
2230 ..Default::default()
2231 })
2232 .await?
2233 .map(|config| config.ipv6?.ndp?.dad?.transmits)
2234 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2235}
2236
2237async fn set_temporary_address_generation_enabled(control: &Control, enabled: bool) -> Result<()> {
2238 let _config: fnet_interfaces_admin::Configuration = control
2239 .set_configuration(&fnet_interfaces_admin::Configuration {
2240 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2241 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2242 slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
2243 temporary_address: Some(enabled),
2244 ..Default::default()
2245 }),
2246 ..Default::default()
2247 }),
2248 ..Default::default()
2249 }),
2250 ..Default::default()
2251 })
2252 .await
2253 .context("FIDL error")?
2254 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))?;
2255 Ok(())
2256}
2257
2258fn get_socket2_domain(addr: &std::net::SocketAddr) -> fposix_socket::Domain {
2260 let domain = match addr {
2261 std::net::SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
2262 std::net::SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
2263 };
2264
2265 domain
2266}
2267
2268pub trait RealmUdpSocket: Sized {
2270 fn bind_in_realm<'a>(
2272 realm: &'a TestRealm<'a>,
2273 addr: std::net::SocketAddr,
2274 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2275
2276 fn bind_in_realm_with_options<'a>(
2278 realm: &'a TestRealm<'a>,
2279 addr: std::net::SocketAddr,
2280 options: fposix_socket::SocketCreationOptions,
2281 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2282}
2283
2284impl RealmUdpSocket for std::net::UdpSocket {
2285 fn bind_in_realm<'a>(
2286 realm: &'a TestRealm<'a>,
2287 addr: std::net::SocketAddr,
2288 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2289 async move {
2290 let sock = realm
2291 .datagram_socket(
2292 get_socket2_domain(&addr),
2293 fposix_socket::DatagramSocketProtocol::Udp,
2294 )
2295 .await
2296 .context("failed to create socket")?;
2297
2298 sock.bind(&addr.into()).context("bind failed")?;
2299
2300 Result::Ok(sock.into())
2301 }
2302 .boxed_local()
2303 }
2304
2305 fn bind_in_realm_with_options<'a>(
2306 realm: &'a TestRealm<'a>,
2307 addr: std::net::SocketAddr,
2308 options: fposix_socket::SocketCreationOptions,
2309 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2310 async move {
2311 let sock = realm
2312 .datagram_socket_with_options(
2313 get_socket2_domain(&addr),
2314 fposix_socket::DatagramSocketProtocol::Udp,
2315 options,
2316 )
2317 .await
2318 .context("failed to create socket")?;
2319
2320 sock.bind(&addr.into()).context("bind failed")?;
2321
2322 Result::Ok(sock.into())
2323 }
2324 .boxed_local()
2325 }
2326}
2327
2328impl RealmUdpSocket for fuchsia_async::net::UdpSocket {
2329 fn bind_in_realm<'a>(
2330 realm: &'a TestRealm<'a>,
2331 addr: std::net::SocketAddr,
2332 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2333 std::net::UdpSocket::bind_in_realm(realm, addr)
2334 .and_then(|udp| {
2335 futures::future::ready(
2336 fuchsia_async::net::UdpSocket::from_socket(udp)
2337 .context("failed to create fuchsia_async socket"),
2338 )
2339 })
2340 .boxed_local()
2341 }
2342
2343 fn bind_in_realm_with_options<'a>(
2344 realm: &'a TestRealm<'a>,
2345 addr: std::net::SocketAddr,
2346 options: fposix_socket::SocketCreationOptions,
2347 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2348 std::net::UdpSocket::bind_in_realm_with_options(realm, addr, options)
2349 .and_then(|udp| {
2350 futures::future::ready(
2351 fuchsia_async::net::UdpSocket::from_socket(udp)
2352 .context("failed to create fuchsia_async socket"),
2353 )
2354 })
2355 .boxed_local()
2356 }
2357}
2358
2359pub trait RealmTcpListener: Sized {
2361 fn listen_in_realm<'a>(
2363 realm: &'a TestRealm<'a>,
2364 addr: std::net::SocketAddr,
2365 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2366 Self::listen_in_realm_with(realm, addr, |_: &socket2::Socket| Ok(()))
2367 }
2368
2369 fn listen_in_realm_with<'a>(
2372 realm: &'a TestRealm<'a>,
2373 addr: std::net::SocketAddr,
2374 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2375 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2376}
2377
2378impl RealmTcpListener for std::net::TcpListener {
2379 fn listen_in_realm_with<'a>(
2380 realm: &'a TestRealm<'a>,
2381 addr: std::net::SocketAddr,
2382 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2383 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2384 async move {
2385 let sock = realm
2386 .stream_socket(get_socket2_domain(&addr), fposix_socket::StreamSocketProtocol::Tcp)
2387 .await
2388 .context("failed to create server socket")?;
2389 setup(&sock)?;
2390 sock.bind(&addr.into()).context("failed to bind server socket")?;
2391 sock.listen(128).context("failed to listen on server socket")?;
2394
2395 Result::Ok(sock.into())
2396 }
2397 .boxed_local()
2398 }
2399}
2400
2401impl RealmTcpListener for fuchsia_async::net::TcpListener {
2402 fn listen_in_realm_with<'a>(
2403 realm: &'a TestRealm<'a>,
2404 addr: std::net::SocketAddr,
2405 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2406 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2407 std::net::TcpListener::listen_in_realm_with(realm, addr, setup)
2408 .and_then(|listener| {
2409 futures::future::ready(
2410 fuchsia_async::net::TcpListener::from_std(listener)
2411 .context("failed to create fuchsia_async socket"),
2412 )
2413 })
2414 .boxed_local()
2415 }
2416}
2417
2418pub trait RealmTcpStream: Sized {
2420 fn connect_in_realm<'a>(
2422 realm: &'a TestRealm<'a>,
2423 addr: std::net::SocketAddr,
2424 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2425
2426 fn bind_and_connect_in_realm<'a>(
2428 realm: &'a TestRealm<'a>,
2429 local: std::net::SocketAddr,
2430 dst: std::net::SocketAddr,
2431 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2432
2433 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2438 realm: &'a TestRealm<'a>,
2439 dst: std::net::SocketAddr,
2440 with_sock: F,
2441 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2442
2443 }
2445
2446impl RealmTcpStream for fuchsia_async::net::TcpStream {
2447 fn connect_in_realm<'a>(
2448 realm: &'a TestRealm<'a>,
2449 addr: std::net::SocketAddr,
2450 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2451 Self::connect_in_realm_with_sock(realm, addr, |_: &socket2::Socket| Ok(()))
2452 }
2453
2454 fn bind_and_connect_in_realm<'a>(
2455 realm: &'a TestRealm<'a>,
2456 local: std::net::SocketAddr,
2457 dst: std::net::SocketAddr,
2458 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2459 Self::connect_in_realm_with_sock(realm, dst, move |sock| {
2460 sock.bind(&local.into()).context("failed to bind")
2461 })
2462 }
2463
2464 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2465 realm: &'a TestRealm<'a>,
2466 dst: std::net::SocketAddr,
2467 with_sock: F,
2468 ) -> futures::future::LocalBoxFuture<'a, Result<fuchsia_async::net::TcpStream>> {
2469 async move {
2470 let sock = realm
2471 .stream_socket(get_socket2_domain(&dst), fposix_socket::StreamSocketProtocol::Tcp)
2472 .await
2473 .context("failed to create socket")?;
2474
2475 with_sock(&sock)?;
2476
2477 let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, dst)
2478 .context("failed to create client tcp stream")?
2479 .await
2480 .context("failed to connect to server")?;
2481
2482 Result::Ok(stream)
2483 }
2484 .boxed_local()
2485 }
2486}
2487
2488fn truncate_dropping_front(s: Cow<'_, str>, len: usize) -> Cow<'_, str> {
2489 match s.len().checked_sub(len) {
2490 None => s,
2491 Some(start) => {
2492 match s {
2496 Cow::Borrowed(s) => Cow::Borrowed(&s[start..]),
2497 Cow::Owned(mut s) => {
2498 let _: std::string::Drain<'_> = s.drain(..start);
2499 Cow::Owned(s)
2500 }
2501 }
2502 }
2503 }
2504}