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 _};
18use vfs::directory::helper::DirectlyMutable;
19use {fidl_fuchsia_net_stackmigrationdeprecated as fnet_migration, fuchsia_async as fasync};
20
21#[fasync::run_singlethreaded]
22pub async fn main() -> std::process::ExitCode {
23    // Start by getting the Netstack version we should use.
24    let current_boot_version = {
25        let migration =
26            fuchsia_component::client::connect_to_protocol::<fnet_migration::StateMarker>()
27                .expect("connect to protocol");
28        let fnet_migration::InEffectVersion { current_boot, .. } =
29            migration.get_netstack_version().await.expect("failed to read netstack version");
30        current_boot
31    };
32
33    println!("netstack migration proxy using version {current_boot_version:?}");
34    let bin_path = match current_boot_version {
35        fnet_migration::NetstackVersion::Netstack2 => c"/pkg/bin/netstack",
36        fnet_migration::NetstackVersion::Netstack3 => c"/pkg/bin/netstack3",
37    };
38
39    let ns = fdio::Namespace::installed().expect("failed to get namespace");
40    let mut entries = ns
41        .export()
42        .expect("failed to export namespace entries")
43        .into_iter()
44        .filter_map(|fdio::NamespaceEntry { handle, path }| match path.as_str() {
45            "/" => {
46                panic!("unexpected non flat namespace, bad capabilities will bleed into netstack")
47            }
48            "/svc" => None,
49            x => {
50                Some((Some(handle), std::ffi::CString::new(x).expect("failed to create C string")))
51            }
52        })
53        .collect::<Vec<_>>();
54
55    let handle =
56        fuchsia_runtime::take_startup_handle(fuchsia_runtime::HandleType::DirectoryRequest.into())
57            .expect("missing startup handle");
58
59    let mut actions = vec![fdio::SpawnAction::add_handle(
60        fuchsia_runtime::HandleInfo::new(fuchsia_runtime::HandleType::DirectoryRequest, 0),
61        handle,
62    )];
63
64    actions.extend(entries.iter_mut().map(|(handle, path)| {
65        // Handle is always Some here, we use an option so we can take it from
66        // entries while entries keeps the CString backing.
67        let handle = handle.take().unwrap();
68        fdio::SpawnAction::add_namespace_entry(path.as_c_str(), handle)
69    }));
70
71    let svc = vfs::directory::immutable::simple::simple();
72    for s in std::fs::read_dir("/svc").expect("failed to get /svc entries") {
73        let entry = s.expect("failed to get directory entry");
74        let name = entry.file_name();
75        let name = name.to_str().expect("failed to get file name");
76
77        // Don't allow Netstack to see the services that we use exclusively to
78        // enable proxying.
79        let block_services = [
80            fidl_fuchsia_process::LauncherMarker::PROTOCOL_NAME,
81            fnet_migration::StateMarker::PROTOCOL_NAME,
82        ];
83        if block_services.into_iter().any(|s| s == name) {
84            continue;
85        }
86        svc.add_entry(
87            name,
88            vfs::service::endpoint(move |_, channel| {
89                fuchsia_component::client::connect_channel_to_protocol_at_path(
90                    channel.into(),
91                    entry.path().to_str().expect("failed to get entry path"),
92                )
93                .unwrap_or_else(|e| eprintln!("error connecting to protocol {:?}", e));
94            }),
95        )
96        .unwrap_or_else(|e| panic!("failed to add entry {name}: {e:?}"));
97    }
98
99    let svc_dir = vfs::directory::serve_read_only(svc);
100    let handle = svc_dir.into_client_end().unwrap().into();
101    actions.push(fdio::SpawnAction::add_namespace_entry(c"/svc", handle));
102
103    // Pass down the configuration VMO if we have it.
104    let config_vmo_handle_info = fuchsia_runtime::HandleType::ComponentConfigVmo.into();
105    if let Some(config_vmo) = fuchsia_runtime::take_startup_handle(config_vmo_handle_info) {
106        actions.push(fdio::SpawnAction::add_handle(config_vmo_handle_info, config_vmo))
107    }
108
109    let proc = fdio::spawn_etc(
110        &fuchsia_runtime::job_default(),
111        fdio::SpawnOptions::CLONE_ALL - fdio::SpawnOptions::CLONE_NAMESPACE,
112        bin_path,
113        &[bin_path],
114        None,
115        &mut actions[..],
116    )
117    .expect("failed to spawn netstack");
118
119    let signals = fasync::OnSignals::new(&proc, zx::Signals::PROCESS_TERMINATED)
120        .await
121        .expect("failed to observe process termination signals");
122    println!("netstack exited unexpectedly with {signals:?}");
123
124    // TODO(https://fxbug.dev/380897722) Inherit the exit code of the proxied netstack process once
125    // netstack supports clean shutdown.
126    std::process::ExitCode::FAILURE
127}