netstack_proxy/
main.rs

1// Copyright 2023 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
5//! Provides a transparent netstack proxy.
6//!
7//! The netstack proxy reads the network stack version it wants to use from
8//! fuchsia.net.stackmigrationdeprecated.Control and spawns the appropriate
9//! netstack binary from its own package.
10//!
11//! The directory request handle is passed directly to the spawned netstack.
12//!
13//! The incoming namespace for the spawned netstack is carefully constructed to
14//! extract out the capabilities that are routed to netstack-proxy that are not
15//! used by netstack itself.
16
17use fidl::endpoints::{DiscoverableProtocolMarker, Proxy as _, RequestStream as _};
18use futures::{FutureExt as _, StreamExt as _};
19use vfs::directory::helper::DirectlyMutable;
20use {
21    fidl_fuchsia_net_stackmigrationdeprecated as fnet_migration,
22    fidl_fuchsia_process_lifecycle as fprocess_lifecycle, fuchsia_async as fasync,
23};
24
25#[fasync::run_singlethreaded]
26pub async fn main() -> std::process::ExitCode {
27    // Start by getting the Netstack version we should use.
28    let current_boot_version = {
29        let migration =
30            fuchsia_component::client::connect_to_protocol::<fnet_migration::StateMarker>()
31                .expect("connect to protocol");
32        let fnet_migration::InEffectVersion { current_boot, .. } =
33            migration.get_netstack_version().await.expect("failed to read netstack version");
34        current_boot
35    };
36
37    println!("netstack migration proxy using version {current_boot_version:?}");
38    let bin_path = match current_boot_version {
39        fnet_migration::NetstackVersion::Netstack2 => c"/pkg/bin/netstack",
40        fnet_migration::NetstackVersion::Netstack3 => c"/pkg/bin/netstack3",
41    };
42
43    let ns = fdio::Namespace::installed().expect("failed to get namespace");
44    let mut entries = ns
45        .export()
46        .expect("failed to export namespace entries")
47        .into_iter()
48        .filter_map(|fdio::NamespaceEntry { handle, path }| match path.as_str() {
49            "/" => {
50                panic!("unexpected non flat namespace, bad capabilities will bleed into netstack")
51            }
52            "/svc" => None,
53            x => {
54                Some((Some(handle), std::ffi::CString::new(x).expect("failed to create C string")))
55            }
56        })
57        .collect::<Vec<_>>();
58
59    let handle =
60        fuchsia_runtime::take_startup_handle(fuchsia_runtime::HandleType::DirectoryRequest.into())
61            .expect("missing startup handle");
62
63    let mut actions = vec![fdio::SpawnAction::add_handle(
64        fuchsia_runtime::HandleInfo::new(fuchsia_runtime::HandleType::DirectoryRequest, 0),
65        handle,
66    )];
67
68    actions.extend(entries.iter_mut().map(|(handle, path)| {
69        // Handle is always Some here, we use an option so we can take it from
70        // entries while entries keeps the CString backing.
71        let handle = handle.take().unwrap();
72        fdio::SpawnAction::add_namespace_entry(path.as_c_str(), handle)
73    }));
74
75    const LIFECYCLE_HANDLE_INFO: fuchsia_runtime::HandleInfo =
76        fuchsia_runtime::HandleInfo::new(fuchsia_runtime::HandleType::Lifecycle, 0);
77    let process_lifecycle = fuchsia_runtime::take_startup_handle(LIFECYCLE_HANDLE_INFO)
78        .expect("missing lifecycle handle");
79
80    let inner_lifecycle_proxy = match current_boot_version {
81        // Netstack2 doesn't support clean shutdown.
82        fnet_migration::NetstackVersion::Netstack2 => None,
83        fnet_migration::NetstackVersion::Netstack3 => {
84            // Create a proxy lifecycle channel that we'll use to tell netstack3
85            // to stop.
86            let (proxy, server) =
87                fidl::endpoints::create_proxy::<fprocess_lifecycle::LifecycleMarker>();
88            actions.push(fdio::SpawnAction::add_handle(
89                LIFECYCLE_HANDLE_INFO,
90                server.into_channel().into(),
91            ));
92            Some(proxy)
93        }
94    };
95
96    let svc = vfs::directory::immutable::simple::simple();
97    for s in std::fs::read_dir("/svc").expect("failed to get /svc entries") {
98        let entry = s.expect("failed to get directory entry");
99        let name = entry.file_name();
100        let name = name.to_str().expect("failed to get file name");
101
102        // Don't allow Netstack to see the services that we use exclusively to
103        // enable proxying.
104        let block_services = [
105            fidl_fuchsia_process::LauncherMarker::PROTOCOL_NAME,
106            fnet_migration::StateMarker::PROTOCOL_NAME,
107        ];
108        if block_services.into_iter().any(|s| s == name) {
109            continue;
110        }
111        svc.add_entry(
112            name,
113            vfs::service::endpoint(move |_, channel| {
114                fuchsia_component::client::connect_channel_to_protocol_at_path(
115                    channel.into(),
116                    entry.path().to_str().expect("failed to get entry path"),
117                )
118                .unwrap_or_else(|e| eprintln!("error connecting to protocol {:?}", e));
119            }),
120        )
121        .unwrap_or_else(|e| panic!("failed to add entry {name}: {e:?}"));
122    }
123
124    let svc_dir = vfs::directory::serve_read_only(svc);
125    let handle = svc_dir.into_client_end().unwrap().into();
126    actions.push(fdio::SpawnAction::add_namespace_entry(c"/svc", handle));
127
128    // Pass down the configuration VMO if we have it.
129    let config_vmo_handle_info = fuchsia_runtime::HandleType::ComponentConfigVmo.into();
130    if let Some(config_vmo) = fuchsia_runtime::take_startup_handle(config_vmo_handle_info) {
131        actions.push(fdio::SpawnAction::add_handle(config_vmo_handle_info, config_vmo))
132    }
133
134    let netstack_process = fdio::spawn_etc(
135        &fuchsia_runtime::job_default(),
136        fdio::SpawnOptions::CLONE_ALL - fdio::SpawnOptions::CLONE_NAMESPACE,
137        bin_path,
138        &[bin_path],
139        None,
140        &mut actions[..],
141    )
142    .expect("failed to spawn netstack");
143
144    let mut process_lifecycle = fprocess_lifecycle::LifecycleRequestStream::from_channel(
145        fasync::Channel::from_channel(process_lifecycle.into()).into(),
146    )
147    .filter_map(|r| {
148        futures::future::ready(match r {
149            Ok(r) => Some(r),
150            Err(e) => {
151                eprintln!("process lifecycle FIDL error {e:?}");
152                None
153            }
154        })
155    });
156
157    let mut wait_signals =
158        fasync::OnSignals::new(&netstack_process, zx::Signals::PROCESS_TERMINATED)
159            .map(|s| s.expect("failed to observe process termination signals"));
160    let request = futures::select! {
161        signals = wait_signals => {
162            println!("netstack exited unexpectedly with {signals:?}");
163            return std::process::ExitCode::FAILURE;
164        },
165        // If the stream terminates just wait for netstack to go away.
166        r = process_lifecycle.select_next_some() => r,
167    };
168
169    let fprocess_lifecycle::LifecycleRequest::Stop { control_handle } = request;
170    // Must drop the control_handle to unwrap the
171    // lifecycle channel.
172    std::mem::drop(control_handle);
173    let (process_lifecycle, _terminated): (_, bool) = process_lifecycle.into_inner().into_inner();
174    let process_lifecycle = std::sync::Arc::try_unwrap(process_lifecycle)
175        .expect("failed to retrieve lifecycle channel");
176    let process_lifecycle: zx::Channel = process_lifecycle.into_channel().into_zx_channel();
177    if let Some(inner) = inner_lifecycle_proxy {
178        inner
179            .stop()
180            .unwrap_or_else(|e| eprintln!("failed to request stop for inner netstack: {e:?}"));
181        // Notify that we're done only on process exit.
182        std::mem::forget(process_lifecycle);
183    } else {
184        // We're not proxying any channels, let component framework take us down
185        // anytime.
186        std::mem::drop(process_lifecycle);
187    }
188
189    let signals = wait_signals.await;
190    assert!(signals.contains(zx::Signals::PROCESS_TERMINATED));
191    // Process is terminated, loosely mimic its exit code.
192    let zx::ProcessInfo { return_code, .. } =
193        netstack_process.info().expect("reading netstack process info");
194    println!("netstack process exited with return code {return_code}");
195    if return_code == 0 {
196        std::process::ExitCode::SUCCESS
197    } else {
198        std::process::ExitCode::FAILURE
199    }
200}