1use 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
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 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 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 .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}