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