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 add_address_and_wait_until(
1922 &self,
1923 subnet: fnet::Subnet,
1924 state: fnet_interfaces::AddressAssignmentState,
1925 ) -> Result<()> {
1926 let (address_state_provider, server) =
1927 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1928 address_state_provider.detach().context("detach address lifetime")?;
1929 self.control
1930 .add_address(&subnet, &fnet_interfaces_admin::AddressParameters::default(), server)
1931 .context("FIDL error")?;
1932
1933 let mut state_stream =
1934 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1935 fnet_interfaces_ext::admin::wait_assignment_state(&mut state_stream, state).await?;
1936 Ok(())
1937 }
1938
1939 pub async fn add_address(&self, subnet: fnet::Subnet) -> Result<()> {
1942 self.add_address_and_wait_until(subnet, fnet_interfaces::AddressAssignmentState::Assigned)
1943 .await
1944 }
1945
1946 pub async fn add_address_and_subnet_route(&self, subnet: fnet::Subnet) -> Result<()> {
1949 let (address_state_provider, server) =
1950 fidl::endpoints::create_proxy::<fnet_interfaces_admin::AddressStateProviderMarker>();
1951 address_state_provider.detach().context("detach address lifetime")?;
1952 self.control
1953 .add_address(
1954 &subnet,
1955 &fnet_interfaces_admin::AddressParameters {
1956 add_subnet_route: Some(true),
1957 ..Default::default()
1958 },
1959 server,
1960 )
1961 .context("FIDL error")?;
1962
1963 let state_stream =
1964 fnet_interfaces_ext::admin::assignment_state_stream(address_state_provider);
1965 let mut state_stream = pin!(state_stream);
1966
1967 fnet_interfaces_ext::admin::wait_assignment_state(
1968 &mut state_stream,
1969 fnet_interfaces::AddressAssignmentState::Assigned,
1970 )
1971 .await
1972 .context("assignment state")?;
1973 Ok(())
1974 }
1975
1976 pub async fn del_address_and_subnet_route(
1978 &self,
1979 addr_with_prefix: fnet::Subnet,
1980 ) -> Result<bool> {
1981 let did_remove =
1982 self.control.remove_address(&addr_with_prefix).await.context("FIDL error").and_then(
1983 |res| {
1984 res.map_err(|e: fnet_interfaces_admin::ControlRemoveAddressError| {
1985 anyhow::anyhow!("{:?}", e)
1986 })
1987 },
1988 )?;
1989
1990 if did_remove {
1991 let destination = fnet_ext::apply_subnet_mask(addr_with_prefix);
1992 let newly_removed_route = self
1993 .remove_route_either(
1994 destination,
1995 None,
1996 fnet_routes::SpecifiedMetric::InheritedFromInterface(fnet_routes::Empty),
1997 )
1998 .await?;
1999
2000 let _: bool = newly_removed_route;
2003 }
2004 Ok(did_remove)
2005 }
2006
2007 pub async fn remove_ipv6_linklocal_addresses(
2011 &self,
2012 ) -> Result<Vec<fnet_interfaces_ext::Address<fnet_interfaces_ext::AllInterest>>> {
2013 let mut result = Vec::new();
2014 for address in self.get_addrs(fnet_interfaces_ext::IncludedAddresses::All).await? {
2015 let fnet_interfaces_ext::Address { addr: fnet::Subnet { addr, prefix_len }, .. } =
2016 &address;
2017 match addr {
2018 fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr: _ }) => {
2019 continue;
2020 }
2021 fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address { addr }) => {
2022 let v6_addr = net_types::ip::Ipv6Addr::from_bytes(*addr);
2023 if !v6_addr.is_unicast_link_local() {
2024 continue;
2025 }
2026 }
2027 }
2028 let _newly_removed: bool = self
2029 .del_address_and_subnet_route(fnet::Subnet { addr: *addr, prefix_len: *prefix_len })
2030 .await?;
2031 result.push(address);
2032 }
2033 Ok(result)
2034 }
2035
2036 async fn set_configuration(&self, config: fnet_interfaces_admin::Configuration) -> Result<()> {
2046 let fnet_interfaces_admin::Configuration {
2047 ipv4: previous_ipv4, ipv6: previous_ipv6, ..
2048 } = self
2049 .control()
2050 .set_configuration(&config.clone())
2051 .await
2052 .context("FIDL error")?
2053 .map_err(|e| anyhow!("set configuration error: {:?}", e))?;
2054
2055 fn verify_config_changed<T: Eq>(previous: Option<T>, current: Option<T>) -> Result<()> {
2056 if let Some(current) = current {
2057 let previous = previous.ok_or_else(|| anyhow!("configuration not supported"))?;
2058 if previous == current {
2059 return Err(anyhow!("configuration change is a no-op"));
2060 }
2061 }
2062 Ok(())
2063 }
2064
2065 let fnet_interfaces_admin::Configuration { ipv4, ipv6, .. } = config;
2066 if let Some(fnet_interfaces_admin::Ipv4Configuration {
2067 unicast_forwarding,
2068 multicast_forwarding,
2069 ..
2070 }) = ipv4
2071 {
2072 let fnet_interfaces_admin::Ipv4Configuration {
2073 unicast_forwarding: previous_unicast_forwarding,
2074 multicast_forwarding: previous_multicast_forwarding,
2075 ..
2076 } = previous_ipv4.ok_or_else(|| anyhow!("IPv4 configuration not supported"))?;
2077 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2078 .context("IPv4 unicast forwarding")?;
2079 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2080 .context("IPv4 multicast forwarding")?;
2081 }
2082 if let Some(fnet_interfaces_admin::Ipv6Configuration {
2083 unicast_forwarding,
2084 multicast_forwarding,
2085 ..
2086 }) = ipv6
2087 {
2088 let fnet_interfaces_admin::Ipv6Configuration {
2089 unicast_forwarding: previous_unicast_forwarding,
2090 multicast_forwarding: previous_multicast_forwarding,
2091 ..
2092 } = previous_ipv6.ok_or_else(|| anyhow!("IPv6 configuration not supported"))?;
2093 verify_config_changed(previous_unicast_forwarding, unicast_forwarding)
2094 .context("IPv6 unicast forwarding")?;
2095 verify_config_changed(previous_multicast_forwarding, multicast_forwarding)
2096 .context("IPv6 multicast forwarding")?;
2097 }
2098 Ok(())
2099 }
2100
2101 pub async fn set_ipv6_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2103 self.set_configuration(fnet_interfaces_admin::Configuration {
2104 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2105 unicast_forwarding: Some(enabled),
2106 ..Default::default()
2107 }),
2108 ..Default::default()
2109 })
2110 .await
2111 }
2112
2113 pub async fn set_ipv4_forwarding_enabled(&self, enabled: bool) -> Result<()> {
2115 self.set_configuration(fnet_interfaces_admin::Configuration {
2116 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2117 unicast_forwarding: Some(enabled),
2118 ..Default::default()
2119 }),
2120 ..Default::default()
2121 })
2122 .await
2123 }
2124
2125 pub async fn remove(
2128 self,
2129 ) -> Result<(fnetemul_network::EndpointProxy, Option<fnet_interfaces_admin::DeviceControlProxy>)>
2130 {
2131 let Self {
2132 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2133 id: _,
2134 realm: _,
2135 control,
2136 device_control,
2137 dhcp_client_task: _,
2138 } = self;
2139 std::mem::drop(control);
2143 Ok((endpoint, device_control))
2144 }
2145
2146 pub fn remove_device(self) -> (Control, Option<fnet_interfaces_admin::DeviceControlProxy>) {
2150 let Self {
2151 endpoint: TestEndpoint { endpoint, name: _, _sandbox: _ },
2152 id: _,
2153 realm: _,
2154 control,
2155 device_control,
2156 dhcp_client_task: _,
2157 } = self;
2158 std::mem::drop(endpoint);
2159 (control, device_control)
2160 }
2161
2162 pub async fn wait_removal(self) -> Result<fnet_interfaces_admin::InterfaceRemovedReason> {
2164 let Self {
2165 endpoint: _endpoint,
2167 id: _,
2168 realm: _,
2169 control,
2170 dhcp_client_task: _,
2171 device_control: _device_control,
2173 } = self;
2174 match control.wait_termination().await {
2175 fnet_interfaces_ext::admin::TerminalError::Fidl(e) => {
2176 Err(e).context("waiting interface control termination")
2177 }
2178 fnet_interfaces_ext::admin::TerminalError::Terminal(reason) => Ok(reason),
2179 }
2180 }
2181
2182 pub async fn set_ipv4_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2186 set_ipv4_dad_transmits(self.control(), dad_transmits).await
2187 }
2188
2189 pub async fn set_ipv6_dad_transmits(&self, dad_transmits: u16) -> Result<Option<u16>> {
2193 set_ipv6_dad_transmits(self.control(), dad_transmits).await
2194 }
2195
2196 pub async fn set_temporary_address_generation_enabled(&self, enabled: bool) -> Result<()> {
2199 set_temporary_address_generation_enabled(self.control(), enabled).await
2200 }
2201}
2202
2203async fn set_ipv4_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2204 control
2205 .set_configuration(&fnet_interfaces_admin::Configuration {
2206 ipv4: Some(fnet_interfaces_admin::Ipv4Configuration {
2207 arp: Some(fnet_interfaces_admin::ArpConfiguration {
2208 dad: Some(fnet_interfaces_admin::DadConfiguration {
2209 transmits: Some(dad_transmits),
2210 ..Default::default()
2211 }),
2212 ..Default::default()
2213 }),
2214 ..Default::default()
2215 }),
2216 ..Default::default()
2217 })
2218 .await?
2219 .map(|config| config.ipv4?.arp?.dad?.transmits)
2220 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2221}
2222
2223async fn set_ipv6_dad_transmits(control: &Control, dad_transmits: u16) -> Result<Option<u16>> {
2224 control
2225 .set_configuration(&fnet_interfaces_admin::Configuration {
2226 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2227 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2228 dad: Some(fnet_interfaces_admin::DadConfiguration {
2229 transmits: Some(dad_transmits),
2230 ..Default::default()
2231 }),
2232 ..Default::default()
2233 }),
2234 ..Default::default()
2235 }),
2236 ..Default::default()
2237 })
2238 .await?
2239 .map(|config| config.ipv6?.ndp?.dad?.transmits)
2240 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))
2241}
2242
2243async fn set_temporary_address_generation_enabled(control: &Control, enabled: bool) -> Result<()> {
2244 let _config: fnet_interfaces_admin::Configuration = control
2245 .set_configuration(&fnet_interfaces_admin::Configuration {
2246 ipv6: Some(fnet_interfaces_admin::Ipv6Configuration {
2247 ndp: Some(fnet_interfaces_admin::NdpConfiguration {
2248 slaac: Some(fnet_interfaces_admin::SlaacConfiguration {
2249 temporary_address: Some(enabled),
2250 ..Default::default()
2251 }),
2252 ..Default::default()
2253 }),
2254 ..Default::default()
2255 }),
2256 ..Default::default()
2257 })
2258 .await
2259 .context("FIDL error")?
2260 .map_err(|e| anyhow::anyhow!("set configuration error {e:?}"))?;
2261 Ok(())
2262}
2263
2264fn get_socket2_domain(addr: &std::net::SocketAddr) -> fposix_socket::Domain {
2266 let domain = match addr {
2267 std::net::SocketAddr::V4(_) => fposix_socket::Domain::Ipv4,
2268 std::net::SocketAddr::V6(_) => fposix_socket::Domain::Ipv6,
2269 };
2270
2271 domain
2272}
2273
2274pub trait RealmUdpSocket: Sized {
2276 fn bind_in_realm<'a>(
2278 realm: &'a TestRealm<'a>,
2279 addr: std::net::SocketAddr,
2280 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2281
2282 fn bind_in_realm_with_options<'a>(
2284 realm: &'a TestRealm<'a>,
2285 addr: std::net::SocketAddr,
2286 options: fposix_socket::SocketCreationOptions,
2287 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2288}
2289
2290impl RealmUdpSocket for std::net::UdpSocket {
2291 fn bind_in_realm<'a>(
2292 realm: &'a TestRealm<'a>,
2293 addr: std::net::SocketAddr,
2294 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2295 async move {
2296 let sock = realm
2297 .datagram_socket(
2298 get_socket2_domain(&addr),
2299 fposix_socket::DatagramSocketProtocol::Udp,
2300 )
2301 .await
2302 .context("failed to create socket")?;
2303
2304 sock.bind(&addr.into()).context("bind failed")?;
2305
2306 Result::Ok(sock.into())
2307 }
2308 .boxed_local()
2309 }
2310
2311 fn bind_in_realm_with_options<'a>(
2312 realm: &'a TestRealm<'a>,
2313 addr: std::net::SocketAddr,
2314 options: fposix_socket::SocketCreationOptions,
2315 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2316 async move {
2317 let sock = realm
2318 .datagram_socket_with_options(
2319 get_socket2_domain(&addr),
2320 fposix_socket::DatagramSocketProtocol::Udp,
2321 options,
2322 )
2323 .await
2324 .context("failed to create socket")?;
2325
2326 sock.bind(&addr.into()).context("bind failed")?;
2327
2328 Result::Ok(sock.into())
2329 }
2330 .boxed_local()
2331 }
2332}
2333
2334impl RealmUdpSocket for fuchsia_async::net::UdpSocket {
2335 fn bind_in_realm<'a>(
2336 realm: &'a TestRealm<'a>,
2337 addr: std::net::SocketAddr,
2338 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2339 std::net::UdpSocket::bind_in_realm(realm, addr)
2340 .and_then(|udp| {
2341 futures::future::ready(
2342 fuchsia_async::net::UdpSocket::from_socket(udp)
2343 .context("failed to create fuchsia_async socket"),
2344 )
2345 })
2346 .boxed_local()
2347 }
2348
2349 fn bind_in_realm_with_options<'a>(
2350 realm: &'a TestRealm<'a>,
2351 addr: std::net::SocketAddr,
2352 options: fposix_socket::SocketCreationOptions,
2353 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2354 std::net::UdpSocket::bind_in_realm_with_options(realm, addr, options)
2355 .and_then(|udp| {
2356 futures::future::ready(
2357 fuchsia_async::net::UdpSocket::from_socket(udp)
2358 .context("failed to create fuchsia_async socket"),
2359 )
2360 })
2361 .boxed_local()
2362 }
2363}
2364
2365pub trait RealmTcpListener: Sized {
2367 fn listen_in_realm<'a>(
2369 realm: &'a TestRealm<'a>,
2370 addr: std::net::SocketAddr,
2371 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2372 Self::listen_in_realm_with(realm, addr, |_: &socket2::Socket| Ok(()))
2373 }
2374
2375 fn listen_in_realm_with<'a>(
2378 realm: &'a TestRealm<'a>,
2379 addr: std::net::SocketAddr,
2380 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2381 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2382}
2383
2384impl RealmTcpListener for std::net::TcpListener {
2385 fn listen_in_realm_with<'a>(
2386 realm: &'a TestRealm<'a>,
2387 addr: std::net::SocketAddr,
2388 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2389 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2390 async move {
2391 let sock = realm
2392 .stream_socket(get_socket2_domain(&addr), fposix_socket::StreamSocketProtocol::Tcp)
2393 .await
2394 .context("failed to create server socket")?;
2395 setup(&sock)?;
2396 sock.bind(&addr.into()).context("failed to bind server socket")?;
2397 sock.listen(128).context("failed to listen on server socket")?;
2400
2401 Result::Ok(sock.into())
2402 }
2403 .boxed_local()
2404 }
2405}
2406
2407impl RealmTcpListener for fuchsia_async::net::TcpListener {
2408 fn listen_in_realm_with<'a>(
2409 realm: &'a TestRealm<'a>,
2410 addr: std::net::SocketAddr,
2411 setup: impl FnOnce(&socket2::Socket) -> Result<()> + 'a,
2412 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2413 std::net::TcpListener::listen_in_realm_with(realm, addr, setup)
2414 .and_then(|listener| {
2415 futures::future::ready(
2416 fuchsia_async::net::TcpListener::from_std(listener)
2417 .context("failed to create fuchsia_async socket"),
2418 )
2419 })
2420 .boxed_local()
2421 }
2422}
2423
2424pub trait RealmTcpStream: Sized {
2426 fn connect_in_realm<'a>(
2428 realm: &'a TestRealm<'a>,
2429 addr: std::net::SocketAddr,
2430 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2431
2432 fn bind_and_connect_in_realm<'a>(
2434 realm: &'a TestRealm<'a>,
2435 local: std::net::SocketAddr,
2436 dst: std::net::SocketAddr,
2437 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2438
2439 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2444 realm: &'a TestRealm<'a>,
2445 dst: std::net::SocketAddr,
2446 with_sock: F,
2447 ) -> futures::future::LocalBoxFuture<'a, Result<Self>>;
2448
2449 }
2451
2452impl RealmTcpStream for fuchsia_async::net::TcpStream {
2453 fn connect_in_realm<'a>(
2454 realm: &'a TestRealm<'a>,
2455 addr: std::net::SocketAddr,
2456 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2457 Self::connect_in_realm_with_sock(realm, addr, |_: &socket2::Socket| Ok(()))
2458 }
2459
2460 fn bind_and_connect_in_realm<'a>(
2461 realm: &'a TestRealm<'a>,
2462 local: std::net::SocketAddr,
2463 dst: std::net::SocketAddr,
2464 ) -> futures::future::LocalBoxFuture<'a, Result<Self>> {
2465 Self::connect_in_realm_with_sock(realm, dst, move |sock| {
2466 sock.bind(&local.into()).context("failed to bind")
2467 })
2468 }
2469
2470 fn connect_in_realm_with_sock<'a, F: FnOnce(&socket2::Socket) -> Result + 'a>(
2471 realm: &'a TestRealm<'a>,
2472 dst: std::net::SocketAddr,
2473 with_sock: F,
2474 ) -> futures::future::LocalBoxFuture<'a, Result<fuchsia_async::net::TcpStream>> {
2475 async move {
2476 let sock = realm
2477 .stream_socket(get_socket2_domain(&dst), fposix_socket::StreamSocketProtocol::Tcp)
2478 .await
2479 .context("failed to create socket")?;
2480
2481 with_sock(&sock)?;
2482
2483 let stream = fuchsia_async::net::TcpStream::connect_from_raw(sock, dst)
2484 .context("failed to create client tcp stream")?
2485 .await
2486 .context("failed to connect to server")?;
2487
2488 Result::Ok(stream)
2489 }
2490 .boxed_local()
2491 }
2492}
2493
2494fn truncate_dropping_front(s: Cow<'_, str>, len: usize) -> Cow<'_, str> {
2495 match s.len().checked_sub(len) {
2496 None => s,
2497 Some(start) => {
2498 match s {
2502 Cow::Borrowed(s) => Cow::Borrowed(&s[start..]),
2503 Cow::Owned(mut s) => {
2504 let _: std::string::Drain<'_> = s.drain(..start);
2505 Cow::Owned(s)
2506 }
2507 }
2508 }
2509 }
2510}