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