netcfg/
socketproxy.rs

1// Copyright 2025 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 {
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
34// Fuchsia Networks should only be added to the socketproxy when the link has a default v4 and/or
35// v6 route. The functions that propagate calls to the socketproxy prioritize maintaining a correct
36// version of local state and logging an error if the socketproxy state is not aligned.
37impl SocketProxyState {
38    pub fn new(fuchsia_networks: fnp_socketproxy::FuchsiaNetworksProxy) -> Self {
39        Self { fuchsia_networks, default_id: None, networks: HashMap::new() }
40    }
41
42    // Set or unset an existing Fuchsia network as the default in the socket proxy registry.
43    //
44    // # Errors
45    // The network does not exist
46    pub(crate) async fn handle_default_network(
47        &mut self,
48        network_id: Option<InterfaceId>,
49    ) -> Result<(), SocketProxyError> {
50        // Default id is already the same, no reason to change the default network.
51        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                // We expect interface ids to safely fit in the range of u32 values.
62                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    /// Handle the removal of the current default network. When other Fuchsia networks exist,
80    /// fallback to one of them instead, prioritizing the network with the lowest id. If no other
81    /// networks exist, the Fuchsia default network will be set to None.
82    pub(crate) async fn handle_default_network_removal(
83        &mut self,
84    ) -> Result<Option<InterfaceId>, SocketProxyError> {
85        if let None = self.default_id {
86            // Do nothing. There is no default network.
87            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            // No need to fallback to another Fuchsia network if one doesn't
97            // exist. Simply set the default to None.
98            self.handle_default_network(None).await.map(|_| None)
99        } else {
100            // Fallback to the network with the lowest id.
101            let new_id = interface_ids.into_iter().min();
102            self.handle_default_network(new_id).await.map(|_| new_id)
103        }
104    }
105
106    /// Add a new Fuchsia network to the socket proxy registry.
107    ///
108    /// # Errors
109    /// The network already exists
110    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    /// Remove an existing Fuchsia network in the socket proxy registry.
128    ///
129    /// # Errors
130    /// The network does not exist or is the current default network
131    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        // We expect interface ids to safely fit in the range of u32 values.
142        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    // Handle an existing interface that no longer meets the criteria to be provided
154    // to the socketproxy. For example, if the interface lost its default routes or
155    // was `Removed`.
156    pub(crate) async fn handle_interface_no_longer_candidate(&mut self, id: InterfaceId) {
157        let default_id = self.default_id;
158        // Ensure that the network is unset as the default first
159        // prior to removing it.
160        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                    // Even with this error, still attempt to remove the network
176                    // in case there is some misaligned state.
177                    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                // It is possible that the interface got removed before
189                // it met the conditions to be added to the socketproxy.
190            }
191            Err(e) => {
192                error!(
193                    "Failed to remove network with id ({id}) \
194                from socketproxy; {e:?}"
195                );
196            }
197        }
198    }
199
200    // Handle a new interface newly meeting the criteria to be provided to the socketproxy.
201    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            // When the new network is added successfully, attempt to set it as the
208            // default network if one does not exist already.
209            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/// Errors produced when maintaining registry state or communicating
248/// updates to the socket proxy.
249#[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
287// Using the interface's current v4/v6 default route properties and v4/v6 default route
288// properties provided through `fnet_interfaces_ext::Event::Changed`, determine
289// whether the interface gained or lost candidacy because of the event, or maintained
290// the same state.
291//
292// Returns None when the candidacy has not changed, Some(true) when the interface
293// gains candidacy, and Some(false) when the interface loses candidacy.
294pub(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        // Attempt to set a network as default when it isn't known
385        // to the SocketProxyState.
386        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        // Add a network without 'officially' adding it so we can
393        // test `handle_default_network` in isolation.
394        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        // Set the default network as an existing network.
405        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        // Unset the default network.
412        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        // Ensure we cannot add a network with the same id twice.
437        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        // Ensure we can add a network with a different id.
446        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        // Attempt to remove a network when it isn't known
463        // to the SocketProxyState.
464        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        // Add a network and make it default without 'officially' adding or
471        // setting it so we can test `handle_remove_network` in isolation.
472        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        // Attempt to remove the network although it is the default network.
484        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        // Directly unset the default network so we can test
491        // `handle_remove_network` in isolation.
492        state.default_id = None;
493
494        // Attempt to remove the network. This should succeed since it is
495        // not the current default network.
496        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        // Attempting to remove the default network when one does not exist
517        // should result in Ok(None).
518        assert_matches::assert_matches!(state.handle_default_network_removal().await, Ok(None));
519
520        // Add two networks and set one as the default without 'officially'
521        // adding or setting it so we can test `handle_default_network_removal`
522        // in isolation.
523        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        // Remove the default network. Since NETWORK_ID1 is the current default
544        // then NETWORK_ID2 should be the chosen fallback.
545        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        // `handle_default_network_removal` does not remove the network itself,
553        // it performs the logic to fallback to another default network or
554        // unset it if one does not exist. Remove NETWORK_ID1 manually.
555        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        // Remove the default network. Since there is no other network, then
561        // the default network should be unset.
562        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        // Add a network.
583        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        // Set the default network.
591        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        // Add another network.
598        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        // Attempt to remove the first network. We should get an error
606        // since network1 is the current default network.
607        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        // Ensure the first network still exists.
614        assert!(state.networks.get(&NETWORK_ID1).is_some());
615
616        // Set the default network to the second network.
617        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        // Remove the first network as it is no longer the default network.
624        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}