xref: /cloud-hypervisor/vmm/src/api/http/mod.rs (revision d580ed55c6b0785aecae9de400f3dab484bb4bd6)
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.
78 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.
107     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 
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 
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 
291 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 
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 
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 
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 
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