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