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