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