1use {
6 fidl_fuchsia_net_interfaces as fnet_interfaces,
7 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
8 fidl_fuchsia_net_policy_socketproxy as fnp_socketproxy,
9 fidl_fuchsia_posix_socket as fposix_socket,
10};
11
12use log::{error, info};
13use socket_proxy::{NetworkConversionError, NetworkExt, NetworkRegistryError};
14use std::collections::HashMap;
15use std::collections::hash_map::Entry;
16use thiserror::Error;
17
18use crate::InterfaceId;
19
20#[derive(Debug)]
21pub struct SocketProxyState {
22 fuchsia_networks: fnp_socketproxy::FuchsiaNetworksProxy,
23 default_id: Option<InterfaceId>,
24 networks: HashMap<InterfaceId, fnp_socketproxy::Network>,
25}
26
27#[cfg(test)]
28impl SocketProxyState {
29 pub fn default_id(&self) -> Option<InterfaceId> {
30 self.default_id.clone()
31 }
32}
33
34impl SocketProxyState {
38 pub fn new(fuchsia_networks: fnp_socketproxy::FuchsiaNetworksProxy) -> Self {
39 Self { fuchsia_networks, default_id: None, networks: HashMap::new() }
40 }
41
42 pub(crate) async fn handle_default_network(
47 &mut self,
48 network_id: Option<InterfaceId>,
49 ) -> Result<(), SocketProxyError> {
50 if self.default_id == network_id {
52 return Ok(());
53 }
54
55 let socketproxy_network_id = match network_id {
56 Some(id) => {
57 if !self.networks.contains_key(&id) {
58 return Err(SocketProxyError::SetDefaultNonexistentNetwork(id));
59 }
60
61 let id_u32: u32 = match id.get().try_into() {
63 Err(_) => {
64 return Err(SocketProxyError::InvalidInterfaceId(id));
65 }
66 Ok(id) => id,
67 };
68
69 fposix_socket::OptionalUint32::Value(id_u32)
70 }
71 None => fposix_socket::OptionalUint32::Unset(fposix_socket::Empty),
72 };
73
74 self.default_id = network_id;
75
76 Ok(self.fuchsia_networks.set_default(&socketproxy_network_id).await??)
77 }
78
79 pub(crate) async fn handle_default_network_removal(
83 &mut self,
84 ) -> Result<Option<InterfaceId>, SocketProxyError> {
85 if let None = self.default_id {
86 return Ok(None);
88 }
89 let mut interface_ids = self
90 .networks
91 .keys()
92 .filter(|network| Some(network.get()) != self.default_id.map(|id| id.get()))
93 .cloned()
94 .peekable();
95 if interface_ids.peek().is_none() {
96 self.handle_default_network(None).await.map(|_| None)
99 } else {
100 let new_id = interface_ids.into_iter().min();
102 self.handle_default_network(new_id).await.map(|_| new_id)
103 }
104 }
105
106 pub(crate) async fn handle_add_network(
111 &mut self,
112 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
113 ) -> Result<(), SocketProxyError> {
114 let network = fnp_socketproxy::Network::from_watcher_properties(properties)?;
115 match self.networks.entry(InterfaceId(properties.id)) {
116 Entry::Vacant(entry) => {
117 let _ = entry.insert(network.clone());
118 }
119 Entry::Occupied(_entry) => {
120 return Err(SocketProxyError::AddedExistingNetwork(network));
121 }
122 }
123
124 Ok(self.fuchsia_networks.add(&network).await??)
125 }
126
127 pub(crate) async fn handle_remove_network(
132 &mut self,
133 network_id: InterfaceId,
134 ) -> Result<(), SocketProxyError> {
135 if !self.networks.contains_key(&network_id) {
136 return Err(SocketProxyError::RemovedNonexistentNetwork(network_id));
137 } else if self.default_id.map(|id| id == network_id).unwrap_or(false) {
138 return Err(SocketProxyError::RemovedDefaultNetwork(network_id));
139 }
140
141 let id_u32: u32 = match network_id.get().try_into() {
143 Err(_) => {
144 return Err(SocketProxyError::InvalidInterfaceId(network_id));
145 }
146 Ok(id) => id,
147 };
148 let _ = self.networks.remove(&network_id);
149
150 Ok(self.fuchsia_networks.remove(id_u32).await??)
151 }
152
153 pub(crate) async fn handle_interface_no_longer_candidate(&mut self, id: InterfaceId) {
157 let default_id = self.default_id;
158 if default_id == Some(id) {
161 match self.handle_default_network_removal().await {
162 Ok(Some(id)) => {
163 info!(
164 "Successfully updated default network in socketproxy. \
165 Default id was {default_id:?}, is now {id}"
166 );
167 }
168 Ok(None) => {
169 info!(
170 "Successfully unset default network in socketproxy. \
171 No backup network available to fallback"
172 );
173 }
174 Err(e) => {
175 error!(
178 "Failed to reset default network in socketproxy. \
179 Default id was {default_id:?}; {e:?}"
180 );
181 }
182 }
183 }
184
185 match self.handle_remove_network(id).await {
186 Ok(()) => {}
187 Err(SocketProxyError::RemovedNonexistentNetwork(_)) => {
188 }
191 Err(e) => {
192 error!(
193 "Failed to remove network with id ({id}) \
194 from socketproxy; {e:?}"
195 );
196 }
197 }
198 }
199
200 pub(crate) async fn handle_interface_new_candidate(
202 &mut self,
203 properties: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
204 ) {
205 let add_result = self.handle_add_network(properties).await;
206 let set_default_result = match add_result {
207 Ok(()) => match self.default_id {
210 Some(_) => None,
211 None => Some(self.handle_default_network(Some(InterfaceId(properties.id))).await),
212 },
213 Err(_) => None,
214 };
215
216 match (add_result, set_default_result) {
217 (Ok(()), Some(Ok(()))) => info!(
218 "Successfully added online Fuchsia network ({:?}) \
219 to socketproxy and set it as default",
220 properties.id
221 ),
222 (Ok(()), None) => info!(
223 "Successfully added Fuchsia network ({:?}) \
224 to socketproxy but did not attempt to set default, \
225 it might already be set",
226 properties.id
227 ),
228 (Ok(()), Some(Err(e))) => error!(
229 "Sucessfully added online Fuchsia network ({:?}) \
230 to socketproxy but failed to set default; {e:?}",
231 properties.id
232 ),
233 (Err(e), None) => error!(
234 "Failed to add online Fuchsia network ({:?}) to \
235 socketproxy state; {e:?}",
236 properties.id
237 ),
238 (Err(_), Some(Ok(()))) | (Err(_), Some(Err(_))) => panic!(
239 "State not possible for id ({:?}); set_default is never \
240 called when add has an error",
241 properties.id
242 ),
243 };
244 }
245}
246
247#[derive(Clone, Debug, Error)]
250pub enum SocketProxyError {
251 #[error("Error adding network that already exists: {0:?}")]
252 AddedExistingNetwork(fnp_socketproxy::Network),
253 #[error("Error converting the watcher properties to a network: {0}")]
254 ConversionError(#[from] NetworkConversionError),
255 #[error("Error calling FIDL on socketproxy: {0:?}")]
256 Fidl(#[from] fidl::Error),
257 #[error("Error converting id to socketproxy network: {0}")]
258 InvalidInterfaceId(InterfaceId),
259 #[error("Network Registry error: {0:?}")]
260 NetworkRegistry(#[from] NetworkRegistryError),
261 #[error("Error removing a current default network with id: {0}")]
262 RemovedDefaultNetwork(InterfaceId),
263 #[error("Error removing network that does not exist with id: {0}")]
264 RemovedNonexistentNetwork(InterfaceId),
265 #[error("Error setting default network that does not exist with id: {0}")]
266 SetDefaultNonexistentNetwork(InterfaceId),
267}
268
269impl From<fnp_socketproxy::NetworkRegistryAddError> for SocketProxyError {
270 fn from(error: fnp_socketproxy::NetworkRegistryAddError) -> Self {
271 SocketProxyError::NetworkRegistry(NetworkRegistryError::Add(error))
272 }
273}
274
275impl From<fnp_socketproxy::NetworkRegistryRemoveError> for SocketProxyError {
276 fn from(error: fnp_socketproxy::NetworkRegistryRemoveError) -> Self {
277 SocketProxyError::NetworkRegistry(NetworkRegistryError::Remove(error))
278 }
279}
280
281impl From<fnp_socketproxy::NetworkRegistrySetDefaultError> for SocketProxyError {
282 fn from(error: fnp_socketproxy::NetworkRegistrySetDefaultError) -> Self {
283 SocketProxyError::NetworkRegistry(NetworkRegistryError::SetDefault(error))
284 }
285}
286
287pub(crate) fn determine_interface_state_changed(
295 prev: &fnet_interfaces::Properties,
296 curr: &fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest>,
297) -> Option<bool> {
298 let was_candidate = (prev.has_default_ipv4_route.unwrap_or(curr.has_default_ipv4_route)
299 || prev.has_default_ipv6_route.unwrap_or(curr.has_default_ipv6_route))
300 && prev.online.unwrap_or(curr.online);
301 let is_candidate = (curr.has_default_ipv4_route || curr.has_default_ipv6_route) && curr.online;
302 (is_candidate != was_candidate).then_some(is_candidate)
303}
304
305#[cfg(test)]
306pub(crate) mod socketproxy_utils {
307 use futures::{FutureExt as _, StreamExt as _};
308
309 use super::*;
310
311 pub(crate) async fn respond_to_socketproxy(
312 socket_proxy_req_stream: &mut fnp_socketproxy::FuchsiaNetworksRequestStream,
313 result: Result<(), SocketProxyError>,
314 ) {
315 socket_proxy_req_stream
316 .next()
317 .map(|req| match req.expect("request stream ended").expect("receive request") {
318 fnp_socketproxy::FuchsiaNetworksRequest::SetDefault {
319 network_id: _,
320 responder,
321 } => {
322 let res = result.map_err(|e| match e {
323 SocketProxyError::NetworkRegistry(NetworkRegistryError::SetDefault(
324 err,
325 )) => err,
326 _ => unreachable!("should have been SetDefault error variant"),
327 });
328 responder.send(res).expect("respond to SetDefault");
329 }
330 fnp_socketproxy::FuchsiaNetworksRequest::Add { network: _, responder } => {
331 let res = result.map_err(|e| match e {
332 SocketProxyError::NetworkRegistry(NetworkRegistryError::Add(err)) => err,
333 _ => unreachable!("should have been Add error variant"),
334 });
335 responder.send(res).expect("respond to Add");
336 }
337 fnp_socketproxy::FuchsiaNetworksRequest::Update { network: _, responder: _ } => {
338 unreachable!("FuchsiaNetworks has no network properties to update")
339 }
340 fnp_socketproxy::FuchsiaNetworksRequest::Remove { network_id: _, responder } => {
341 let res = result.map_err(|e| match e {
342 SocketProxyError::NetworkRegistry(NetworkRegistryError::Remove(err)) => err,
343 _ => unreachable!("should have been Remove error variant"),
344 });
345 responder.send(res).expect("respond to Remove");
346 }
347 })
348 .await;
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use socket_proxy_testing::respond_to_socketproxy;
355 use std::num::NonZeroU64;
356
357 use super::*;
358
359 fn interface_properties_from_id(
360 id: u64,
361 ) -> fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest> {
362 fnet_interfaces_ext::Properties {
363 id: NonZeroU64::new(id).expect("this is a valid u64"),
364 online: true,
365 name: String::from("network"),
366 has_default_ipv4_route: true,
367 has_default_ipv6_route: true,
368 addresses: vec![],
369 port_class: fnet_interfaces_ext::PortClass::Ethernet,
370 port_identity_koid: Default::default(),
371 }
372 }
373
374 #[fuchsia::test]
375 async fn test_set_default_network_id() -> Result<(), SocketProxyError> {
376 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
377 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
378 let mut state = SocketProxyState::new(fuchsia_networks);
379 const NETWORK_ID_U64: u64 = 1u64;
380 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
381
382 assert_matches::assert_matches!(
385 state.handle_default_network(Some(NETWORK_ID)).await,
386 Err(SocketProxyError::SetDefaultNonexistentNetwork(id))
387 if id.get() == NETWORK_ID_U64
388 );
389
390 assert_matches::assert_matches!(
393 state.networks.insert(
394 NETWORK_ID,
395 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
396 NETWORK_ID_U64
397 ))?,
398 ),
399 None
400 );
401
402 let (set_default_network_result, ()) = futures::join!(
404 state.handle_default_network(Some(NETWORK_ID)),
405 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
406 );
407 assert_matches::assert_matches!(set_default_network_result, Ok(()));
408
409 let (set_default_network_result2, ()) = futures::join!(
411 state.handle_default_network(None),
412 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
413 );
414 assert_matches::assert_matches!(set_default_network_result2, Ok(()));
415
416 Ok(())
417 }
418
419 #[fuchsia::test]
420 async fn test_add_network() {
421 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
422 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
423 let mut state = SocketProxyState::new(fuchsia_networks);
424 const NETWORK_ID1_U32: u32 = 1u32;
425 const NETWORK_ID2_U64: u64 = 2u64;
426
427 let network1 = interface_properties_from_id(NETWORK_ID1_U32.into());
428 let (add_network_result, ()) = futures::join!(
429 state.handle_add_network(&network1),
430 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
431 );
432 assert_matches::assert_matches!(add_network_result, Ok(()));
433
434 assert_matches::assert_matches!(
436 state.handle_add_network(&network1).await,
437 Err(SocketProxyError::AddedExistingNetwork(fnp_socketproxy::Network {
438 network_id: Some(NETWORK_ID1_U32),
439 ..
440 }))
441 );
442
443 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
445 let (add_network_result2, ()) = futures::join!(
446 state.handle_add_network(&network2),
447 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
448 );
449 assert_matches::assert_matches!(add_network_result2, Ok(()));
450 }
451
452 #[fuchsia::test]
453 async fn test_remove_network() -> Result<(), SocketProxyError> {
454 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
455 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
456 let mut state = SocketProxyState::new(fuchsia_networks);
457 const NETWORK_ID_U64: u64 = 1;
458 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
459
460 assert_matches::assert_matches!(
463 state.handle_remove_network(NETWORK_ID).await,
464 Err(SocketProxyError::RemovedNonexistentNetwork(id))
465 if id.get() == NETWORK_ID_U64
466 );
467
468 assert_matches::assert_matches!(
471 state.networks.insert(
472 NETWORK_ID,
473 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
474 NETWORK_ID_U64
475 ),)?,
476 ),
477 None
478 );
479 state.default_id = Some(NETWORK_ID);
480
481 assert_matches::assert_matches!(
483 state.handle_remove_network(NETWORK_ID).await,
484 Err(SocketProxyError::RemovedDefaultNetwork(id))
485 if id.get() == NETWORK_ID_U64
486 );
487
488 state.default_id = None;
491
492 let (remove_network_result, ()) = futures::join!(
495 state.handle_remove_network(NETWORK_ID),
496 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
497 );
498 assert_matches::assert_matches!(remove_network_result, Ok(()));
499 assert_matches::assert_matches!(state.networks.get(&NETWORK_ID), None);
500
501 Ok(())
502 }
503
504 #[fuchsia::test]
505 async fn test_default_network_removal() -> Result<(), SocketProxyError> {
506 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
507 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
508 let mut state = SocketProxyState::new(fuchsia_networks);
509 const NETWORK_ID1_U64: u64 = 1;
510 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
511 const NETWORK_ID2_U64: u64 = 2;
512 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
513
514 assert_matches::assert_matches!(state.handle_default_network_removal().await, Ok(None));
517
518 assert_matches::assert_matches!(
522 state.networks.insert(
523 NETWORK_ID1,
524 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
525 NETWORK_ID1_U64
526 ),)?,
527 ),
528 None
529 );
530 assert_matches::assert_matches!(
531 state.networks.insert(
532 NETWORK_ID2,
533 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
534 NETWORK_ID2_U64
535 ),)?,
536 ),
537 None
538 );
539 state.default_id = Some(NETWORK_ID1);
540
541 let (default_network_removal_result, ()) = futures::join!(
544 state.handle_default_network_removal(),
545 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
546 );
547 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
548 assert_matches::assert_matches!(state.default_id, Some(NETWORK_ID2));
549
550 assert_matches::assert_matches!(
554 state.networks.remove(&NETWORK_ID1),
555 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
556 );
557
558 let (default_network_removal_result2, ()) = futures::join!(
561 state.handle_default_network_removal(),
562 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
563 );
564 assert_matches::assert_matches!(default_network_removal_result2, Ok(None));
565 assert_matches::assert_matches!(state.default_id, None);
566
567 Ok(())
568 }
569
570 #[fuchsia::test]
571 async fn test_multiple_operations() {
572 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
573 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
574 let mut state = SocketProxyState::new(fuchsia_networks);
575 const NETWORK_ID1_U64: u64 = 1;
576 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
577 const NETWORK_ID2_U64: u64 = 2;
578 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
579
580 let network1 = interface_properties_from_id(NETWORK_ID1_U64);
582 let (add_network_result, ()) = futures::join!(
583 state.handle_add_network(&network1),
584 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
585 );
586 assert_matches::assert_matches!(add_network_result, Ok(()));
587
588 let (set_default_network_result, ()) = futures::join!(
590 state.handle_default_network(Some(NETWORK_ID1)),
591 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
592 );
593 assert_matches::assert_matches!(set_default_network_result, Ok(()));
594
595 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
597 let (add_network_result, ()) = futures::join!(
598 state.handle_add_network(&network2),
599 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
600 );
601 assert_matches::assert_matches!(add_network_result, Ok(()));
602
603 assert_matches::assert_matches!(
606 state.handle_remove_network(NETWORK_ID1).await,
607 Err(SocketProxyError::RemovedDefaultNetwork(id))
608 if id.get() == NETWORK_ID1_U64
609 );
610
611 assert!(state.networks.get(&NETWORK_ID1).is_some());
613
614 let (default_network_removal_result, ()) = futures::join!(
616 state.handle_default_network_removal(),
617 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
618 );
619 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
620
621 assert_matches::assert_matches!(
623 state.networks.remove(&NETWORK_ID1),
624 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
625 );
626 }
627}