guest_cli/
socat.rs

1// Copyright 2022 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.
4use crate::platform::{GuestConsole, PlatformServices};
5use fidl_fuchsia_virtualization::{
6    GuestManagerProxy, GuestMarker, GuestStatus, HostVsockAcceptorMarker, HostVsockEndpointMarker,
7    HostVsockEndpointProxy,
8};
9use futures::TryStreamExt;
10use std::fmt;
11use {guest_cli_args as arguments, zx_status};
12
13#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
14pub enum SocatResult {
15    SocatSuccess(SocatSuccess),
16    SocatError(SocatError),
17}
18
19impl fmt::Display for SocatResult {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            SocatResult::SocatSuccess(v) => write!(f, "{}", v),
23            SocatResult::SocatError(v) => write!(f, "{}", v),
24        }
25    }
26}
27
28#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
29pub enum SocatSuccess {
30    Connected,
31    Listened(u32),
32}
33
34impl fmt::Display for SocatSuccess {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            SocatSuccess::Connected => write!(f, "Disconnected after a successful connect"),
38            SocatSuccess::Listened(port) => write!(f, "Stopped listening on port {}", port),
39        }
40    }
41}
42
43#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
44pub enum SocatError {
45    NotRunning,
46    NoVsockDevice,
47    NoListener(u32),
48    FailedToListen(u32),
49    InternalFailure(String),
50}
51
52impl fmt::Display for SocatError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            SocatError::NotRunning => write!(f, "Can't attach to a non-running guest"),
56            SocatError::NoListener(port) => write!(f, "No listener on port {}", port),
57            SocatError::FailedToListen(port) => write!(f, "Already a listener on port {}", port),
58            SocatError::InternalFailure(err) => write!(f, "Internal error: {}", err),
59            SocatError::NoVsockDevice => write!(f, "Guest lacks the required vsock device"),
60        }
61    }
62}
63
64impl std::convert::From<fidl::Status> for SocatError {
65    fn from(err: fidl::Status) -> SocatError {
66        SocatError::InternalFailure(format!("FIDL status - {}", err))
67    }
68}
69
70impl std::convert::From<fidl::Error> for SocatError {
71    fn from(err: fidl::Error) -> SocatError {
72        SocatError::InternalFailure(format!("FIDL error - {}", err))
73    }
74}
75
76fn duplicate_socket(_socket: fidl::Socket) -> Result<(fidl::Socket, fidl::Socket), SocatError> {
77    #[cfg(target_os = "fuchsia")]
78    {
79        use fidl::HandleBased;
80
81        let other = _socket.duplicate_handle(fidl::Rights::SAME_RIGHTS)?;
82        Ok((_socket, other))
83    }
84
85    // TODO(https://fxbug.dev/42068091): Remove when overnet supports duplicated socket handles.
86    #[cfg(not(target_os = "fuchsia"))]
87    unimplemented!()
88}
89
90async fn handle_socat_listen(
91    vsock_endpoint: HostVsockEndpointProxy,
92    host_port: u32,
93) -> Result<SocatSuccess, SocatError> {
94    let (vsock_accept_client, mut vsock_acceptor_stream) =
95        fidl::endpoints::create_request_stream::<HostVsockAcceptorMarker>();
96
97    vsock_endpoint
98        .listen(host_port, vsock_accept_client)
99        .await?
100        .map_err(|_| SocatError::FailedToListen(host_port))?;
101
102    // Try_next returns a Result<Option<T>, Err>, hence we need to unwrap our value
103    let connection = vsock_acceptor_stream
104        .try_next()
105        .await?
106        .ok_or_else(|| SocatError::InternalFailure("unexpected end of stream".to_string()))?;
107
108    let (_src_cid, _src_port, port, responder) = connection
109        .into_accept()
110        .ok_or_else(|| SocatError::InternalFailure("unexpected message on stream".to_string()))?;
111
112    if port != host_port {
113        responder.send(Err(zx_status::Status::CONNECTION_REFUSED.into_raw()))?;
114        return Err(SocatError::InternalFailure(
115            "connection attempt on unexpected port".to_string(),
116        ));
117    }
118
119    let (socket, remote_socket) = fidl::Socket::create_stream();
120    responder.send(Ok(remote_socket))?;
121
122    let (input, output) = duplicate_socket(socket)?;
123
124    let console = GuestConsole::new(input, output)
125        .map_err(|err| SocatError::InternalFailure(format!("failed to create console: {}", err)))?;
126    if let Err(err) = console.run_with_stdio().await {
127        Err(SocatError::InternalFailure(format!("failed to run console: {}", err)))
128    } else {
129        Ok(SocatSuccess::Listened(host_port))
130    }
131}
132
133async fn handle_socat_connect(
134    vsock_endpoint: HostVsockEndpointProxy,
135    port: u32,
136) -> Result<SocatSuccess, SocatError> {
137    let Ok(socket) = vsock_endpoint.connect(port).await? else {
138        return Err(SocatError::NoListener(port));
139    };
140
141    let (input, output) = duplicate_socket(socket)?;
142    let console = GuestConsole::new(input, output)
143        .map_err(|err| SocatError::InternalFailure(format!("failed to create console: {}", err)))?;
144
145    if let Err(err) = console.run_with_stdio().await {
146        Err(SocatError::InternalFailure(format!("failed to run console: {}", err)))
147    } else {
148        Ok(SocatSuccess::Connected)
149    }
150}
151
152async fn connect_to_vsock_endpoint(
153    manager: GuestManagerProxy,
154) -> Result<HostVsockEndpointProxy, SocatError> {
155    let (guest_endpoint, guest_server_end) = fidl::endpoints::create_proxy::<GuestMarker>();
156    manager
157        .connect(guest_server_end)
158        .await?
159        .map_err(|err| SocatError::InternalFailure(format!("failed to connect: {:?}", err)))?;
160
161    let (vsock_endpoint, vsock_server_end) =
162        fidl::endpoints::create_proxy::<HostVsockEndpointMarker>();
163
164    guest_endpoint
165        .get_host_vsock_endpoint(vsock_server_end)
166        .await?
167        .map_err(|_| SocatError::NoVsockDevice)?;
168
169    Ok(vsock_endpoint)
170}
171
172async fn get_manager<P: PlatformServices>(
173    services: &P,
174    guest_type: arguments::GuestType,
175) -> Result<GuestManagerProxy, SocatError> {
176    if guest_type == arguments::GuestType::Zircon {
177        // We don't have a Zircon vsock device.
178        return Err(SocatError::NoVsockDevice);
179    }
180
181    let guest_manager = services.connect_to_manager(guest_type).await.map_err(|err| {
182        SocatError::InternalFailure(format!("failed to connect to manager: {}", err))
183    })?;
184
185    let guest_info = guest_manager.get_info().await?;
186    let status = guest_info.guest_status.expect("guest status should always be set");
187    if status != GuestStatus::Starting && status != GuestStatus::Running {
188        return Err(SocatError::NotRunning);
189    }
190
191    Ok(guest_manager)
192}
193
194async fn handle_impl<P: PlatformServices>(
195    services: &P,
196    args: &arguments::socat_args::SocatArgs,
197) -> Result<SocatSuccess, SocatError> {
198    match &args.socat_cmd {
199        arguments::socat_args::SocatCommands::Listen(args) => {
200            let manager = get_manager(services, args.guest_type).await?;
201            let endpoint = connect_to_vsock_endpoint(manager).await?;
202            handle_socat_listen(endpoint, args.host_port).await
203        }
204        arguments::socat_args::SocatCommands::Connect(args) => {
205            let manager = get_manager(services, args.guest_type).await?;
206            let endpoint = connect_to_vsock_endpoint(manager).await?;
207            handle_socat_connect(endpoint, args.guest_port).await
208        }
209    }
210}
211
212pub async fn handle_socat<P: PlatformServices>(
213    services: &P,
214    args: &arguments::socat_args::SocatArgs,
215) -> SocatResult {
216    match handle_impl(services, args).await {
217        Err(err) => SocatResult::SocatError(err),
218        Ok(ok) => SocatResult::SocatSuccess(ok),
219    }
220}
221
222#[cfg(test)]
223mod test {
224    use super::*;
225    use fidl::endpoints::create_proxy_and_stream;
226    use fuchsia_async as fasync;
227    use futures::future::join;
228    use futures::StreamExt;
229
230    #[fasync::run_until_stalled(test)]
231    async fn socat_listen_invalid_host_returns_err() {
232        let (proxy, mut stream) = create_proxy_and_stream::<HostVsockEndpointMarker>();
233        let server = async move {
234            let (port, _acceptor, responder) = stream
235                .next()
236                .await
237                .expect("Failed to read from stream")
238                .expect("Failed to parse request")
239                .into_listen()
240                .expect("Unexpected call to Guest Proxy");
241            assert_eq!(port, 0);
242            responder
243                .send(Err(zx_status::Status::CONNECTION_REFUSED.into_raw()))
244                .expect("Failed to send status code to client");
245        };
246
247        let client = handle_socat_listen(proxy, 0);
248        let (_server_res, client_res) = join(server, client).await;
249        assert_eq!(client_res.unwrap_err().to_string(), "Already a listener on port 0");
250    }
251
252    #[fasync::run_until_stalled(test)]
253    async fn socat_listen_mismatched_ports_returns_err() {
254        let (proxy, mut stream) = create_proxy_and_stream::<HostVsockEndpointMarker>();
255        let server = async move {
256            let (port, acceptor, responder) = stream
257                .next()
258                .await
259                .expect("Failed to read from stream")
260                .expect("Failed to parse request")
261                .into_listen()
262                .expect("Unexpected call to Guest Proxy");
263            assert_eq!(port, 0);
264            responder.send(Ok(())).expect("Failed to send status code to client");
265            let _ = acceptor.into_proxy().accept(0, 0, 1).await.expect("Failed to accept listener");
266        };
267
268        let client = handle_socat_listen(proxy, 0);
269        let (_server_res, client_res) = join(server, client).await;
270        assert_eq!(
271            client_res.unwrap_err().to_string(),
272            "Internal error: connection attempt on unexpected port"
273        );
274    }
275}