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