Skip to main content

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