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