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