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::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
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            })
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        // Attempt to set a network as default when it isn't known
383        // to the SocketProxyState.
384        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        // Add a network without 'officially' adding it so we can
391        // test `handle_default_network` in isolation.
392        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        // Set the default network as an existing network.
403        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        // Unset the default network.
410        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        // Ensure we cannot add a network with the same id twice.
435        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        // Ensure we can add a network with a different id.
444        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        // Attempt to remove a network when it isn't known
461        // to the SocketProxyState.
462        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        // Add a network and make it default without 'officially' adding or
469        // setting it so we can test `handle_remove_network` in isolation.
470        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        // Attempt to remove the network although it is the default network.
482        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        // Directly unset the default network so we can test
489        // `handle_remove_network` in isolation.
490        state.default_id = None;
491
492        // Attempt to remove the network. This should succeed since it is
493        // not the current default network.
494        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        // Attempting to remove the default network when one does not exist
515        // should result in Ok(None).
516        assert_matches::assert_matches!(state.handle_default_network_removal().await, Ok(None));
517
518        // Add two networks and set one as the default without 'officially'
519        // adding or setting it so we can test `handle_default_network_removal`
520        // in isolation.
521        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        // Remove the default network. Since NETWORK_ID1 is the current default
542        // then NETWORK_ID2 should be the chosen fallback.
543        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        // `handle_default_network_removal` does not remove the network itself,
551        // it performs the logic to fallback to another default network or
552        // unset it if one does not exist. Remove NETWORK_ID1 manually.
553        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        // Remove the default network. Since there is no other network, then
559        // the default network should be unset.
560        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        // Add a network.
581        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        // Set the default network.
589        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        // Add another network.
596        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        // Attempt to remove the first network. We should get an error
604        // since network1 is the current default network.
605        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        // Ensure the first network still exists.
612        assert!(state.networks.get(&NETWORK_ID1).is_some());
613
614        // Set the default network to the second network.
615        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        // Remove the first network as it is no longer the default network.
622        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}