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