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(zx::Status::from_raw)
925 .context("add_entry failed")
926 }
927
928 pub fn get_interface_event_stream(
931 &self,
932 ) -> Result<
933 impl futures::Stream<
934 Item = std::result::Result<
935 fnet_interfaces_ext::EventWithInterest<fnet_interfaces_ext::DefaultInterest>,
936 fidl::Error,
937 >,
938 >,
939 > {
940 self.get_interface_event_stream_with_interest::<fnet_interfaces_ext::DefaultInterest>()
941 }
942
943 pub fn get_interface_event_stream_with_interest<I: fnet_interfaces_ext::FieldInterests>(
946 &self,
947 ) -> Result<
948 impl futures::Stream<
949 Item = std::result::Result<fnet_interfaces_ext::EventWithInterest<I>, fidl::Error>,
950 >,
951 > {
952 let interface_state = self
953 .connect_to_protocol::<fnet_interfaces::StateMarker>()
954 .context("connect to protocol")?;
955 fnet_interfaces_ext::event_stream_from_state::<I>(&interface_state, Default::default())
956 .context("get interface event stream")
957 }
958
959 pub async fn main_table_id<
961 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
962 >(
963 &self,
964 ) -> u32 {
965 let main_route_table = self
966 .connect_to_protocol::<I::RouteTableMarker>()
967 .expect("failed to connect to main route table");
968 fnet_routes_ext::admin::get_table_id::<I>(&main_route_table)
969 .await
970 .expect("failed to get_table_id")
971 .get()
972 }
973}
974
975#[must_use]
980pub struct TestNetwork<'a> {
981 network: fnetemul_network::NetworkProxy,
982 name: Cow<'a, str>,
983 sandbox: &'a TestSandbox,
984}
985
986impl<'a> std::fmt::Debug for TestNetwork<'a> {
987 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
988 let Self { name, network: _, sandbox: _ } = self;
989 f.debug_struct("TestNetwork").field("name", name).finish_non_exhaustive()
990 }
991}
992
993impl<'a> TestNetwork<'a> {
994 pub fn into_proxy(self) -> fnetemul_network::NetworkProxy {
1001 let Self { network, name: _, sandbox: _ } = self;
1002 network
1003 }
1004
1005 async fn get_client_end_clone(
1007 &self,
1008 ) -> Result<fidl::endpoints::ClientEnd<fnetemul_network::NetworkMarker>> {
1009 let network_manager =
1010 self.sandbox.get_network_manager().context("get_network_manager failed")?;
1011 let client = network_manager
1012 .get_network(&self.name)
1013 .await
1014 .context("get_network failed")?
1015 .with_context(|| format!("no network found with name {}", self.name))?;
1016 Ok(client)
1017 }
1018
1019 pub async fn set_config(&self, config: fnetemul_network::NetworkConfig) -> Result<()> {
1021 let status = self.network.set_config(&config).await.context("call set_config")?;
1022 zx::Status::ok(status).context("set config")
1023 }
1024
1025 pub async fn attach_endpoint(&self, ep: &TestEndpoint<'a>) -> Result<()> {
1027 let status =
1028 self.network.attach_endpoint(&ep.name).await.context("attach_endpoint FIDL error")?;
1029 zx::Status::ok(status).context("attach_endpoint failed")?;
1030 Ok(())
1031 }
1032
1033 pub async fn create_endpoint<S>(&self, name: S) -> Result<TestEndpoint<'a>>
1037 where
1038 S: Into<Cow<'a, str>>,
1039 {
1040 let ep = self
1041 .sandbox
1042 .create_endpoint(name)
1043 .await
1044 .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1045 self.attach_endpoint(&ep).await.with_context(|| {
1046 format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1047 })?;
1048 Ok(ep)
1049 }
1050
1051 pub async fn create_endpoint_with(
1055 &self,
1056 name: impl Into<Cow<'a, str>>,
1057 config: fnetemul_network::EndpointConfig,
1058 ) -> Result<TestEndpoint<'a>> {
1059 let ep = self
1060 .sandbox
1061 .create_endpoint_with(name, config)
1062 .await
1063 .with_context(|| format!("failed to create endpoint for network {}", self.name))?;
1064 self.attach_endpoint(&ep).await.with_context(|| {
1065 format!("failed to attach endpoint {} to network {}", ep.name, self.name)
1066 })?;
1067 Ok(ep)
1068 }
1069
1070 pub fn create_fake_endpoint(&self) -> Result<TestFakeEndpoint<'a>> {
1072 let (endpoint, server) =
1073 fidl::endpoints::create_proxy::<fnetemul_network::FakeEndpointMarker>();
1074 self.network.create_fake_endpoint(server)?;
1075 return Ok(TestFakeEndpoint { endpoint, _sandbox: self.sandbox });
1076 }
1077
1078 pub async fn start_capture(&self, name: &str) -> Result<PacketCapture> {
1084 let manager = self.sandbox.get_network_manager()?;
1085 let client = manager.get_network(&self.name).await?.expect("network must exist");
1086 zx::ok(self.network.start_capture(name).await?)?;
1087 let sync_proxy = fnetemul_network::NetworkSynchronousProxy::new(client.into_channel());
1088 Ok(PacketCapture { sync_proxy })
1089 }
1090
1091 pub async fn stop_capture(&self) -> Result<()> {
1093 Ok(self.network.stop_capture().await?)
1094 }
1095}
1096
1097pub struct PacketCapture {
1100 sync_proxy: fnetemul_network::NetworkSynchronousProxy,
1101}
1102
1103impl Drop for PacketCapture {
1104 fn drop(&mut self) {
1105 self.sync_proxy
1106 .stop_capture(zx::MonotonicInstant::INFINITE)
1107 .expect("failed to stop packet capture")
1108 }
1109}
1110
1111#[must_use]
1113pub struct TestEndpoint<'a> {
1114 endpoint: fnetemul_network::EndpointProxy,
1115 name: Cow<'a, str>,
1116 _sandbox: &'a TestSandbox,
1117}
1118
1119impl<'a> TestEndpoint<'a> {
1120 pub async fn get_port_identity_koid(&self) -> Result<zx::Koid> {
1123 let (client, server) = fidl::endpoints::create_proxy::<fnetwork::PortMarker>();
1124 self.get_port(server)?;
1125 let identity = client.get_identity().await?;
1126 Ok(identity.koid()?)
1127 }
1128}
1129
1130impl<'a> std::fmt::Debug for TestEndpoint<'a> {
1131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1132 let Self { endpoint: _, name, _sandbox } = self;
1133 f.debug_struct("TestEndpoint").field("name", name).finish_non_exhaustive()
1134 }
1135}
1136
1137impl<'a> std::ops::Deref for TestEndpoint<'a> {
1138 type Target = fnetemul_network::EndpointProxy;
1139
1140 fn deref(&self) -> &Self::Target {
1141 &self.endpoint
1142 }
1143}
1144
1145#[must_use]
1147pub struct TestFakeEndpoint<'a> {
1148 endpoint: fnetemul_network::FakeEndpointProxy,
1149 _sandbox: &'a TestSandbox,
1150}
1151
1152impl<'a> std::ops::Deref for TestFakeEndpoint<'a> {
1153 type Target = fnetemul_network::FakeEndpointProxy;
1154
1155 fn deref(&self) -> &Self::Target {
1156 &self.endpoint
1157 }
1158}
1159
1160impl<'a> TestFakeEndpoint<'a> {
1161 pub fn frame_stream(
1165 &self,
1166 ) -> impl futures::Stream<Item = std::result::Result<(Vec<u8>, u64), fidl::Error>> + '_ {
1167 futures::stream::try_unfold(&self.endpoint, |ep| ep.read().map_ok(move |r| Some((r, ep))))
1168 }
1169}
1170
1171async fn to_netdevice_inner(
1174 port: fidl::endpoints::ClientEnd<fnetwork::PortMarker>,
1175) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1176 let port = port.into_proxy();
1177 let (device, server_end) = fidl::endpoints::create_endpoints::<fnetwork::DeviceMarker>();
1178 port.get_device(server_end)?;
1179 let port_id = port
1180 .get_info()
1181 .await
1182 .context("get port info")?
1183 .id
1184 .ok_or_else(|| anyhow::anyhow!("missing port id"))?;
1185 Ok((device, port_id))
1186}
1187
1188impl<'a> TestEndpoint<'a> {
1189 pub fn into_proxy(self) -> fnetemul_network::EndpointProxy {
1196 let Self { endpoint, name: _, _sandbox: _ } = self;
1197 endpoint
1198 }
1199
1200 pub async fn get_netdevice(
1205 &self,
1206 ) -> Result<(fidl::endpoints::ClientEnd<fnetwork::DeviceMarker>, fnetwork::PortId)> {
1207 let (port, server_end) = fidl::endpoints::create_endpoints();
1208 self.get_port(server_end)
1209 .with_context(|| format!("failed to get device connection for {}", self.name))?;
1210 to_netdevice_inner(port).await
1211 }
1212
1213 pub async fn install(
1219 &self,
1220 installer: fnet_interfaces_admin::InstallerProxy,
1221 InterfaceConfig {
1222 name,
1223 metric,
1224 ipv4_dad_transmits,
1225 ipv6_dad_transmits,
1226 temporary_addresses,
1227 netstack_managed_routes_designation,
1228 }: InterfaceConfig<'_>,
1229 ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1230 let name = name.map(|n| {
1231 truncate_dropping_front(n.into(), fnet_interfaces::INTERFACE_NAME_LENGTH.into())
1232 .to_string()
1233 });
1234 let (device, port_id) = self.get_netdevice().await?;
1235 let device_control = {
1236 let (control, server_end) =
1237 fidl::endpoints::create_proxy::<fnet_interfaces_admin::DeviceControlMarker>();
1238 installer.install_device(device, server_end).context("install device")?;
1239 control
1240 };
1241 let (control, server_end) = Control::create_endpoints().context("create endpoints")?;
1242 device_control
1243 .create_interface(
1244 &port_id,
1245 server_end,
1246 fnet_interfaces_admin::Options {
1247 name,
1248 metric,
1249 netstack_managed_routes_designation,
1250 __source_breaking: fidl::marker::SourceBreaking,
1251 },
1252 )
1253 .context("create interface")?;
1254 if let Some(ipv4_dad_transmits) = ipv4_dad_transmits {
1255 let _: Option<u16> = set_ipv4_dad_transmits(&control, ipv4_dad_transmits)
1256 .await
1257 .context("set dad transmits")?;
1258 }
1259 if let Some(ipv6_dad_transmits) = ipv6_dad_transmits {
1260 let _: Option<u16> = set_ipv6_dad_transmits(&control, ipv6_dad_transmits)
1261 .await
1262 .context("set dad transmits")?;
1263 }
1264 if let Some(enabled) = temporary_addresses {
1265 set_temporary_address_generation_enabled(&control, enabled)
1266 .await
1267 .context("set temporary addresses")?;
1268 }
1269
1270 let id = control.get_id().await.context("get id")?;
1271 Ok((id, control, device_control))
1272 }
1273
1274 pub async fn add_to_stack(
1279 &self,
1280 realm: &TestRealm<'a>,
1281 config: InterfaceConfig<'a>,
1282 ) -> Result<(u64, Control, fnet_interfaces_admin::DeviceControlProxy)> {
1283 let installer = realm
1284 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1285 .context("connect to protocol")?;
1286
1287 self.install(installer, config).await
1288 }
1289
1290 pub async fn into_interface_in_realm(self, realm: &TestRealm<'a>) -> Result<TestInterface<'a>> {
1292 self.into_interface_in_realm_with_name(realm, Default::default()).await
1293 }
1294
1295 pub async fn into_interface_in_realm_with_name(
1298 self,
1299 realm: &TestRealm<'a>,
1300 config: InterfaceConfig<'a>,
1301 ) -> Result<TestInterface<'a>> {
1302 let installer = realm
1303 .connect_to_protocol::<fnet_interfaces_admin::InstallerMarker>()
1304 .context("connect to protocol")?;
1305
1306 let (id, control, device_control) =
1307 self.install(installer, config).await.context("failed to install")?;
1308
1309 Ok(TestInterface {
1310 endpoint: self,
1311 id,
1312 realm: realm.clone(),
1313 control,
1314 device_control: Some(device_control),
1315 dhcp_client_task: futures::lock::Mutex::default(),
1316 })
1317 }
1318}
1319
1320#[derive(Copy, Clone, PartialEq, Debug)]
1322pub enum DhcpClientVersion {
1323 InStack,
1325 OutOfStack,
1327}
1328
1329pub trait DhcpClient {
1331 const DHCP_CLIENT_VERSION: DhcpClientVersion;
1333}
1334
1335pub enum InStack {}
1337
1338impl DhcpClient for InStack {
1339 const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::InStack;
1340}
1341
1342pub enum OutOfStack {}
1344
1345impl DhcpClient for OutOfStack {
1346 const DHCP_CLIENT_VERSION: DhcpClientVersion = DhcpClientVersion::OutOfStack;
1347}
1348
1349#[must_use]
1355pub struct TestInterface<'a> {
1356 endpoint: TestEndpoint<'a>,
1357 realm: TestRealm<'a>,
1358 id: u64,
1359 control: Control,
1360 device_control: Option<fnet_interfaces_admin::DeviceControlProxy>,
1361 dhcp_client_task: futures::lock::Mutex<Option<fnet_dhcp_ext::testutil::DhcpClientTask>>,
1362}
1363
1364impl<'a> std::fmt::Debug for TestInterface<'a> {
1365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
1366 let Self { endpoint, id, realm: _, control: _, device_control: _, dhcp_client_task: _ } =
1367 self;
1368 f.debug_struct("TestInterface")
1369 .field("endpoint", endpoint)
1370 .field("id", id)
1371 .finish_non_exhaustive()
1372 }
1373}
1374
1375impl<'a> std::ops::Deref for TestInterface<'a> {
1376 type Target = fnetemul_network::EndpointProxy;
1377
1378 fn deref(&self) -> &Self::Target {
1379 &self.endpoint
1380 }
1381}
1382
1383impl<'a> TestInterface<'a> {
1384 pub fn id(&self) -> u64 {
1386 self.id
1387 }
1388
1389 pub fn endpoint(&self) -> &TestEndpoint<'a> {
1391 &self.endpoint
1392 }
1393
1394 pub fn control(&self) -> &Control {
1396 &self.control
1397 }
1398
1399 pub async fn get_authorization(
1401 &self,
1402 ) -> Result<fnet_resources::GrantForInterfaceAuthorization> {
1403 Ok(self.control.get_authorization_for_interface().await?)
1404 }
1405
1406 pub fn connect_stack(&self) -> Result<fnet_stack::StackProxy> {
1408 self.realm.connect_to_protocol::<fnet_stack::StackMarker>()
1409 }
1410
1411 async fn add_route<
1416 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1417 >(
1418 &self,
1419 destination: Subnet<I::Addr>,
1420 next_hop: Option<SpecifiedAddr<I::Addr>>,
1421 metric: fnet_routes::SpecifiedMetric,
1422 ) -> Result<bool> {
1423 let route_set = self.create_authenticated_global_route_set::<I>().await?;
1424 fnet_routes_ext::admin::add_route::<I>(
1425 &route_set,
1426 &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1427 .try_into()
1428 .expect("convert to FIDL should succeed"),
1429 )
1430 .await
1431 .context("FIDL error adding route")?
1432 .map_err(|e| anyhow::anyhow!("error adding route: {e:?}"))
1433 }
1434
1435 pub async fn add_route_either(
1441 &self,
1442 destination: fnet::Subnet,
1443 next_hop: Option<fnet::IpAddress>,
1444 metric: fnet_routes::SpecifiedMetric,
1445 ) -> Result<bool> {
1446 let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1447 match destination_addr {
1448 fnet::IpAddress::Ipv4(destination_addr) => {
1449 let next_hop = match next_hop {
1450 Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1451 SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1452 .ok_or_else(|| {
1453 anyhow::anyhow!("next hop must not be unspecified address")
1454 })?,
1455 ),
1456 Some(fnet::IpAddress::Ipv6(_)) => {
1457 return Err(anyhow::anyhow!(
1458 "next hop must be same IP version as destination"
1459 ));
1460 }
1461 None => None,
1462 };
1463 self.add_route::<Ipv4>(
1464 Subnet::new(destination_addr.into_ext(), prefix_len)
1465 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1466 next_hop,
1467 metric,
1468 )
1469 .await
1470 }
1471 fnet::IpAddress::Ipv6(destination_addr) => {
1472 let next_hop = match next_hop {
1473 Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1474 SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1475 .ok_or_else(|| {
1476 anyhow::anyhow!("next hop must not be unspecified address")
1477 })?,
1478 ),
1479 Some(fnet::IpAddress::Ipv4(_)) => {
1480 return Err(anyhow::anyhow!(
1481 "next hop must be same IP version as destination"
1482 ));
1483 }
1484 None => None,
1485 };
1486 self.add_route::<Ipv6>(
1487 Subnet::new(destination_addr.into_ext(), prefix_len)
1488 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1489 next_hop,
1490 metric,
1491 )
1492 .await
1493 }
1494 }
1495 }
1496
1497 async fn remove_route<
1502 I: Ip + fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1503 >(
1504 &self,
1505 destination: Subnet<I::Addr>,
1506 next_hop: Option<SpecifiedAddr<I::Addr>>,
1507 metric: fnet_routes::SpecifiedMetric,
1508 ) -> Result<bool> {
1509 let route_set = self.create_authenticated_global_route_set::<I>().await?;
1510 fnet_routes_ext::admin::remove_route::<I>(
1511 &route_set,
1512 &fnet_routes_ext::Route::<I>::new_forward(destination, self.id(), next_hop, metric)
1513 .try_into()
1514 .expect("convert to FIDL should succeed"),
1515 )
1516 .await
1517 .context("FIDL error removing route")?
1518 .map_err(|e| anyhow::anyhow!("error removing route: {e:?}"))
1519 }
1520
1521 async fn remove_route_either(
1527 &self,
1528 destination: fnet::Subnet,
1529 next_hop: Option<fnet::IpAddress>,
1530 metric: fnet_routes::SpecifiedMetric,
1531 ) -> Result<bool> {
1532 let fnet::Subnet { addr: destination_addr, prefix_len } = destination;
1533 match destination_addr {
1534 fnet::IpAddress::Ipv4(destination_addr) => {
1535 let next_hop = match next_hop {
1536 Some(fnet::IpAddress::Ipv4(next_hop)) => Some(
1537 SpecifiedAddr::new(net_types::ip::Ipv4Addr::from_ext(next_hop))
1538 .ok_or_else(|| {
1539 anyhow::anyhow!("next hop must not be unspecified address")
1540 })?,
1541 ),
1542 Some(fnet::IpAddress::Ipv6(_)) => {
1543 return Err(anyhow::anyhow!(
1544 "next hop must be same IP version as destination"
1545 ));
1546 }
1547 None => None,
1548 };
1549 self.remove_route::<Ipv4>(
1550 Subnet::new(destination_addr.into_ext(), prefix_len)
1551 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1552 next_hop,
1553 metric,
1554 )
1555 .await
1556 }
1557 fnet::IpAddress::Ipv6(destination_addr) => {
1558 let next_hop = match next_hop {
1559 Some(fnet::IpAddress::Ipv6(next_hop)) => Some(
1560 SpecifiedAddr::new(net_types::ip::Ipv6Addr::from_ext(next_hop))
1561 .ok_or_else(|| {
1562 anyhow::anyhow!("next hop must not be unspecified address")
1563 })?,
1564 ),
1565 Some(fnet::IpAddress::Ipv4(_)) => {
1566 return Err(anyhow::anyhow!(
1567 "next hop must be same IP version as destination"
1568 ));
1569 }
1570 None => None,
1571 };
1572 self.remove_route::<Ipv6>(
1573 Subnet::new(destination_addr.into_ext(), prefix_len)
1574 .map_err(|e| anyhow::anyhow!("invalid subnet: {e:?}"))?,
1575 next_hop,
1576 metric,
1577 )
1578 .await
1579 }
1580 }
1581 }
1582
1583 pub async fn add_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1585 let subnet = fnet_ext::apply_subnet_mask(subnet);
1586 let newly_added = self
1587 .add_route_either(
1588 subnet,
1589 None,
1590 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1591 )
1592 .await?;
1593
1594 if !newly_added {
1595 Err(anyhow::anyhow!(
1596 "route to {subnet:?} on {} should not have already existed",
1597 self.id()
1598 ))
1599 } else {
1600 Ok(())
1601 }
1602 }
1603
1604 pub async fn del_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1606 let subnet = fnet_ext::apply_subnet_mask(subnet);
1607 let newly_removed = self
1608 .remove_route_either(
1609 subnet,
1610 None,
1611 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1612 )
1613 .await?;
1614
1615 if !newly_removed {
1616 Err(anyhow::anyhow!(
1617 "route to {subnet:?} on {} should have previously existed before being removed",
1618 self.id()
1619 ))
1620 } else {
1621 Ok(())
1622 }
1623 }
1624
1625 pub async fn add_default_route_with_metric(
1627 &self,
1628 next_hop: fnet::IpAddress,
1629 metric: fnet_routes::SpecifiedMetric,
1630 ) -> Result<()> {
1631 let corresponding_default_subnet = match next_hop {
1632 fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1633 fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1634 };
1635
1636 let newly_added =
1637 self.add_route_either(corresponding_default_subnet, Some(next_hop), metric).await?;
1638
1639 if !newly_added {
1640 Err(anyhow::anyhow!(
1641 "default route through {} via {next_hop:?} already exists",
1642 self.id()
1643 ))
1644 } else {
1645 Ok(())
1646 }
1647 }
1648
1649 pub async fn add_default_route_with_explicit_metric(
1651 &self,
1652 next_hop: fnet::IpAddress,
1653 metric: u32,
1654 ) -> Result<()> {
1655 self.add_default_route_with_metric(
1656 next_hop,
1657 fnet_routes::SpecifiedMetric::ExplicitMetric(metric),
1658 )
1659 .await
1660 }
1661
1662 pub async fn add_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1664 self.add_default_route_with_metric(
1665 next_hop,
1666 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1667 )
1668 .await
1669 }
1670
1671 pub async fn remove_default_route(&self, next_hop: fnet::IpAddress) -> Result<()> {
1673 let corresponding_default_subnet = match next_hop {
1674 fnet::IpAddress::Ipv4(_) => net_declare::fidl_subnet!("0.0.0.0/0"),
1675 fnet::IpAddress::Ipv6(_) => net_declare::fidl_subnet!("::/0"),
1676 };
1677
1678 let newly_removed = self
1679 .remove_route_either(
1680 corresponding_default_subnet,
1681 Some(next_hop),
1682 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1683 )
1684 .await?;
1685
1686 if !newly_removed {
1687 Err(anyhow::anyhow!(
1688 "default route through {} via {next_hop:?} does not exist",
1689 self.id()
1690 ))
1691 } else {
1692 Ok(())
1693 }
1694 }
1695
1696 pub async fn add_gateway_route(
1698 &self,
1699 destination: fnet::Subnet,
1700 next_hop: fnet::IpAddress,
1701 ) -> Result<()> {
1702 let newly_added = self
1703 .add_route_either(
1704 destination,
1705 Some(next_hop),
1706 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1707 )
1708 .await?;
1709
1710 if !newly_added {
1711 Err(anyhow::anyhow!(
1712 "should have newly added route to {destination:?} via {next_hop:?} through {}",
1713 self.id()
1714 ))
1715 } else {
1716 Ok(())
1717 }
1718 }
1719
1720 pub async fn create_authenticated_global_route_set<
1722 I: fnet_routes_ext::FidlRouteIpExt + fnet_routes_ext::admin::FidlRouteAdminIpExt,
1723 >(
1724 &self,
1725 ) -> Result<<I::RouteSetMarker as ProtocolMarker>::Proxy> {
1726 #[derive(GenericOverIp)]
1727 #[generic_over_ip(I, Ip)]
1728 struct Out<'a, I: fnet_routes_ext::admin::FidlRouteAdminIpExt>(
1729 LocalBoxFuture<'a, <I::RouteSetMarker as ProtocolMarker>::Proxy>,
1730 );
1731
1732 let Out(proxy_fut) = I::map_ip_out(
1733 self,
1734 |this| {
1735 Out(this
1736 .get_global_route_set_v4()
1737 .map(|result| result.expect("get global route set"))
1738 .boxed_local())
1739 },
1740 |this| {
1741 Out(this
1742 .get_global_route_set_v6()
1743 .map(|result| result.expect("get global route set"))
1744 .boxed_local())
1745 },
1746 );
1747
1748 let route_set = proxy_fut.await;
1749 let fnet_resources::GrantForInterfaceAuthorization { interface_id, token } =
1750 self.get_authorization().await.expect("get interface grant");
1751 fnet_routes_ext::admin::authenticate_for_interface::<I>(
1752 &route_set,
1753 fnet_resources::ProofOfInterfaceAuthorization { interface_id, token },
1754 )
1755 .await
1756 .expect("authentication should not have FIDL error")
1757 .expect("authentication should succeed");
1758 Ok(route_set)
1759 }
1760
1761 async fn get_global_route_set_v4(&self) -> Result<fnet_routes_admin::RouteSetV4Proxy> {
1762 let root_routes = self
1763 .realm
1764 .connect_to_protocol::<fnet_root::RoutesV4Marker>()
1765 .expect("get fuchsia.net.root.RoutesV4");
1766 let (route_set, server_end) =
1767 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1768 root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1769 Ok(route_set)
1770 }
1771
1772 async fn get_global_route_set_v6(&self) -> Result<fnet_routes_admin::RouteSetV6Proxy> {
1773 let root_routes = self
1774 .realm
1775 .connect_to_protocol::<fnet_root::RoutesV6Marker>()
1776 .expect("get fuchsia.net.root.RoutesV6");
1777 let (route_set, server_end) =
1778 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV6Marker>();
1779 root_routes.global_route_set(server_end).expect("calling global_route_set should succeed");
1780 Ok(route_set)
1781 }
1782
1783 async fn get_properties(
1785 &self,
1786 included_addresses: fnet_interfaces_ext::IncludedAddresses,
1787 ) -> Result<fnet_interfaces_ext::Properties<fnet_interfaces_ext::AllInterest>> {
1788 let interface_state = self.realm.connect_to_protocol::<fnet_interfaces::StateMarker>()?;
1789 let properties = fnet_interfaces_ext::existing(
1790 fnet_interfaces_ext::event_stream_from_state(
1791 &interface_state,
1792 fnet_interfaces_ext::WatchOptions { included_addresses, ..Default::default() },
1793 )?,
1794 fnet_interfaces_ext::InterfaceState::<(), _>::Unknown(self.id),
1795 )
1796 .await
1797 .context("failed to get existing interfaces")?;
1798 match properties {
1799 fnet_interfaces_ext::InterfaceState::Unknown(id) => Err(anyhow::anyhow!(
1800 "could not find interface {} for endpoint {}",
1801 id,
1802 self.endpoint.name
1803 )),
1804 fnet_interfaces_ext::InterfaceState::Known(
1805 fnet_interfaces_ext::PropertiesAndState { properties, state: () },
1806 ) => Ok(properties),
1807 }
1808 }
1809
1810 pub async fn get_addrs(
1812 &self,
1813 included_addresses: fnet_interfaces_ext::IncludedAddresses,
1814 ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
1815 let fnet_interfaces_ext::Properties { addresses, .. } =
1816 self.get_properties(included_addresses).await?;
1817 Ok(addresses)
1818 }
1819
1820 pub async fn get_interface_name(&self) -> Result<String> {
1822 let fnet_interfaces_ext::Properties { name, .. } =
1823 self.get_properties(Default::default()).await?;
1824 Ok(name)
1825 }
1826
1827 pub async fn get_port_class(&self) -> Result<fnet_interfaces_ext::PortClass> {
1829 let fnet_interfaces_ext::Properties { port_class, .. } =
1830 self.get_properties(Default::default()).await?;
1831 Ok(port_class)
1832 }
1833
1834 pub async fn mac(&self) -> fnet::MacAddress {
1836 let (port, server_end) =
1837 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::PortMarker>();
1838 self.get_port(server_end).expect("get_port");
1839 let (mac_addressing, server_end) =
1840 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_network::MacAddressingMarker>();
1841 port.get_mac(server_end).expect("get_mac");
1842 mac_addressing.get_unicast_address().await.expect("get_unicast_address")
1843 }
1844
1845 async fn set_dhcp_client_enabled(&self, enable: bool) -> Result<()> {
1846 self.connect_stack()
1847 .context("connect stack")?
1848 .set_dhcp_client_enabled(self.id, enable)
1849 .await
1850 .context("failed to call SetDhcpClientEnabled")?
1851 .map_err(|e| anyhow!("{:?}", e))
1852 }
1853
1854 pub async fn start_dhcp<D: DhcpClient>(&self) -> Result<()> {
1856 match D::DHCP_CLIENT_VERSION {
1857 DhcpClientVersion::InStack => self.start_dhcp_in_stack().await,
1858 DhcpClientVersion::OutOfStack => self.start_dhcp_client_out_of_stack().await,
1859 }
1860 }
1861
1862 async fn start_dhcp_in_stack(&self) -> Result<()> {
1863 self.set_dhcp_client_enabled(true).await.context("failed to start dhcp client")
1864 }
1865
1866 async fn start_dhcp_client_out_of_stack(&self) -> Result<()> {
1867 let Self { endpoint: _, realm, id, control, device_control: _, dhcp_client_task } = self;
1868 let id = NonZeroU64::new(*id).expect("interface ID should be nonzero");
1869 let mut dhcp_client_task = dhcp_client_task.lock().await;
1870 let dhcp_client_task = dhcp_client_task.deref_mut();
1871
1872 let provider = realm
1873 .connect_to_protocol::<fnet_dhcp::ClientProviderMarker>()
1874 .expect("get fuchsia.net.dhcp.ClientProvider");
1875
1876 provider.check_presence().await.expect("check presence should succeed");
1877
1878 let client = provider.new_client_ext(id, fnet_dhcp_ext::default_new_client_params());
1879 let control = control.clone();
1880 let route_set_provider = realm
1881 .connect_to_protocol::<fnet_routes_admin::RouteTableV4Marker>()
1882 .expect("get fuchsia.net.routes.RouteTableV4");
1883 let (route_set, server_end) =
1884 fidl::endpoints::create_proxy::<fnet_routes_admin::RouteSetV4Marker>();
1885 route_set_provider.new_route_set(server_end).expect("calling new_route_set should succeed");
1886 let task = fnet_dhcp_ext::testutil::DhcpClientTask::new(client, id, route_set, control);
1887 *dhcp_client_task = Some(task);
1888 Ok(())
1889 }
1890
1891 pub async fn stop_dhcp<D: DhcpClient>(&self) -> Result<()> {
1893 match D::DHCP_CLIENT_VERSION {
1894 DhcpClientVersion::InStack => self.stop_dhcp_in_stack().await,
1895 DhcpClientVersion::OutOfStack => {
1896 self.stop_dhcp_out_of_stack().await;
1897 Ok(())
1898 }
1899 }
1900 }
1901
1902 async fn stop_dhcp_in_stack(&self) -> Result<()> {
1903 self.set_dhcp_client_enabled(false).await.context("failed to stop dhcp client")
1904 }
1905
1906 async fn stop_dhcp_out_of_stack(&self) {
1907 let Self { endpoint: _, realm: _, id: _, control: _, device_control: _, dhcp_client_task } =
1908 self;
1909 let mut dhcp_client_task = dhcp_client_task.lock().await;
1910 if let Some(task) = dhcp_client_task.deref_mut().take() {
1911 task.shutdown().await.expect("client shutdown should succeed");
1912 }
1913 }
1914
1915 pub async fn add_address_and_wait_until(
1917 &self,
1918 subnet: fnet::Subnet,
1919 state: fnet_interfaces::AddressAssignmentState,
1920 ) -> Result<()> {
1921 let (address_state_provider, server) =
1922 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1923 address_state_provider.detach().context("detach address lifetime")?;
1924 self.control
1925 .add_address(&subnet, &fnet_interfaces_admin::AddressParameters::default(), server)
1926 .context("FIDL error")?;
1927
1928 let mut state_stream =
1929 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1930 fnet_interfaces_ext::admin::wait_assignment_state(&mut state_stream, state).await?;
1931 Ok(())
1932 }
1933
1934 pub async fn add_address(&self, subnet: fnet::Subnet) -> Result<()> {
1937 self.add_address_and_wait_until(subnet, fnet_interfaces::AddressAssignmentState::Assigned)
1938 .await
1939 }
1940
1941 pub async fn add_address_and_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1944 let (address_state_provider, server) =
1945 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1946 address_state_provider.detach().context("detach address lifetime")?;
1947 self.control
1948 .add_address(
1949 &subnet,
1950 &fnet_interfaces_admin::AddressParameters {
1951 add_subnet_route: Some(true),
1952 ..Default::default()
1953 },
1954 server,
1955 )
1956 .context("FIDL error")?;
1957
1958 let state_stream =
1959 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1960 let mut state_stream = pin!(state_stream);
1961
1962 fnet_interfaces_ext::admin::wait_assignment_state(
1963 &mut state_stream,
1964 fnet_interfaces::AddressAssignmentState::Assigned,
1965 )
1966 .await
1967 .context("assignment state")?;
1968 Ok(())
1969 }
1970
1971 pub async fn del_address_and_subnet_route(
1973 &self,
1974 addr_with_prefix: fnet::Subnet,
1975 ) -> Result<bool> {
1976 let did_remove =
1977 self.control.remove_address(&addr_with_prefix).await.context("FIDL error").and_then(
1978 |res| {
1979 res.map_err(|e: fnet_interfaces_admin::ControlRemoveAddressError| {
1980 anyhow::anyhow!("{:?}", e)
1981 })
1982 },
1983 )?;
1984
1985 if did_remove {
1986 let destination = fnet_ext::apply_subnet_mask(addr_with_prefix);
1987 let newly_removed_route = self
1988 .remove_route_either(
1989 destination,
1990 None,
1991 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1992 )
1993 .await?;
1994
1995 let _: bool = newly_removed_route;
1998 }
1999 Ok(did_remove)
2000 }
2001
2002 pub async fn remove_ipv6_linklocal_addresses(
2006 &self,
2007 ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
2008 let mut result = Vec::new();
2009 for address in self.get_addrs(fnet_interfaces_ext::IncludedAddresses::All).await? {
2010 let fnet_interfaces_ext::Address { addr: fnet::Subnet { addr, prefix_len }, .. } =
2011 &address;
2012 match addr {
2013 fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: _ }) => {
2014 continue;
2015 }
2016 fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
2017 let v6_addr = net_types::ip::Ipv6Addr::from_bytes(*addr);
2018 if !v6_addr.is_unicast_link_local() {
2019 continue;
2020 }
2021 }
2022 }
2023 let _newly_removed: bool = self
2024 .del_address_and_subnet_route(fnet::Subnet { addr: *addr, prefix_len: *prefix_len })
2025 .await?;
2026 result.push(address);
2027 }
2028 Ok(result)
2029 }
2030
2031 async fn set_configuration(&self, config: fnet_interfaces_admin::Configuration) -> Result<()> {
2041 let fnet_interfaces_admin::Configuration {
2042 ipv4: previous_ipv4, ipv6: previous_ipv6, ..
2043 } = self
2044 .control()
2045 .set_configuration(&config.clone())
2046 .await
2047 .context("FIDL error")?
2048 .map_err(|e| anyhow!("set configuration error: {:?}", e))?;
2049
2050 fn verify_config_changed<T: Eq>(previous: Option<T>, current: Option<T>) -> Result<()> {
2051 if let Some(current) = current {
2052 let previous = previous.ok_or_else(|| anyhow!("configuration not supported"))?;
2053 if previous == current {
2054 return Err(anyhow!("configuration change is a no-op"));
2055 }
2056 }
2057 Ok(())
2058 }
2059
2060 let fnet_interfaces_admin::Configuration { ipv4, ipv6, .. } = config;
2061 if let Some(fnet_interfaces_admin::Ipv4Configuration {
2062 unicast_forwarding,
2063 multicast_forwarding,
2064 ..
2065 }) = ipv4
2066 {
2067 let fnet_interfaces_admin::Ipv4Configuration {
2068 unicast_forwarding: previous_unicast_forwarding,
2069 multicast_forwarding: previous_multicast_forwarding,
2070 ..
2071 } = previous_ipv4.ok_or_else(|| anyhow!("IPv4 configuration not supported"))?;
2072 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2073 .context("IPv4 unicast forwarding")?;
2074 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2075 .context("IPv4 multicast forwarding")?;
2076 }
2077 if let Some(fnet_interfaces_admin::Ipv6Configuration {
2078 unicast_forwarding,
2079 multicast_forwarding,
2080 ..
2081 }) = ipv6
2082 {
2083 let fnet_interfaces_admin::Ipv6Configuration {
2084 unicast_forwarding: previous_unicast_forwarding,
2085 multicast_forwarding: previous_multicast_forwarding,
2086 ..
2087 } = previous_ipv6.ok_or_else(|| anyhow!("IPv6 configuration not supported"))?;
2088 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2089 .context("IPv6 unicast forwarding")?;
2090 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2091 .context("IPv6 multicast forwarding")?;
2092 }
2093 Ok(())
2094 }
2095
2096 pub async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2098 self.set_configuration(fnet_interfaces_admin::Configuration {
2099 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2100 unicast_forwarding: Some(enabled),
2101 ..Default::default()
2102 }),
2103 ..Default::default()
2104 })
2105 .await
2106 }
2107
2108 pub async fn set_ipv4_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2110 self.set_configuration(fnet_interfaces_admin::Configuration {
2111 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2112 unicast_forwarding: Some(enabled),
2113 ..Default::default()
2114 }),
2115 ..Default::default()
2116 })
2117 .await
2118 }
2119
2120 pub async fn remove(
2123 self,
2124 ) -> Result<(fnetemul_network::EndpointProxy, Option<fnet_interfaces_admin::DeviceControlProxy>)>
2125 {
2126 let Self {
2127 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2128 id: _,
2129 realm: _,
2130 control,
2131 device_control,
2132 dhcp_client_task: _,
2133 } = self;
2134 std::mem::drop(control);
2138 Ok((endpoint, device_control))
2139 }
2140
2141 pub fn remove_device(self) -> (Control, Option<fnet_interfaces_admin::DeviceControlProxy>) {
2145 let Self {
2146 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2147 id: _,
2148 realm: _,
2149 control,
2150 device_control,
2151 dhcp_client_task: _,
2152 } = self;
2153 std::mem::drop(endpoint);
2154 (control, device_control)
2155 }
2156
2157 pub async fn wait_removal(self) -> Result<fnet_interfaces_admin::InterfaceRemovedReason> {
2159 let Self {
2160 endpoint: _endpoint,
2162 id: _,
2163 realm: _,
2164 control,
2165 dhcp_client_task: _,
2166 device_control: _device_control,
2168 } = self;
2169 match control.wait_termination().await {
2170 fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
2171 Err(e).context("waiting interface control termination")
2172 }
2173 fnet_interfaces_ext::admin::TerminalError::Terminal(reason) => Ok(reason),
2174 }
2175 }
2176
2177 pub async fn set_ipv4_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2181 set_ipv4_dad_transmits(self.control(), dad_transmits).await
2182 }
2183
2184 pub async fn set_ipv6_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2188 set_ipv6_dad_transmits(self.control(), dad_transmits).await
2189 }
2190
2191 pub async fn set_temporary_address_generation_enabled(&self, enabled: bool) -> Result<()> {
2194 set_temporary_address_generation_enabled(self.control(), enabled).await
2195 }
2196}
2197
2198async fn set_ipv4_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2199 control
2200 .set_configuration(&fnet_interfaces_admin::Configuration {
2201 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2202 arp: Some(fnet_interfaces_admin::ArpConfiguration {
2203 dad: Some(fnet_interfaces_admin::DadConfiguration {
2204 transmits: Some(dad_transmits),
2205 ..Default::default()
2206 }),
2207 ..Default::default()
2208 }),
2209 ..Default::default()
2210 }),
2211 ..Default::default()
2212 })
2213 .await?
2214 .map(|config| config.ipv4?.arp?.dad?.transmits)
2215 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2216}
2217
2218async fn set_ipv6_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2219 control
2220 .set_configuration(&fnet_interfaces_admin::Configuration {
2221 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2222 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2223 dad: Some(fnet_interfaces_admin::DadConfiguration {
2224 transmits: Some(dad_transmits),
2225 ..Default::default()
2226 }),
2227 ..Default::default()
2228 }),
2229 ..Default::default()
2230 }),
2231 ..Default::default()
2232 })
2233 .await?
2234 .map(|config| config.ipv6?.ndp?.dad?.transmits)
2235 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2236}
2237
2238async fn set_temporary_address_generation_enabled(control: &Control, enabled: bool) -> Result<()> {
2239 let _config: fnet_interfaces_admin::Configuration = control
2240 .set_configuration(&fnet_interfaces_admin::Configuration {
2241 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2242 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2243 slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
2244 temporary_address: Some(enabled),
2245 ..Default::default()
2246 }),
2247 ..Default::default()
2248 }),
2249 ..Default::default()
2250 }),
2251 ..Default::default()
2252 })
2253 .await
2254 .context("FIDL error")?
2255 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))?;
2256 Ok(())
2257}
2258
2259fn get_socket2_domain(addr: &std::net::SocketAddr) -> fposix_socket::Domain {
2261 let domain = match addr {
2262 std::net::SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
2263 std::net::SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
2264 };
2265
2266 domain
2267}
2268
2269pub trait RealmUdpSocket: Sized {
2271 fn bind_in_realm<'a>(
2273 realm: &'a TestRealm<'a>,
2274 addr: std::net::SocketAddr,
2275 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2276
2277 fn bind_in_realm_with_options<'a>(
2279 realm: &'a TestRealm<'a>,
2280 addr: std::net::SocketAddr,
2281 options: fposix_socket::SocketCreationOptions,
2282 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2283}
2284
2285impl RealmUdpSocket for std::net::UdpSocket {
2286 fn bind_in_realm<'a>(
2287 realm: &'a TestRealm<'a>,
2288 addr: std::net::SocketAddr,
2289 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2290 async move {
2291 let sock = realm
2292 .datagram_socket(
2293 get_socket2_domain(&addr),
2294 fposix_socket::DatagramSocketProtocol::Udp,
2295 )
2296 .await
2297 .context("failed to create socket")?;
2298
2299 sock.bind(&addr.into()).context("bind failed")?;
2300
2301 Result::Ok(sock.into())
2302 }
2303 .boxed_local()
2304 }
2305
2306 fn bind_in_realm_with_options<'a>(
2307 realm: &'a TestRealm<'a>,
2308 addr: std::net::SocketAddr,
2309 options: fposix_socket::SocketCreationOptions,
2310 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2311 async move {
2312 let sock = realm
2313 .datagram_socket_with_options(
2314 get_socket2_domain(&addr),
2315 fposix_socket::DatagramSocketProtocol::Udp,
2316 options,
2317 )
2318 .await
2319 .context("failed to create socket")?;
2320
2321 sock.bind(&addr.into()).context("bind failed")?;
2322
2323 Result::Ok(sock.into())
2324 }
2325 .boxed_local()
2326 }
2327}
2328
2329impl RealmUdpSocket for fuchsia_async::net::UdpSocket {
2330 fn bind_in_realm<'a>(
2331 realm: &'a TestRealm<'a>,
2332 addr: std::net::SocketAddr,
2333 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2334 std::net::UdpSocket::bind_in_realm(realm, addr)
2335 .and_then(|udp| {
2336 futures::future::ready(
2337 fuchsia_async::net::UdpSocket::from_socket(udp)
2338 .context("failed to create fuchsia_async socket"),
2339 )
2340 })
2341 .boxed_local()
2342 }
2343
2344 fn bind_in_realm_with_options<'a>(
2345 realm: &'a TestRealm<'a>,
2346 addr: std::net::SocketAddr,
2347 options: fposix_socket::SocketCreationOptions,
2348 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2349 std::net::UdpSocket::bind_in_realm_with_options(realm, addr, options)
2350 .and_then(|udp| {
2351 futures::future::ready(
2352 fuchsia_async::net::UdpSocket::from_socket(udp)
2353 .context("failed to create fuchsia_async socket"),
2354 )
2355 })
2356 .boxed_local()
2357 }
2358}
2359
2360pub trait RealmTcpListener: Sized {
2362 fn listen_in_realm<'a>(
2364 realm: &'a TestRealm<'a>,
2365 addr: std::net::SocketAddr,
2366 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2367 Self::listen_in_realm_with(realm, addr, |_: &socket2::Socket| Ok(()))
2368 }
2369
2370 fn listen_in_realm_with<'a>(
2373 realm: &'a TestRealm<'a>,
2374 addr: std::net::SocketAddr,
2375 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2376 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2377}
2378
2379impl RealmTcpListener for std::net::TcpListener {
2380 fn listen_in_realm_with<'a>(
2381 realm: &'a TestRealm<'a>,
2382 addr: std::net::SocketAddr,
2383 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2384 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2385 async move {
2386 let sock = realm
2387 .stream_socket(get_socket2_domain(&addr), fposix_socket::StreamSocketProtocol::Tcp)
2388 .await
2389 .context("failed to create server socket")?;
2390 setup(&sock)?;
2391 sock.bind(&addr.into()).context("failed to bind server socket")?;
2392 sock.listen(128).context("failed to listen on server socket")?;
2395
2396 Result::Ok(sock.into())
2397 }
2398 .boxed_local()
2399 }
2400}
2401
2402impl RealmTcpListener for fuchsia_async::net::TcpListener {
2403 fn listen_in_realm_with<'a>(
2404 realm: &'a TestRealm<'a>,
2405 addr: std::net::SocketAddr,
2406 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2407 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2408 std::net::TcpListener::listen_in_realm_with(realm, addr, setup)
2409 .and_then(|listener| {
2410 futures::future::ready(
2411 fuchsia_async::net::TcpListener::from_std(listener)
2412 .context("failed to create fuchsia_async socket"),
2413 )
2414 })
2415 .boxed_local()
2416 }
2417}
2418
2419pub trait RealmTcpStream: Sized {
2421 fn connect_in_realm<'a>(
2423 realm: &'a TestRealm<'a>,
2424 addr: std::net::SocketAddr,
2425 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2426
2427 fn bind_and_connect_in_realm<'a>(
2429 realm: &'a TestRealm<'a>,
2430 local: std::net::SocketAddr,
2431 dst: std::net::SocketAddr,
2432 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2433
2434 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2439 realm: &'a TestRealm<'a>,
2440 dst: std::net::SocketAddr,
2441 with_sock: F,
2442 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2443
2444 }
2446
2447impl RealmTcpStream for fuchsia_async::net::TcpStream {
2448 fn connect_in_realm<'a>(
2449 realm: &'a TestRealm<'a>,
2450 addr: std::net::SocketAddr,
2451 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2452 Self::connect_in_realm_with_sock(realm, addr, |_: &socket2::Socket| Ok(()))
2453 }
2454
2455 fn bind_and_connect_in_realm<'a>(
2456 realm: &'a TestRealm<'a>,
2457 local: std::net::SocketAddr,
2458 dst: std::net::SocketAddr,
2459 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2460 Self::connect_in_realm_with_sock(realm, dst, move |sock| {
2461 sock.bind(&local.into()).context("failed to bind")
2462 })
2463 }
2464
2465 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2466 realm: &'a TestRealm<'a>,
2467 dst: std::net::SocketAddr,
2468 with_sock: F,
2469 ) -> futures::future::LocalBoxFuture<'a, Result<fuchsia_async::net::TcpStream>> {
2470 async move {
2471 let sock = realm
2472 .stream_socket(get_socket2_domain(&dst), fposix_socket::StreamSocketProtocol::Tcp)
2473 .await
2474 .context("failed to create socket")?;
2475
2476 with_sock(&sock)?;
2477
2478 let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, dst)
2479 .context("failed to create client tcp stream")?
2480 .await
2481 .context("failed to connect to server")?;
2482
2483 Result::Ok(stream)
2484 }
2485 .boxed_local()
2486 }
2487}
2488
2489fn truncate_dropping_front(s: Cow<'_, str>, len: usize) -> Cow<'_, str> {
2490 match s.len().checked_sub(len) {
2491 None => s,
2492 Some(start) => {
2493 match s {
2497 Cow::Borrowed(s) => Cow::Borrowed(&s[start..]),
2498 Cow::Owned(mut s) => {
2499 let _: std::string::Drain<'_> = s.drain(..start);
2500 Cow::Owned(s)
2501 }
2502 }
2503 }
2504 }
2505}