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