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