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