Skip to main content

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 guest_cli_args as arguments;
11use std::fmt;
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        let other = _socket.duplicate_handle(fidl::Rights::SAME_RIGHTS)?;
80        Ok((_socket, other))
81    }
82
83    // TODO(https://fxbug.dev/42068091): Remove when overnet supports duplicated socket handles.
84    #[cfg(not(target_os = "fuchsia"))]
85    unimplemented!()
86}
87
88async fn handle_socat_listen(
89    vsock_endpoint: HostVsockEndpointProxy,
90    host_port: u32,
91) -> Result<SocatSuccess, SocatError> {
92    let (vsock_accept_client, mut vsock_acceptor_stream) =
93        fidl::endpoints::create_request_stream::<HostVsockAcceptorMarker>();
94
95    vsock_endpoint
96        .listen(host_port, vsock_accept_client)
97        .await?
98        .map_err(|_| SocatError::FailedToListen(host_port))?;
99
100    // Try_next returns a Result<Option<T>, Err>, hence we need to unwrap our value
101    let connection = vsock_acceptor_stream
102        .try_next()
103        .await?
104        .ok_or_else(|| SocatError::InternalFailure("unexpected end of stream".to_string()))?;
105
106    let (_src_cid, _src_port, port, responder) = connection
107        .into_accept()
108        .ok_or_else(|| SocatError::InternalFailure("unexpected message on stream".to_string()))?;
109
110    if port != host_port {
111        responder.send(Err(zx_status::Status::CONNECTION_REFUSED.into_raw()))?;
112        return Err(SocatError::InternalFailure(
113            "connection attempt on unexpected port".to_string(),
114        ));
115    }
116
117    let (socket, remote_socket) = fidl::Socket::create_stream();
118    responder.send(Ok(remote_socket))?;
119
120    let (input, output) = duplicate_socket(socket)?;
121
122    let console = GuestConsole::new(input, output)
123        .map_err(|err| SocatError::InternalFailure(format!("failed to create console: {}", err)))?;
124    if let Err(err) = console.run_with_stdio().await {
125        Err(SocatError::InternalFailure(format!("failed to run console: {}", err)))
126    } else {
127        Ok(SocatSuccess::Listened(host_port))
128    }
129}
130
131async fn handle_socat_connect(
132    vsock_endpoint: HostVsockEndpointProxy,
133    port: u32,
134) -> Result<SocatSuccess, SocatError> {
135    let Ok(socket) = vsock_endpoint.connect(port).await? else {
136        return Err(SocatError::NoListener(port));
137    };
138
139    let (input, output) = duplicate_socket(socket)?;
140    let console = GuestConsole::new(input, output)
141        .map_err(|err| SocatError::InternalFailure(format!("failed to create console: {}", err)))?;
142
143    if let Err(err) = console.run_with_stdio().await {
144        Err(SocatError::InternalFailure(format!("failed to run console: {}", err)))
145    } else {
146        Ok(SocatSuccess::Connected)
147    }
148}
149
150async fn connect_to_vsock_endpoint(
151    manager: GuestManagerProxy,
152) -> Result<HostVsockEndpointProxy, SocatError> {
153    let (guest_endpoint, guest_server_end) = fidl::endpoints::create_proxy::<GuestMarker>();
154    manager
155        .connect(guest_server_end)
156        .await?
157        .map_err(|err| SocatError::InternalFailure(format!("failed to connect: {:?}", err)))?;
158
159    let (vsock_endpoint, vsock_server_end) =
160        fidl::endpoints::create_proxy::<HostVsockEndpointMarker>();
161
162    guest_endpoint
163        .get_host_vsock_endpoint(vsock_server_end)
164        .await?
165        .map_err(|_| SocatError::NoVsockDevice)?;
166
167    Ok(vsock_endpoint)
168}
169
170async fn get_manager<P: PlatformServices>(
171    services: &P,
172    guest_type: arguments::GuestType,
173) -> Result<GuestManagerProxy, SocatError> {
174    if guest_type == arguments::GuestType::Zircon {
175        // We don't have a Zircon vsock device.
176        return Err(SocatError::NoVsockDevice);
177    }
178
179    let guest_manager = services.connect_to_manager(guest_type).await.map_err(|err| {
180        SocatError::InternalFailure(format!("failed to connect to manager: {}", err))
181    })?;
182
183    let guest_info = guest_manager.get_info().await?;
184    let status = guest_info.guest_status.expect("guest status should always be set");
185    if status != GuestStatus::Starting && status != GuestStatus::Running {
186        return Err(SocatError::NotRunning);
187    }
188
189    Ok(guest_manager)
190}
191
192async fn handle_impl<P: PlatformServices>(
193    services: &P,
194    args: &arguments::socat_args::SocatArgs,
195) -> Result<SocatSuccess, SocatError> {
196    match &args.socat_cmd {
197        arguments::socat_args::SocatCommands::Listen(args) => {
198            let manager = get_manager(services, args.guest_type).await?;
199            let endpoint = connect_to_vsock_endpoint(manager).await?;
200            handle_socat_listen(endpoint, args.host_port).await
201        }
202        arguments::socat_args::SocatCommands::Connect(args) => {
203            let manager = get_manager(services, args.guest_type).await?;
204            let endpoint = connect_to_vsock_endpoint(manager).await?;
205            handle_socat_connect(endpoint, args.guest_port).await
206        }
207    }
208}
209
210pub async fn handle_socat<P: PlatformServices>(
211    services: &P,
212    args: &arguments::socat_args::SocatArgs,
213) -> SocatResult {
214    match handle_impl(services, args).await {
215        Err(err) => SocatResult::SocatError(err),
216        Ok(ok) => SocatResult::SocatSuccess(ok),
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223    use fidl::endpoints::create_proxy_and_stream;
224    use fuchsia_async as fasync;
225    use futures::StreamExt;
226    use futures::future::join;
227
228    #[fasync::run_until_stalled(test)]
229    async fn socat_listen_invalid_host_returns_err() {
230        let (proxy, mut stream) = create_proxy_and_stream::<HostVsockEndpointMarker>();
231        let server = async move {
232            let (port, _acceptor, responder) = stream
233                .next()
234                .await
235                .expect("Failed to read from stream")
236                .expect("Failed to parse request")
237                .into_listen()
238                .expect("Unexpected call to Guest Proxy");
239            assert_eq!(port, 0);
240            responder
241                .send(Err(zx_status::Status::CONNECTION_REFUSED.into_raw()))
242                .expect("Failed to send status code to client");
243        };
244
245        let client = handle_socat_listen(proxy, 0);
246        let (_server_res, client_res) = join(server, client).await;
247        assert_eq!(client_res.unwrap_err().to_string(), "Already a listener on port 0");
248    }
249
250    #[fasync::run_until_stalled(test)]
251    async fn socat_listen_mismatched_ports_returns_err() {
252        let (proxy, mut stream) = create_proxy_and_stream::<HostVsockEndpointMarker>();
253        let server = async move {
254            let (port, acceptor, responder) = stream
255                .next()
256                .await
257                .expect("Failed to read from stream")
258                .expect("Failed to parse request")
259                .into_listen()
260                .expect("Unexpected call to Guest Proxy");
261            assert_eq!(port, 0);
262            responder.send(Ok(())).expect("Failed to send status code to client");
263            let _ = acceptor.into_proxy().accept(0, 0, 1).await.expect("Failed to accept listener");
264        };
265
266        let client = handle_socat_listen(proxy, 0);
267        let (_server_res, client_res) = join(server, client).await;
268        assert_eq!(
269            client_res.unwrap_err().to_string(),
270            "Internal error: connection attempt on unexpected port"
271        );
272    }
273}