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_device as fdev, 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 const PATH: &'static str = "/dev/class/network";
79
80    pub async fn get_instance_stream(
81        installer: &fidl_fuchsia_net_interfaces_admin::InstallerProxy,
82        path: &std::path::PathBuf,
83    ) -> Result<impl futures::Stream<Item = Result<Self, errors::Error>> + use<>, errors::Error>
84    {
85        let (topological_path, _file_path, device_instance) =
86            get_topo_path_and_device::<fhwnet::DeviceInstanceMarker>(path)
87                .await
88                .with_context(|| format!("open netdevice at {:?}", path))?;
89
90        let get_device = || {
91            let (device, device_server_end) =
92                fidl::endpoints::create_endpoints::<fhwnet::DeviceMarker>();
93            device_instance
94                .get_device(device_server_end)
95                .context("calling DeviceInstance get_device")
96                .map_err(errors::Error::NonFatal)?;
97            Ok(device)
98        };
99
100        let device = get_device()?.into_proxy();
101
102        let (port_watcher, port_watcher_server_end) =
103            fidl::endpoints::create_proxy::<fhwnet::PortWatcherMarker>();
104        device
105            .get_port_watcher(port_watcher_server_end)
106            .context("calling Device get_port_watcher")
107            .map_err(errors::Error::NonFatal)?;
108
109        let (device_control, device_control_server_end) = fidl::endpoints::create_proxy::<
110            fidl_fuchsia_net_interfaces_admin::DeviceControlMarker,
111        >();
112
113        let device_for_netstack = get_device()?;
114        installer
115            .install_device(device_for_netstack, device_control_server_end)
116            // NB: Failing to communicate with installer is a fatal error, that
117            // means the Netstack is gone, which we don't tolerate.
118            .unwrap_or_else(|err| exit_with_fidl_error(err));
119
120        Ok(futures::stream::try_unfold(
121            (port_watcher, device_control, device, topological_path),
122            |(port_watcher, device_control, device, topological_path)| async move {
123                loop {
124                    let port_event = match port_watcher.watch().await {
125                        Ok(port_event) => port_event,
126                        Err(err) => {
127                            break if err.is_closed() {
128                                Ok(None)
129                            } else {
130                                Err(errors::Error::Fatal(err.into()))
131                                    .context("calling PortWatcher watch")
132                            };
133                        }
134                    };
135                    match port_event {
136                        fhwnet::DevicePortEvent::Idle(fhwnet::Empty {}) => {}
137                        fhwnet::DevicePortEvent::Removed(port_id) => {
138                            let _: fhwnet::PortId = port_id;
139                        }
140                        fhwnet::DevicePortEvent::Added(port_id)
141                        | fhwnet::DevicePortEvent::Existing(port_id) => {
142                            let (port, port_server_end) =
143                                fidl::endpoints::create_proxy::<fhwnet::PortMarker>();
144                            device
145                                .get_port(&port_id, port_server_end)
146                                .context("calling Device get_port")
147                                .map_err(errors::Error::NonFatal)?;
148                            break Ok(Some((
149                                NetworkDeviceInstance {
150                                    port,
151                                    port_id,
152                                    device_control: device_control.clone(),
153                                    topological_path: topological_path.clone(),
154                                },
155                                (port_watcher, device_control, device, topological_path),
156                            )));
157                        }
158                    }
159                }
160            },
161        ))
162    }
163
164    pub async fn get_device_info(&self) -> Result<DeviceInfo, errors::Error> {
165        let NetworkDeviceInstance { port, port_id: _, device_control: _, topological_path } = self;
166        let fhwnet::PortInfo { id: _, base_info, .. } = port
167            .get_info()
168            .await
169            .context("error getting port info")
170            .map_err(errors::Error::NonFatal)?;
171        let port_class = base_info
172            .ok_or_else(|| errors::Error::Fatal(anyhow::anyhow!("missing base info in port info")))?
173            .port_class
174            .ok_or_else(|| {
175                errors::Error::Fatal(anyhow::anyhow!("missing port class in port base info"))
176            })?;
177
178        let (mac_addressing, mac_addressing_server_end) =
179            fidl::endpoints::create_proxy::<fhwnet::MacAddressingMarker>();
180        port.get_mac(mac_addressing_server_end)
181            .context("calling Port get_mac")
182            .map_err(errors::Error::NonFatal)?;
183
184        let mac = mac_addressing
185            .get_unicast_address()
186            .await
187            .map(Some)
188            .or_else(|fidl_err| {
189                if fidl_err.is_closed() { Ok(None) } else { Err(anyhow::Error::from(fidl_err)) }
190            })
191            .map_err(errors::Error::NonFatal)?;
192        Ok(DeviceInfo {
193            port_class,
194            mac: mac.map(Into::into),
195            topological_path: topological_path.clone(),
196        })
197    }
198
199    pub async fn add_to_stack(
200        &self,
201        _netcfg: &super::NetCfg<'_>,
202        config: crate::InterfaceConfig,
203    ) -> Result<(u64, fidl_fuchsia_net_interfaces_ext::admin::Control), AddDeviceError> {
204        let NetworkDeviceInstance { port: _, port_id, device_control, topological_path: _ } = self;
205        let crate::InterfaceConfig { name, metric, netstack_managed_routes_designation } = config;
206
207        let (control, control_server_end) =
208            fidl_fuchsia_net_interfaces_ext::admin::Control::create_endpoints()
209                .context("create Control proxy")
210                .map_err(errors::Error::NonFatal)?;
211
212        device_control
213            .create_interface(
214                &port_id,
215                control_server_end,
216                fidl_fuchsia_net_interfaces_admin::Options {
217                    name: Some(name.clone()),
218                    metric: Some(metric),
219                    netstack_managed_routes_designation: netstack_managed_routes_designation
220                        .map(Into::into),
221                    ..Default::default()
222                },
223            )
224            .context("calling DeviceControl create_interface")
225            .map_err(errors::Error::NonFatal)?;
226
227        let interface_id = control.get_id().await.map_err(|err| {
228            let other = match err {
229                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Fidl(err) => err.into(),
230                fidl_fuchsia_net_interfaces_ext::admin::TerminalError::Terminal(terminal_error) => {
231                    match terminal_error {
232                        fidl_fuchsia_net_interfaces_admin::InterfaceRemovedReason::DuplicateName => {
233                            return AddDeviceError::AlreadyExists(name);
234                        }
235                        reason => {
236                            anyhow::anyhow!("received terminal event {:?}", reason)
237                        }
238                    }
239                }
240            };
241            AddDeviceError::Other(
242                errors::Error::NonFatal(other).context("calling Control get_id"),
243            )
244        })?;
245        Ok((interface_id, control))
246    }
247}
248
249/// Returns the topological path for a device located at `filepath`, `filepath`
250/// converted to `String`, and a proxy to `S`.
251///
252/// It is expected that the node at `filepath` implements `S`,
253/// and that the node at `filepath` + `/device_controller` implements `fuchsia.device/Controller`.
254async fn get_topo_path_and_device<S: fidl::endpoints::ProtocolMarker>(
255    filepath: &std::path::PathBuf,
256) -> Result<(String, String, S::Proxy), errors::Error> {
257    let filepath = filepath
258        .to_str()
259        .ok_or_else(|| anyhow::anyhow!("failed to convert {:?} to str", filepath))
260        .map_err(errors::Error::NonFatal)?;
261
262    // Get the topological path using `fuchsia.device/Controller`.
263    let (controller, req) = fidl::endpoints::create_proxy::<fdev::ControllerMarker>();
264    let controller_path = format!("{filepath}/device_controller");
265    fdio::service_connect(&controller_path, req.into_channel().into())
266        .with_context(|| format!("error calling fdio::service_connect({})", controller_path))
267        .map_err(errors::Error::NonFatal)?;
268    let topological_path = controller
269        .get_topological_path()
270        .await
271        .context("error sending get topological path request")
272        .map_err(errors::Error::NonFatal)?
273        .map_err(zx::Status::from_raw)
274        .context("error getting topological path")
275        .map_err(errors::Error::NonFatal)?;
276
277    // Now connect to the device channel.
278    let (device, req) = fidl::endpoints::create_proxy::<S>();
279    fdio::service_connect(filepath, req.into_channel().into())
280        .with_context(|| format!("error calling fdio::service_connect({})", filepath))
281        .map_err(errors::Error::NonFatal)?;
282
283    Ok((topological_path, filepath.to_string(), device))
284}