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