1use 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 #[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 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 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}