1 // Copyright © 2019 Intel Corporation
2 //
3 // SPDX-License-Identifier: Apache-2.0
4 //
5
6 use std::collections::BTreeMap;
7 use std::error::Error;
8 use std::fs::File;
9 use std::os::unix::io::{IntoRawFd, RawFd};
10 use std::os::unix::net::UnixListener;
11 use std::panic::AssertUnwindSafe;
12 use std::path::PathBuf;
13 use std::sync::mpsc::Sender;
14 use std::sync::LazyLock;
15 use std::thread;
16
17 use hypervisor::HypervisorType;
18 use micro_http::{
19 Body, HttpServer, MediaType, Method, Request, Response, ServerError, StatusCode, Version,
20 };
21 use seccompiler::{apply_filter, SeccompAction};
22 use serde_json::Error as SerdeError;
23 use thiserror::Error;
24 use vmm_sys_util::eventfd::EventFd;
25
26 use self::http_endpoint::{VmActionHandler, VmCreate, VmInfo, VmmPing, VmmShutdown};
27 #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
28 use crate::api::VmCoredump;
29 use crate::api::{
30 AddDisk, ApiError, ApiRequest, VmAddDevice, VmAddFs, VmAddNet, VmAddPmem, VmAddUserDevice,
31 VmAddVdpa, VmAddVsock, VmBoot, VmCounters, VmDelete, VmNmi, VmPause, VmPowerButton, VmReboot,
32 VmReceiveMigration, VmRemoveDevice, VmResize, VmResizeZone, VmRestore, VmResume,
33 VmSendMigration, VmShutdown, VmSnapshot,
34 };
35 use crate::landlock::Landlock;
36 use crate::seccomp_filters::{get_seccomp_filter, Thread};
37 use crate::{Error as VmmError, Result};
38
39 pub mod http_endpoint;
40
41 pub type HttpApiHandle = (thread::JoinHandle<Result<()>>, EventFd);
42
43 /// Errors associated with VMM management
44 #[derive(Error, Debug)]
45 pub enum HttpError {
46 /// API request receive error
47 #[error("Failed to deserialize JSON")]
48 SerdeJsonDeserialize(#[from] SerdeError),
49
50 /// Attempt to access unsupported HTTP method
51 #[error("Bad Request")]
52 BadRequest,
53
54 /// Undefined endpoints
55 #[error("Not Found")]
56 NotFound,
57
58 /// Too many requests
59 #[error("Too Many Requests")]
60 TooManyRequests,
61
62 /// Internal Server Error
63 #[error("Internal Server Error")]
64 InternalServerError,
65
66 /// Error from internal API
67 #[error("Error from API")]
68 ApiError(#[source] ApiError),
69 }
70
71 const HTTP_ROOT: &str = "/api/v1";
72
73 /// Creates the error response's JSON body meant to be sent back to an API client.
74 ///
75 /// The error message contained in the response is supposed to be user-facing,
76 /// thus insightful and helpful while balancing technical accuracy and
77 /// simplicity.
error_response(error: HttpError, status: StatusCode) -> Response78 pub fn error_response(error: HttpError, status: StatusCode) -> Response {
79 let mut response = Response::new(Version::Http11, status);
80
81 let error: &dyn Error = &error;
82 // Write the Display::display() output all errors (from top to root).
83 let error_messages = std::iter::successors(Some(error), |sub_error| {
84 // Dereference necessary to mitigate rustc compiler bug.
85 // See <https://github.com/rust-lang/rust/issues/141673>
86 (*sub_error).source()
87 })
88 .map(|error| format!("{error}"))
89 .collect::<Vec<_>>();
90
91 // TODO: Move `api` module from `vmm` to dedicated crate and use a common type definition
92 let json = serde_json::to_string(&error_messages).unwrap();
93
94 let body = Body::new(json);
95 response.set_body(body);
96
97 response
98 }
99
100 /// An HTTP endpoint handler interface
101 pub trait EndpointHandler {
102 /// Handles an HTTP request.
103 /// After parsing the request, the handler could decide to send an
104 /// associated API request down to the VMM API server to e.g. create
105 /// or start a VM. The request will block waiting for an answer from the
106 /// API server and translate that into an HTTP response.
handle_request( &self, req: &Request, api_notifier: EventFd, api_sender: Sender<ApiRequest>, ) -> Response107 fn handle_request(
108 &self,
109 req: &Request,
110 api_notifier: EventFd,
111 api_sender: Sender<ApiRequest>,
112 ) -> Response {
113 // Cloning the files here is very important as it dup() the file
114 // descriptors, leaving open the one that was received. This way,
115 // rebooting the VM will work since the VM will be created from the
116 // original file descriptors.
117 let files = req.files.iter().map(|f| f.try_clone().unwrap()).collect();
118 let res = match req.method() {
119 Method::Put => self.put_handler(api_notifier, api_sender, &req.body, files),
120 Method::Get => self.get_handler(api_notifier, api_sender, &req.body),
121 _ => return Response::new(Version::Http11, StatusCode::BadRequest),
122 };
123
124 match res {
125 Ok(response_body) => {
126 if let Some(body) = response_body {
127 let mut response = Response::new(Version::Http11, StatusCode::OK);
128 response.set_body(body);
129 response
130 } else {
131 Response::new(Version::Http11, StatusCode::NoContent)
132 }
133 }
134 Err(e @ HttpError::BadRequest) => error_response(e, StatusCode::BadRequest),
135 Err(e @ HttpError::SerdeJsonDeserialize(_)) => {
136 error_response(e, StatusCode::BadRequest)
137 }
138 Err(e @ HttpError::TooManyRequests) => error_response(e, StatusCode::TooManyRequests),
139 Err(e) => error_response(e, StatusCode::InternalServerError),
140 }
141 }
142
put_handler( &self, _api_notifier: EventFd, _api_sender: Sender<ApiRequest>, _body: &Option<Body>, _files: Vec<File>, ) -> std::result::Result<Option<Body>, HttpError>143 fn put_handler(
144 &self,
145 _api_notifier: EventFd,
146 _api_sender: Sender<ApiRequest>,
147 _body: &Option<Body>,
148 _files: Vec<File>,
149 ) -> std::result::Result<Option<Body>, HttpError> {
150 Err(HttpError::BadRequest)
151 }
152
get_handler( &self, _api_notifier: EventFd, _api_sender: Sender<ApiRequest>, _body: &Option<Body>, ) -> std::result::Result<Option<Body>, HttpError>153 fn get_handler(
154 &self,
155 _api_notifier: EventFd,
156 _api_sender: Sender<ApiRequest>,
157 _body: &Option<Body>,
158 ) -> std::result::Result<Option<Body>, HttpError> {
159 Err(HttpError::BadRequest)
160 }
161 }
162
163 /// An HTTP routes structure.
164 pub struct HttpRoutes {
165 /// routes is a hash table mapping endpoint URIs to their endpoint handlers.
166 pub routes: BTreeMap<String, Box<dyn EndpointHandler + Sync + Send>>,
167 }
168
169 macro_rules! endpoint {
170 ($path:expr) => {
171 format!("{}{}", HTTP_ROOT, $path)
172 };
173 }
174
175 /// HTTP_ROUTES contain all the cloud-hypervisor HTTP routes.
176 pub static HTTP_ROUTES: LazyLock<HttpRoutes> = LazyLock::new(|| {
177 let mut r = HttpRoutes {
178 routes: BTreeMap::new(),
179 };
180
181 r.routes.insert(
182 endpoint!("/vm.add-device"),
183 Box::new(VmActionHandler::new(&VmAddDevice)),
184 );
185 r.routes.insert(
186 endpoint!("/vm.add-user-device"),
187 Box::new(VmActionHandler::new(&VmAddUserDevice)),
188 );
189 r.routes.insert(
190 endpoint!("/vm.add-disk"),
191 Box::new(VmActionHandler::new(&AddDisk)),
192 );
193 r.routes.insert(
194 endpoint!("/vm.add-fs"),
195 Box::new(VmActionHandler::new(&VmAddFs)),
196 );
197 r.routes.insert(
198 endpoint!("/vm.add-net"),
199 Box::new(VmActionHandler::new(&VmAddNet)),
200 );
201 r.routes.insert(
202 endpoint!("/vm.add-pmem"),
203 Box::new(VmActionHandler::new(&VmAddPmem)),
204 );
205 r.routes.insert(
206 endpoint!("/vm.add-vdpa"),
207 Box::new(VmActionHandler::new(&VmAddVdpa)),
208 );
209 r.routes.insert(
210 endpoint!("/vm.add-vsock"),
211 Box::new(VmActionHandler::new(&VmAddVsock)),
212 );
213 r.routes.insert(
214 endpoint!("/vm.boot"),
215 Box::new(VmActionHandler::new(&VmBoot)),
216 );
217 r.routes.insert(
218 endpoint!("/vm.counters"),
219 Box::new(VmActionHandler::new(&VmCounters)),
220 );
221 r.routes
222 .insert(endpoint!("/vm.create"), Box::new(VmCreate {}));
223 r.routes.insert(
224 endpoint!("/vm.delete"),
225 Box::new(VmActionHandler::new(&VmDelete)),
226 );
227 r.routes.insert(endpoint!("/vm.info"), Box::new(VmInfo {}));
228 r.routes.insert(
229 endpoint!("/vm.pause"),
230 Box::new(VmActionHandler::new(&VmPause)),
231 );
232 r.routes.insert(
233 endpoint!("/vm.power-button"),
234 Box::new(VmActionHandler::new(&VmPowerButton)),
235 );
236 r.routes.insert(
237 endpoint!("/vm.reboot"),
238 Box::new(VmActionHandler::new(&VmReboot)),
239 );
240 r.routes.insert(
241 endpoint!("/vm.receive-migration"),
242 Box::new(VmActionHandler::new(&VmReceiveMigration)),
243 );
244 r.routes.insert(
245 endpoint!("/vm.remove-device"),
246 Box::new(VmActionHandler::new(&VmRemoveDevice)),
247 );
248 r.routes.insert(
249 endpoint!("/vm.resize"),
250 Box::new(VmActionHandler::new(&VmResize)),
251 );
252 r.routes.insert(
253 endpoint!("/vm.resize-zone"),
254 Box::new(VmActionHandler::new(&VmResizeZone)),
255 );
256 r.routes.insert(
257 endpoint!("/vm.restore"),
258 Box::new(VmActionHandler::new(&VmRestore)),
259 );
260 r.routes.insert(
261 endpoint!("/vm.resume"),
262 Box::new(VmActionHandler::new(&VmResume)),
263 );
264 r.routes.insert(
265 endpoint!("/vm.send-migration"),
266 Box::new(VmActionHandler::new(&VmSendMigration)),
267 );
268 r.routes.insert(
269 endpoint!("/vm.shutdown"),
270 Box::new(VmActionHandler::new(&VmShutdown)),
271 );
272 r.routes.insert(
273 endpoint!("/vm.snapshot"),
274 Box::new(VmActionHandler::new(&VmSnapshot)),
275 );
276 #[cfg(all(target_arch = "x86_64", feature = "guest_debug"))]
277 r.routes.insert(
278 endpoint!("/vm.coredump"),
279 Box::new(VmActionHandler::new(&VmCoredump)),
280 );
281 r.routes
282 .insert(endpoint!("/vmm.ping"), Box::new(VmmPing {}));
283 r.routes
284 .insert(endpoint!("/vmm.shutdown"), Box::new(VmmShutdown {}));
285 r.routes
286 .insert(endpoint!("/vm.nmi"), Box::new(VmActionHandler::new(&VmNmi)));
287
288 r
289 });
290
handle_http_request( request: &Request, api_notifier: &EventFd, api_sender: &Sender<ApiRequest>, ) -> Response291 fn handle_http_request(
292 request: &Request,
293 api_notifier: &EventFd,
294 api_sender: &Sender<ApiRequest>,
295 ) -> Response {
296 let path = request.uri().get_abs_path().to_string();
297 let mut response = match HTTP_ROUTES.routes.get(&path) {
298 Some(route) => match api_notifier.try_clone() {
299 Ok(notifier) => route.handle_request(request, notifier, api_sender.clone()),
300 Err(_) => error_response(
301 HttpError::InternalServerError,
302 StatusCode::InternalServerError,
303 ),
304 },
305 None => error_response(HttpError::NotFound, StatusCode::NotFound),
306 };
307
308 response.set_server("Cloud Hypervisor API");
309 response.set_content_type(MediaType::ApplicationJson);
310 response
311 }
312
start_http_thread( mut server: HttpServer, api_notifier: EventFd, api_sender: Sender<ApiRequest>, seccomp_action: &SeccompAction, exit_evt: EventFd, hypervisor_type: HypervisorType, landlock_enable: bool, ) -> Result<HttpApiHandle>313 fn start_http_thread(
314 mut server: HttpServer,
315 api_notifier: EventFd,
316 api_sender: Sender<ApiRequest>,
317 seccomp_action: &SeccompAction,
318 exit_evt: EventFd,
319 hypervisor_type: HypervisorType,
320 landlock_enable: bool,
321 ) -> Result<HttpApiHandle> {
322 // Retrieve seccomp filter for API thread
323 let api_seccomp_filter = get_seccomp_filter(seccomp_action, Thread::HttpApi, hypervisor_type)
324 .map_err(VmmError::CreateSeccompFilter)?;
325
326 let api_shutdown_fd = EventFd::new(libc::EFD_NONBLOCK).map_err(VmmError::EventFdCreate)?;
327 let api_shutdown_fd_clone = api_shutdown_fd.try_clone().unwrap();
328
329 server
330 .add_kill_switch(api_shutdown_fd_clone)
331 .map_err(VmmError::CreateApiServer)?;
332
333 let thread = thread::Builder::new()
334 .name("http-server".to_string())
335 .spawn(move || {
336 // Apply seccomp filter for API thread.
337 if !api_seccomp_filter.is_empty() {
338 apply_filter(&api_seccomp_filter)
339 .map_err(VmmError::ApplySeccompFilter)
340 .map_err(|e| {
341 error!("Error applying seccomp filter: {:?}", e);
342 exit_evt.write(1).ok();
343 e
344 })?;
345 }
346
347 if landlock_enable {
348 Landlock::new()
349 .map_err(VmmError::CreateLandlock)?
350 .restrict_self()
351 .map_err(VmmError::ApplyLandlock)
352 .map_err(|e| {
353 error!("Error applying landlock to http-server thread: {:?}", e);
354 exit_evt.write(1).ok();
355 e
356 })?;
357 }
358
359 std::panic::catch_unwind(AssertUnwindSafe(move || {
360 server.start_server().unwrap();
361 loop {
362 match server.requests() {
363 Ok(request_vec) => {
364 for server_request in request_vec {
365 if let Err(e) = server.respond(server_request.process(|request| {
366 handle_http_request(request, &api_notifier, &api_sender)
367 })) {
368 error!("HTTP server error on response: {}", e);
369 }
370 }
371 }
372 Err(ServerError::ShutdownEvent) => {
373 server.flush_outgoing_writes();
374 return;
375 }
376 Err(e) => {
377 error!(
378 "HTTP server error on retrieving incoming request. Error: {}",
379 e
380 );
381 }
382 }
383 }
384 }))
385 .map_err(|_| {
386 error!("http-server thread panicked");
387 exit_evt.write(1).ok()
388 })
389 .ok();
390
391 Ok(())
392 })
393 .map_err(VmmError::HttpThreadSpawn)?;
394
395 Ok((thread, api_shutdown_fd))
396 }
397
start_http_path_thread( path: &str, api_notifier: EventFd, api_sender: Sender<ApiRequest>, seccomp_action: &SeccompAction, exit_evt: EventFd, hypervisor_type: HypervisorType, landlock_enable: bool, ) -> Result<HttpApiHandle>398 pub fn start_http_path_thread(
399 path: &str,
400 api_notifier: EventFd,
401 api_sender: Sender<ApiRequest>,
402 seccomp_action: &SeccompAction,
403 exit_evt: EventFd,
404 hypervisor_type: HypervisorType,
405 landlock_enable: bool,
406 ) -> Result<HttpApiHandle> {
407 let socket_path = PathBuf::from(path);
408 let socket_fd = UnixListener::bind(socket_path).map_err(VmmError::CreateApiServerSocket)?;
409 // SAFETY: Valid FD just opened
410 let server = unsafe { HttpServer::new_from_fd(socket_fd.into_raw_fd()) }
411 .map_err(VmmError::CreateApiServer)?;
412
413 start_http_thread(
414 server,
415 api_notifier,
416 api_sender,
417 seccomp_action,
418 exit_evt,
419 hypervisor_type,
420 landlock_enable,
421 )
422 }
423
start_http_fd_thread( fd: RawFd, api_notifier: EventFd, api_sender: Sender<ApiRequest>, seccomp_action: &SeccompAction, exit_evt: EventFd, hypervisor_type: HypervisorType, landlock_enable: bool, ) -> Result<HttpApiHandle>424 pub fn start_http_fd_thread(
425 fd: RawFd,
426 api_notifier: EventFd,
427 api_sender: Sender<ApiRequest>,
428 seccomp_action: &SeccompAction,
429 exit_evt: EventFd,
430 hypervisor_type: HypervisorType,
431 landlock_enable: bool,
432 ) -> Result<HttpApiHandle> {
433 // SAFETY: Valid FD
434 let server = unsafe { HttpServer::new_from_fd(fd) }.map_err(VmmError::CreateApiServer)?;
435 start_http_thread(
436 server,
437 api_notifier,
438 api_sender,
439 seccomp_action,
440 exit_evt,
441 hypervisor_type,
442 landlock_enable,
443 )
444 }
445
http_api_graceful_shutdown(http_handle: HttpApiHandle) -> Result<()>446 pub fn http_api_graceful_shutdown(http_handle: HttpApiHandle) -> Result<()> {
447 let (api_thread, api_shutdown_fd) = http_handle;
448
449 api_shutdown_fd.write(1).unwrap();
450 api_thread.join().map_err(VmmError::ThreadCleanup)?
451 }
452