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