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::hash_map::Entry;
15use std::collections::HashMap;
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 fnp_socketproxy::FuchsiaNetworksRequest::CheckPresence { responder: _ } => {
348 unreachable!("not called in tests");
349 }
350 })
351 .await;
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use socket_proxy_testing::respond_to_socketproxy;
358 use std::num::NonZeroU64;
359
360 use super::*;
361
362 fn interface_properties_from_id(
363 id: u64,
364 ) -> fnet_interfaces_ext::Properties<fnet_interfaces_ext::DefaultInterest> {
365 fnet_interfaces_ext::Properties {
366 id: NonZeroU64::new(id).expect("this is a valid u64"),
367 online: true,
368 name: String::from("network"),
369 has_default_ipv4_route: true,
370 has_default_ipv6_route: true,
371 addresses: vec![],
372 port_class: fnet_interfaces_ext::PortClass::Ethernet,
373 }
374 }
375
376 #[fuchsia::test]
377 async fn test_set_default_network_id() -> Result<(), SocketProxyError> {
378 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
379 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
380 let mut state = SocketProxyState::new(fuchsia_networks);
381 const NETWORK_ID_U64: u64 = 1u64;
382 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
383
384 assert_matches::assert_matches!(
387 state.handle_default_network(Some(NETWORK_ID)).await,
388 Err(SocketProxyError::SetDefaultNonexistentNetwork(id))
389 if id.get() == NETWORK_ID_U64
390 );
391
392 assert_matches::assert_matches!(
395 state.networks.insert(
396 NETWORK_ID,
397 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
398 NETWORK_ID_U64
399 ))?,
400 ),
401 None
402 );
403
404 let (set_default_network_result, ()) = futures::join!(
406 state.handle_default_network(Some(NETWORK_ID)),
407 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
408 );
409 assert_matches::assert_matches!(set_default_network_result, Ok(()));
410
411 let (set_default_network_result2, ()) = futures::join!(
413 state.handle_default_network(None),
414 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
415 );
416 assert_matches::assert_matches!(set_default_network_result2, Ok(()));
417
418 Ok(())
419 }
420
421 #[fuchsia::test]
422 async fn test_add_network() {
423 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
424 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
425 let mut state = SocketProxyState::new(fuchsia_networks);
426 const NETWORK_ID1_U32: u32 = 1u32;
427 const NETWORK_ID2_U64: u64 = 2u64;
428
429 let network1 = interface_properties_from_id(NETWORK_ID1_U32.into());
430 let (add_network_result, ()) = futures::join!(
431 state.handle_add_network(&network1),
432 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
433 );
434 assert_matches::assert_matches!(add_network_result, Ok(()));
435
436 assert_matches::assert_matches!(
438 state.handle_add_network(&network1).await,
439 Err(SocketProxyError::AddedExistingNetwork(fnp_socketproxy::Network {
440 network_id: Some(NETWORK_ID1_U32),
441 ..
442 }))
443 );
444
445 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
447 let (add_network_result2, ()) = futures::join!(
448 state.handle_add_network(&network2),
449 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
450 );
451 assert_matches::assert_matches!(add_network_result2, Ok(()));
452 }
453
454 #[fuchsia::test]
455 async fn test_remove_network() -> Result<(), SocketProxyError> {
456 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
457 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
458 let mut state = SocketProxyState::new(fuchsia_networks);
459 const NETWORK_ID_U64: u64 = 1;
460 const NETWORK_ID: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID_U64).unwrap());
461
462 assert_matches::assert_matches!(
465 state.handle_remove_network(NETWORK_ID).await,
466 Err(SocketProxyError::RemovedNonexistentNetwork(id))
467 if id.get() == NETWORK_ID_U64
468 );
469
470 assert_matches::assert_matches!(
473 state.networks.insert(
474 NETWORK_ID,
475 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
476 NETWORK_ID_U64
477 ),)?,
478 ),
479 None
480 );
481 state.default_id = Some(NETWORK_ID);
482
483 assert_matches::assert_matches!(
485 state.handle_remove_network(NETWORK_ID).await,
486 Err(SocketProxyError::RemovedDefaultNetwork(id))
487 if id.get() == NETWORK_ID_U64
488 );
489
490 state.default_id = None;
493
494 let (remove_network_result, ()) = futures::join!(
497 state.handle_remove_network(NETWORK_ID),
498 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
499 );
500 assert_matches::assert_matches!(remove_network_result, Ok(()));
501 assert_matches::assert_matches!(state.networks.get(&NETWORK_ID), None);
502
503 Ok(())
504 }
505
506 #[fuchsia::test]
507 async fn test_default_network_removal() -> Result<(), SocketProxyError> {
508 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
509 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
510 let mut state = SocketProxyState::new(fuchsia_networks);
511 const NETWORK_ID1_U64: u64 = 1;
512 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
513 const NETWORK_ID2_U64: u64 = 2;
514 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
515
516 assert_matches::assert_matches!(state.handle_default_network_removal().await, Ok(None));
519
520 assert_matches::assert_matches!(
524 state.networks.insert(
525 NETWORK_ID1,
526 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
527 NETWORK_ID1_U64
528 ),)?,
529 ),
530 None
531 );
532 assert_matches::assert_matches!(
533 state.networks.insert(
534 NETWORK_ID2,
535 fnp_socketproxy::Network::from_watcher_properties(&interface_properties_from_id(
536 NETWORK_ID2_U64
537 ),)?,
538 ),
539 None
540 );
541 state.default_id = Some(NETWORK_ID1);
542
543 let (default_network_removal_result, ()) = futures::join!(
546 state.handle_default_network_removal(),
547 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
548 );
549 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
550 assert_matches::assert_matches!(state.default_id, Some(NETWORK_ID2));
551
552 assert_matches::assert_matches!(
556 state.networks.remove(&NETWORK_ID1),
557 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
558 );
559
560 let (default_network_removal_result2, ()) = futures::join!(
563 state.handle_default_network_removal(),
564 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
565 );
566 assert_matches::assert_matches!(default_network_removal_result2, Ok(None));
567 assert_matches::assert_matches!(state.default_id, None);
568
569 Ok(())
570 }
571
572 #[fuchsia::test]
573 async fn test_multiple_operations() {
574 let (fuchsia_networks, mut fuchsia_networks_req_stream) =
575 fidl::endpoints::create_proxy_and_stream::<fnp_socketproxy::FuchsiaNetworksMarker>();
576 let mut state = SocketProxyState::new(fuchsia_networks);
577 const NETWORK_ID1_U64: u64 = 1;
578 const NETWORK_ID1: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID1_U64).unwrap());
579 const NETWORK_ID2_U64: u64 = 2;
580 const NETWORK_ID2: InterfaceId = InterfaceId(NonZeroU64::new(NETWORK_ID2_U64).unwrap());
581
582 let network1 = interface_properties_from_id(NETWORK_ID1_U64);
584 let (add_network_result, ()) = futures::join!(
585 state.handle_add_network(&network1),
586 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
587 );
588 assert_matches::assert_matches!(add_network_result, Ok(()));
589
590 let (set_default_network_result, ()) = futures::join!(
592 state.handle_default_network(Some(NETWORK_ID1)),
593 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
594 );
595 assert_matches::assert_matches!(set_default_network_result, Ok(()));
596
597 let network2 = interface_properties_from_id(NETWORK_ID2_U64);
599 let (add_network_result, ()) = futures::join!(
600 state.handle_add_network(&network2),
601 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
602 );
603 assert_matches::assert_matches!(add_network_result, Ok(()));
604
605 assert_matches::assert_matches!(
608 state.handle_remove_network(NETWORK_ID1).await,
609 Err(SocketProxyError::RemovedDefaultNetwork(id))
610 if id.get() == NETWORK_ID1_U64
611 );
612
613 assert!(state.networks.get(&NETWORK_ID1).is_some());
615
616 let (default_network_removal_result, ()) = futures::join!(
618 state.handle_default_network_removal(),
619 respond_to_socketproxy(&mut fuchsia_networks_req_stream, Ok(()))
620 );
621 assert_matches::assert_matches!(default_network_removal_result, Ok(Some(NETWORK_ID2)));
622
623 assert_matches::assert_matches!(
625 state.networks.remove(&NETWORK_ID1),
626 Some(network) if network.network_id.unwrap() == NETWORK_ID1_U64 as u32
627 );
628 }
629}