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