1use 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
14pub(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#[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
58pub(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 .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
249async 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 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 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}