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