1use anyhow::{Context as _, Error};
6use component_debug::dirs::{OpenDirType, connect_to_instance_protocol};
7use once_cell::sync::OnceCell;
8use serde::Serialize;
9
10fn serialize_ipv4<S: serde::Serializer>(
11 addresses: &Vec<std::net::Ipv4Addr>,
12 serializer: S,
13) -> Result<S::Ok, S::Error> {
14 serializer.collect_seq(addresses.iter().map(|address| address.octets()))
15}
16
17fn serialize_ipv6<S: serde::Serializer>(
18 addresses: &Vec<std::net::Ipv6Addr>,
19 serializer: S,
20) -> Result<S::Ok, S::Error> {
21 serializer.collect_seq(addresses.iter().map(|address| address.octets()))
22}
23
24fn serialize_mac<S: serde::Serializer>(
25 mac: &Option<fidl_fuchsia_net_ext::MacAddress>,
26 serializer: S,
27) -> Result<S::Ok, S::Error> {
28 match mac {
29 None => serializer.serialize_none(),
30 Some(fidl_fuchsia_net_ext::MacAddress { octets }) => serializer.collect_seq(octets.iter()),
31 }
32}
33
34#[derive(Serialize)]
35enum DeviceClass {
36 Loopback,
37 Blackhole,
38 Virtual,
39 Ethernet,
40 WlanClient,
41 Ppp,
42 Bridge,
43 WlanAp,
44 Lowpan,
45}
46
47#[derive(Serialize)]
48pub struct Properties {
49 id: u64,
50 name: String,
51 device_class: DeviceClass,
52 online: bool,
53 #[serde(serialize_with = "serialize_ipv4")]
54 ipv4_addresses: Vec<std::net::Ipv4Addr>,
55 #[serde(serialize_with = "serialize_ipv6")]
56 ipv6_addresses: Vec<std::net::Ipv6Addr>,
57 #[serde(serialize_with = "serialize_mac")]
58 mac: Option<fidl_fuchsia_net_ext::MacAddress>,
59}
60
61impl
62 From<(
63 fidl_fuchsia_net_interfaces_ext::Properties<fidl_fuchsia_net_interfaces_ext::AllInterest>,
64 Option<fidl_fuchsia_net::MacAddress>,
65 )> for Properties
66{
67 fn from(
68 t: (
69 fidl_fuchsia_net_interfaces_ext::Properties<
70 fidl_fuchsia_net_interfaces_ext::AllInterest,
71 >,
72 Option<fidl_fuchsia_net::MacAddress>,
73 ),
74 ) -> Self {
75 use itertools::Itertools as _;
76
77 let (
78 fidl_fuchsia_net_interfaces_ext::Properties {
79 id,
80 name,
81 port_class,
82 online,
83 addresses,
84 has_default_ipv4_route: _,
85 has_default_ipv6_route: _,
86 port_identity_koid: _,
87 },
88 mac,
89 ) = t;
90 let device_class = match port_class {
91 fidl_fuchsia_net_interfaces_ext::PortClass::Loopback => DeviceClass::Loopback,
92 fidl_fuchsia_net_interfaces_ext::PortClass::Blackhole => DeviceClass::Blackhole,
93 fidl_fuchsia_net_interfaces_ext::PortClass::Virtual => DeviceClass::Virtual,
94 fidl_fuchsia_net_interfaces_ext::PortClass::Ethernet => DeviceClass::Ethernet,
95 fidl_fuchsia_net_interfaces_ext::PortClass::WlanClient => DeviceClass::WlanClient,
96 fidl_fuchsia_net_interfaces_ext::PortClass::WlanAp => DeviceClass::WlanAp,
97 fidl_fuchsia_net_interfaces_ext::PortClass::Ppp => DeviceClass::Ppp,
98 fidl_fuchsia_net_interfaces_ext::PortClass::Bridge => DeviceClass::Bridge,
99 fidl_fuchsia_net_interfaces_ext::PortClass::Lowpan => DeviceClass::Lowpan,
100 };
101 let (ipv4_addresses, ipv6_addresses) =
102 addresses.into_iter().partition_map::<_, _, _, std::net::Ipv4Addr, std::net::Ipv6Addr>(
103 |fidl_fuchsia_net_interfaces_ext::Address {
104 addr,
105 valid_until: _,
106 preferred_lifetime_info: _,
107 assignment_state,
108 }| {
109 assert_eq!(
111 assignment_state,
112 fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned,
113 "Support for unassigned addresses have not been implemented",
114 );
115 let fidl_fuchsia_net_ext::Subnet { addr, prefix_len: _ } = addr.into();
116 let fidl_fuchsia_net_ext::IpAddress(addr) = addr;
117 match addr {
118 std::net::IpAddr::V4(addr) => itertools::Either::Left(addr),
119 std::net::IpAddr::V6(addr) => itertools::Either::Right(addr),
120 }
121 },
122 );
123 Self {
124 id: id.get(),
125 name,
126 device_class,
127 online,
128 ipv4_addresses,
129 ipv6_addresses,
130 mac: mac.map(Into::into),
131 }
132 }
133}
134
135#[derive(Debug, Default)]
137pub struct NetstackFacade {
138 interfaces_state: OnceCell<fidl_fuchsia_net_interfaces::StateProxy>,
139 root_interfaces: OnceCell<fidl_fuchsia_net_root::InterfacesProxy>,
140}
141
142async fn get_netstack_proxy<P: fidl::endpoints::DiscoverableProtocolMarker>()
143-> Result<P::Proxy, Error> {
144 let query =
145 fuchsia_component::client::connect_to_protocol::<fidl_fuchsia_sys2::RealmQueryMarker>()?;
146 let moniker = "./core/network/netstack".try_into()?;
147 let proxy = connect_to_instance_protocol::<P>(&moniker, OpenDirType::Exposed, &query).await?;
148 Ok(proxy)
149}
150
151impl NetstackFacade {
152 async fn get_interfaces_state(
153 &self,
154 ) -> Result<&fidl_fuchsia_net_interfaces::StateProxy, Error> {
155 let Self { interfaces_state, root_interfaces: _ } = self;
156 if let Some(state_proxy) = interfaces_state.get() {
157 Ok(state_proxy)
158 } else {
159 let state_proxy =
160 get_netstack_proxy::<fidl_fuchsia_net_interfaces::StateMarker>().await?;
161 interfaces_state.set(state_proxy).unwrap();
162 let state_proxy = interfaces_state.get().unwrap();
163 Ok(state_proxy)
164 }
165 }
166
167 async fn get_root_interfaces(&self) -> Result<&fidl_fuchsia_net_root::InterfacesProxy, Error> {
168 let Self { interfaces_state: _, root_interfaces } = self;
169 if let Some(interfaces_proxy) = root_interfaces.get() {
170 Ok(interfaces_proxy)
171 } else {
172 let interfaces_proxy =
173 get_netstack_proxy::<fidl_fuchsia_net_root::InterfacesMarker>().await?;
174 root_interfaces.set(interfaces_proxy).unwrap();
175 let interfaces_proxy = root_interfaces.get().unwrap();
176 Ok(interfaces_proxy)
177 }
178 }
179
180 async fn get_control(
181 &self,
182 id: u64,
183 ) -> Result<fidl_fuchsia_net_interfaces_ext::admin::Control, Error> {
184 let root_interfaces = self.get_root_interfaces().await?;
185 let (control, server_end) =
186 fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
187 .context("create admin control endpoints")?;
188 let () = root_interfaces.get_admin(id, server_end).context("send get admin request")?;
189 Ok(control)
190 }
191
192 pub async fn enable_interface(&self, id: u64) -> Result<(), Error> {
193 let control = self.get_control(id).await?;
194 let _did_enable: bool = control
195 .enable()
196 .await
197 .map_err(anyhow::Error::new)
198 .and_then(|res| {
199 res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlEnableError| {
200 anyhow::anyhow!("{:?}", e)
201 })
202 })
203 .with_context(|| format!("failed to enable interface {}", id))?;
204 Ok(())
205 }
206
207 pub async fn disable_interface(&self, id: u64) -> Result<(), Error> {
208 let control = self.get_control(id).await?;
209 let _did_disable: bool = control
210 .disable()
211 .await
212 .map_err(anyhow::Error::new)
213 .and_then(|res| {
214 res.map_err(|e: fidl_fuchsia_net_interfaces_admin::ControlDisableError| {
215 anyhow::anyhow!("{:?}", e)
216 })
217 })
218 .with_context(|| format!("failed to disable interface {}", id))?;
219 Ok(())
220 }
221
222 pub async fn list_interfaces(&self) -> Result<Vec<Properties>, Error> {
223 let interfaces_state = self.get_interfaces_state().await?;
224 let root_interfaces = self.get_root_interfaces().await?;
225 let stream = fidl_fuchsia_net_interfaces_ext::event_stream_from_state(
228 interfaces_state,
229 Default::default(),
230 )?;
231 let response = fidl_fuchsia_net_interfaces_ext::existing(
232 stream,
233 std::collections::HashMap::<u64, _>::new(),
234 )
235 .await?;
236 let response = response.into_values().map(
237 |fidl_fuchsia_net_interfaces_ext::PropertiesAndState { properties, state: () }| async {
238 match root_interfaces.get_mac(properties.id.get()).await? {
239 Ok(mac) => {
240 let mac = mac.map(|boxed_mac| *boxed_mac);
241 let view: Properties = (properties, mac).into();
242 Ok::<_, Error>(Some(view))
243 }
244 Err(fidl_fuchsia_net_root::InterfacesGetMacError::NotFound) => {
245 Ok::<_, Error>(None)
248 }
249 }
250 },
251 );
252 let mut response: Vec<Properties> =
253 futures::future::try_join_all(response).await?.into_iter().filter_map(|r| r).collect();
254 let () = response.sort_by_key(|&Properties { id, .. }| id);
255 Ok(response)
256 }
257
258 async fn get_addresses<T, F: Copy + FnMut(fidl_fuchsia_net::Subnet) -> Option<T>>(
259 &self,
260 f: F,
261 ) -> Result<Vec<T>, Error> {
262 let mut output = Vec::new();
263
264 let interfaces_state = self.get_interfaces_state().await?;
265 let (watcher, server) =
266 fidl::endpoints::create_proxy::<fidl_fuchsia_net_interfaces::WatcherMarker>();
267 let () = interfaces_state
268 .get_watcher(&fidl_fuchsia_net_interfaces::WatcherOptions::default(), server)?;
269
270 loop {
271 match watcher.watch().await? {
272 fidl_fuchsia_net_interfaces::Event::Existing(
273 fidl_fuchsia_net_interfaces::Properties { addresses, .. },
274 ) => {
275 let addresses = addresses.unwrap();
276 let () = output.extend(
277 addresses
278 .into_iter()
279 .map(
280 |fidl_fuchsia_net_interfaces::Address {
281 addr,
282 valid_until: _,
283 ..
284 }| addr.unwrap(),
285 )
286 .filter_map(f),
287 );
288 }
289 fidl_fuchsia_net_interfaces::Event::Idle(fidl_fuchsia_net_interfaces::Empty {}) => {
290 break;
291 }
292 event => unreachable!("{:?}", event),
293 }
294 }
295
296 Ok(output)
297 }
298
299 pub fn get_ipv6_addresses(
300 &self,
301 ) -> impl std::future::Future<Output = Result<Vec<std::net::Ipv6Addr>, Error>> + '_ {
302 self.get_addresses(|addr| {
303 let fidl_fuchsia_net_ext::Subnet { addr, prefix_len: _ } = addr.into();
304 let fidl_fuchsia_net_ext::IpAddress(addr) = addr;
305 match addr {
306 std::net::IpAddr::V4(_) => None,
307 std::net::IpAddr::V6(addr) => Some(addr),
308 }
309 })
310 }
311
312 pub fn get_link_local_ipv6_addresses(
313 &self,
314 ) -> impl std::future::Future<Output = Result<Vec<std::net::Ipv6Addr>, Error>> + '_ {
315 use futures::TryFutureExt as _;
316
317 self.get_ipv6_addresses().map_ok(|addresses| {
318 addresses.into_iter().filter(|address| address.octets()[..2] == [0xfe, 0x80]).collect()
319 })
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use fidl_fuchsia_net as fnet;
327 use fidl_fuchsia_net_interfaces as finterfaces;
328 use fuchsia_async as fasync;
329 use futures::StreamExt as _;
330
331 struct MockStateTester {
332 expected_state: Vec<Box<dyn FnOnce(finterfaces::WatcherRequest) + Send + 'static>>,
333 }
334
335 impl MockStateTester {
336 fn new() -> Self {
337 Self { expected_state: vec![] }
338 }
339
340 pub fn create_facade_and_serve_state(
341 self,
342 ) -> (NetstackFacade, impl std::future::Future<Output = ()>) {
343 let (interfaces_state, stream_future) = self.build_state_and_watcher();
344 (
345 NetstackFacade { interfaces_state: interfaces_state.into(), ..Default::default() },
346 stream_future,
347 )
348 }
349
350 fn push_state(
351 mut self,
352 request: impl FnOnce(finterfaces::WatcherRequest) + Send + 'static,
353 ) -> Self {
354 self.expected_state.push(Box::new(request));
355 self
356 }
357
358 fn build_state_and_watcher(
359 self,
360 ) -> (finterfaces::StateProxy, impl std::future::Future<Output = ()>) {
361 let (proxy, mut stream) =
362 fidl::endpoints::create_proxy_and_stream::<finterfaces::StateMarker>();
363 let stream_fut = async move {
364 match stream.next().await {
365 Some(Ok(finterfaces::StateRequest::GetWatcher { watcher, .. })) => {
366 let mut into_stream = watcher.into_stream();
367 for expected in self.expected_state {
368 let () = expected(into_stream.next().await.unwrap().unwrap());
369 }
370 let finterfaces::WatcherRequest::Watch { responder } =
371 into_stream.next().await.unwrap().unwrap();
372 let () = responder
373 .send(&finterfaces::Event::Idle(finterfaces::Empty {}))
374 .unwrap();
375 }
376 err => panic!("Error in request handler: {:?}", err),
377 }
378 };
379 (proxy, stream_fut)
380 }
381
382 fn expect_get_ipv6_addresses(self, result: Vec<fnet::Subnet>) -> Self {
383 let addresses = result
384 .into_iter()
385 .map(|addr| finterfaces::Address { addr: Some(addr), ..Default::default() })
386 .collect();
387 self.push_state(move |req| match req {
388 finterfaces::WatcherRequest::Watch { responder } => responder
389 .send(&finterfaces::Event::Existing(finterfaces::Properties {
390 addresses: Some(addresses),
391 ..Default::default()
392 }))
393 .unwrap(),
394 })
395 }
396 }
397
398 #[fasync::run_singlethreaded(test)]
399 async fn test_get_ipv6_addresses() {
400 let ipv6_octets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
401
402 let ipv6_address = fnet::Subnet {
403 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: ipv6_octets }),
404 prefix_len: 137,
406 };
407 let ipv4_address = fnet::Subnet {
408 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [0, 1, 2, 3] }),
409 prefix_len: 139,
411 };
412 let all_addresses = [ipv6_address.clone(), ipv4_address.clone()];
413 let (facade, stream_fut) = MockStateTester::new()
414 .expect_get_ipv6_addresses(all_addresses.to_vec())
415 .create_facade_and_serve_state();
416 let facade_fut = async move {
417 let result_address: Vec<_> = facade.get_ipv6_addresses().await.unwrap();
418 assert_eq!(result_address, [std::net::Ipv6Addr::from(ipv6_octets)]);
419 };
420 futures::future::join(facade_fut, stream_fut).await;
421 }
422
423 #[fasync::run_singlethreaded(test)]
424 async fn test_get_link_local_ipv6_addresses() {
425 let ipv6_address = fnet::Subnet {
426 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address {
427 addr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
428 }),
429 prefix_len: 137,
431 };
432 let link_local_ipv6_octets = [0xfe, 0x80, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
433 let link_local_ipv6_address = fnet::Subnet {
434 addr: fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr: link_local_ipv6_octets }),
435 prefix_len: 139,
437 };
438 let ipv4_address = fnet::Subnet {
439 addr: fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr: [0, 1, 2, 3] }),
440 prefix_len: 141,
442 };
443 let all_addresses =
444 [ipv6_address.clone(), link_local_ipv6_address.clone(), ipv4_address.clone()];
445 let (facade, stream_fut) = MockStateTester::new()
446 .expect_get_ipv6_addresses(all_addresses.to_vec())
447 .create_facade_and_serve_state();
448 let facade_fut = async move {
449 let result_address: Vec<_> = facade.get_link_local_ipv6_addresses().await.unwrap();
450 assert_eq!(result_address, [std::net::Ipv6Addr::from(link_local_ipv6_octets)]);
451 };
452 futures::future::join(facade_fut, stream_fut).await;
453 }
454}