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