Skip to main content

netcfg/
devices.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fmt::Display;
6
7use fidl_fuchsia_hardware_network as fhwnet;
8
9use anyhow::Context as _;
10
11use crate::errors::{self, ContextExt as _};
12use crate::exit_with_fidl_error;
13
14/// An error when adding a device.
15pub(super) enum AddDeviceError {
16    AlreadyExists(String),
17    Other(errors::Error),
18}
19
20impl From<errors::Error> for AddDeviceError {
21    fn from(e: errors::Error) -> AddDeviceError {
22        AddDeviceError::Other(e)
23    }
24}
25
26impl errors::ContextExt for AddDeviceError {
27    fn context<C>(self, context: C) -> AddDeviceError
28    where
29        C: Display + Send + Sync + 'static,
30    {
31        match self {
32            AddDeviceError::AlreadyExists(name) => AddDeviceError::AlreadyExists(name),
33            AddDeviceError::Other(e) => AddDeviceError::Other(e.context(context)),
34        }
35    }
36
37    fn with_context<C, F>(self, f: F) -> AddDeviceError
38    where
39        C: Display + Send + Sync + 'static,
40        F: FnOnce() -> C,
41    {
42        match self {
43            AddDeviceError::AlreadyExists(name) => AddDeviceError::AlreadyExists(name),
44            AddDeviceError::Other(e) => AddDeviceError::Other(e.with_context(f)),
45        }
46    }
47}
48
49// Cannot be completely replaced by `DeviceInfoRef` due to `get_device_info`
50// not being able to return a struct of references.
51#[derive(Debug, Clone)]
52pub(super) struct DeviceInfo {
53    pub(super) port_class: fhwnet::PortClass,
54    pub(super) mac: Option<fidl_fuchsia_net_ext::MacAddress>,
55    pub(super) topological_path: String,
56}
57
58/// An instance of a network device.
59pub(super) struct NetworkDeviceInstance {
60    port: fhwnet::PortProxy,
61    port_id: fhwnet::PortId,
62    device_control: fidl_fuchsia_net_interfaces_admin::DeviceControlProxy,
63    topological_path: String,
64}
65
66impl std::fmt::Debug for NetworkDeviceInstance {
67    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68        let NetworkDeviceInstance { port: _, port_id, device_control: _, topological_path } = self;
69        write!(
70            f,
71            "NetworkDeviceInstance{{topological_path={}, port={:?}}}",
72            topological_path, port_id
73        )
74    }
75}
76
77impl NetworkDeviceInstance {
78    pub async fn get_instance_stream(
79        installer: &fidl_fuchsia_net_interfaces_admin::InstallerProxy,
80        service: fhwnet::ServiceProxy,
81    ) -> Result<impl futures::Stream<Item = Result<Self, errors::Error>> + use<>, errors::Error>
82    {
83        let device = service
84            .connect_to_device()
85            .context("cannot connect to device")
86            .map_err(errors::Error::NonFatal)?;
87        // TODO(https://fxbug.dev/501161302): Use
88        // `fuchsia.driver.token.NodeBusTopology`.
89        let device_topology = service
90            .connect_to_device_topology()
91            .context("cannot connect to device_topology")
92            .map_err(errors::Error::NonFatal)?;
93
94        let topological_path = device_topology
95            .get_topological_path()
96            .await
97            .context("error sending get topological path request")
98            .map_err(errors::Error::NonFatal)?
99            .map_err(zx::Status::from_raw)
100            .context("error getting topological path")
101            .map_err(errors::Error::NonFatal)?;
102
103        let (port_watcher, port_watcher_server_end) =
104            fidl::endpoints::create_proxy::<fhwnet::PortWatcherMarker>();
105        device
106            .get_port_watcher(port_watcher_server_end)
107            .context("calling Device get_port_watcher")
108            .map_err(errors::Error::NonFatal)?;
109
110        let (device_control, device_control_server_end) = fidl::endpoints::create_proxy::<
111            fidl_fuchsia_net_interfaces_admin::DeviceControlMarker,
112        >();
113
114        let (device_for_netstack, device_server_end) =
115            fidl::endpoints::create_endpoints::<fhwnet::DeviceMarker>();
116        device
117            .clone(device_server_end)
118            .context("calling Device clone")
119            .map_err(errors::Error::NonFatal)?;
120        installer
121            .install_device(device_for_netstack, device_control_server_end)
122            // NB: Failing to communicate with installer is a fatal error, that
123            // means the Netstack is gone, which we don't tolerate.
124            .unwrap_or_else(|err| exit_with_fidl_error(err));
125
126        Ok(futures::stream::try_unfold(
127            (port_watcher, device_control, device, topological_path),
128            |(port_watcher, device_control, device, topological_path)| async move {
129                loop {
130                    let port_event = match port_watcher.watch().await {
131                        Ok(port_event) => port_event,
132                        Err(err) => {
133                            break if err.is_closed() {
134                                Ok(None)
135                            } else {
136                                Err(errors::Error::Fatal(err.into()))
137                                    .context("calling PortWatcher watch")
138                            };
139                        }
140                    };
141                    match port_event {
142                        fhwnet::DevicePortEvent::Idle(fhwnet::Empty {}) => {}
143                        fhwnet::DevicePortEvent::Removed(port_id) => {
144                            let _: fhwnet::PortId = port_id;
145                        }
146                        fhwnet::DevicePortEvent::Added(port_id)
147                        | fhwnet::DevicePortEvent::Existing(port_id) => {
148                            let (port, port_server_end) =
149                                fidl::endpoints::create_proxy::<fhwnet::PortMarker>();
150                            device
151                                .get_port(&port_id, port_server_end)
152                                .context("calling Device get_port")
153                                .map_err(errors::Error::NonFatal)?;
154                            break Ok(Some((
155                                NetworkDeviceInstance {
156                                    port,
157                                    port_id,
158                                    device_control: device_control.clone(),
159                                    topological_path: topological_path.clone(),
160                                },
161                                (port_watcher, device_control, device, topological_path),
162                            )));
163                        }
164                    }
165                }
166            },
167        ))
168    }
169
170    pub async fn get_device_info(&self) -> Result<DeviceInfo, errors::Error> {
171        let NetworkDeviceInstance { port, port_id: _, device_control: _, topological_path } = self;
172        let fhwnet::PortInfo { id: _, base_info, .. } = port
173            .get_info()
174            .await
175            .context("error getting port info")
176            .map_err(errors::Error::NonFatal)?;
177        let port_class = base_info
178            .ok_or_else(|| errors::Error::Fatal(anyhow::anyhow!("missing base info in port info")))?
179            .port_class
180            .ok_or_else(|| {
181                errors::Error::Fatal(anyhow::anyhow!("missing port class in port base info"))
182            })?;
183
184        let (mac_addressing, mac_addressing_server_end) =
185            fidl::endpoints::create_proxy::<fhwnet::MacAddressingMarker>();
186        port.get_mac(mac_addressing_server_end)
187            .context("calling Port get_mac")
188            .map_err(errors::Error::NonFatal)?;
189
190        let mac = mac_addressing
191            .get_unicast_address()
192            .await
193            .map(Some)
194            .or_else(|fidl_err| {
195                if fidl_err.is_closed() { Ok(None) } else { Err(anyhow::Error::from(fidl_err)) }
196            })
197            .map_err(errors::Error::NonFatal)?;
198        Ok(DeviceInfo {
199            port_class,
200            mac: mac.map(Into::into),
201            topological_path: topological_path.clone(),
202        })
203    }
204
205    pub async fn add_to_stack(
206        &self,
207        _netcfg: &super::NetCfg<'_>,
208        config: crate::InterfaceConfig,
209    ) -> Result<(u64, fidl_fuchsia_net_interfaces_ext::admin::Control), AddDeviceError> {
210        let NetworkDeviceInstance { port: _, port_id, device_control, topological_path: _ } = self;
211        let crate::InterfaceConfig { name, metric, netstack_managed_routes_designation } = config;
212
213        let (control, control_server_end) =
214            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
215                .context("create Control proxy")
216                .map_err(errors::Error::NonFatal)?;
217
218        device_control
219            .create_interface(
220                &port_id,
221                control_server_end,
222                fidl_fuchsia_net_interfaces_admin::Options {
223                    name: Some(name.clone()),
224                    metric: Some(metric),
225                    netstack_managed_routes_designation: netstack_managed_routes_designation
226                        .map(Into::into),
227                    ..Default::default()
228                },
229            )
230            .context("calling DeviceControl create_interface")
231            .map_err(errors::Error::NonFatal)?;
232
233        let interface_id = control.get_id().await.map_err(|err| {
234            let other = match err {
235                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Fidl(err) => err.into(),
236                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(terminal_error) => {
237                    match terminal_error {
238                        fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName => {
239                            return AddDeviceError::AlreadyExists(name);
240                        }
241                        reason => {
242                            anyhow::anyhow!("received terminal event {:?}", reason)
243                        }
244                    }
245                }
246            };
247            AddDeviceError::Other(
248                errors::Error::NonFatal(other).context("calling Control get_id"),
249            )
250        })?;
251        Ok((interface_id, control))
252    }
253}