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