1#![forbid(unsafe_code)]
7
8pub use hyperv_ic_protocol::shutdown::INSTANCE_ID;
9pub use hyperv_ic_protocol::shutdown::INTERFACE_ID;
10
11#[cfg(test)]
12#[path = "shutdown_tests.rs"]
13mod tests;
14
15use guid::Guid;
16use hyperv_ic_protocol::FRAMEWORK_VERSION_1;
17use hyperv_ic_protocol::FRAMEWORK_VERSION_3;
18use hyperv_ic_protocol::Status;
19use hyperv_ic_protocol::shutdown::SHUTDOWN_VERSION_1;
20use hyperv_ic_protocol::shutdown::SHUTDOWN_VERSION_3;
21use hyperv_ic_protocol::shutdown::SHUTDOWN_VERSION_3_1;
22use hyperv_ic_protocol::shutdown::SHUTDOWN_VERSION_3_2;
23use hyperv_ic_resources::shutdown::ShutdownParams;
24use hyperv_ic_resources::shutdown::ShutdownResult;
25use hyperv_ic_resources::shutdown::ShutdownType;
26use inspect::Inspect;
27use inspect::InspectMut;
28use mesh::rpc::Rpc;
29use mesh::rpc::RpcSend;
30use std::io::IoSlice;
31use std::mem::size_of_val;
32use task_control::Cancelled;
33use task_control::StopTask;
34use thiserror::Error;
35use vmbus_async::async_dgram::AsyncRecvExt;
36use vmbus_async::async_dgram::AsyncSendExt;
37use vmbus_async::pipe::MessagePipe;
38use vmbus_channel::RawAsyncChannel;
39use vmbus_channel::channel::ChannelOpenError;
40use vmbus_relay_intercept_device::OfferResponse;
41use vmbus_relay_intercept_device::SaveRestoreSimpleVmbusClientDevice;
42use vmbus_relay_intercept_device::SimpleVmbusClientDevice;
43use vmbus_relay_intercept_device::SimpleVmbusClientDeviceAsync;
44use vmbus_relay_intercept_device::ring_buffer::MemoryBlockRingBuffer;
45use vmbus_ring::RingMem;
46use vmcore::save_restore::NoSavedState;
47use zerocopy::FromBytes;
48use zerocopy::FromZeros;
49use zerocopy::IntoBytes;
50
51#[derive(InspectMut)]
53pub struct ShutdownGuestIc {
54 #[inspect(skip)]
55 send_shutdown_notification: mesh::Sender<Rpc<ShutdownParams, ShutdownResult>>,
56 #[inspect(skip)]
57 recv_shutdown_notification: Option<mesh::Receiver<Rpc<ShutdownParams, ShutdownResult>>>,
58}
59
60#[derive(Debug, Inspect)]
61#[inspect(tag = "channel_state")]
62enum ShutdownGuestChannelState {
63 NegotiateVersion,
64 Running {
65 #[inspect(display)]
66 framework_version: hyperv_ic_protocol::Version,
67 #[inspect(display)]
68 message_version: hyperv_ic_protocol::Version,
69 },
70}
71
72#[derive(InspectMut)]
74pub struct ShutdownGuestChannel<M: RingMem = MemoryBlockRingBuffer> {
75 state: ShutdownGuestChannelState,
77 #[inspect(mut)]
79 pipe: MessagePipe<M>,
80}
81
82#[derive(Debug, Error)]
83enum Error {
84 #[error("cancelled")]
85 Cancelled(Cancelled),
86 #[error("ring buffer error")]
87 Ring(#[source] std::io::Error),
88 #[error("truncated message")]
89 TruncatedMessage,
90}
91
92impl From<Cancelled> for Error {
93 fn from(value: Cancelled) -> Self {
94 Self::Cancelled(value)
95 }
96}
97
98impl ShutdownGuestIc {
99 pub fn new() -> Self {
101 let (send_shutdown_notification, recv_shutdown_notification) = mesh::channel();
102 Self {
103 send_shutdown_notification,
104 recv_shutdown_notification: Some(recv_shutdown_notification),
105 }
106 }
107
108 pub fn get_shutdown_notifier(&mut self) -> mesh::Receiver<Rpc<ShutdownParams, ShutdownResult>> {
110 self.recv_shutdown_notification
111 .take()
112 .expect("can only be called once")
113 }
114}
115
116impl<M: RingMem + Unpin> ShutdownGuestChannel<M> {
117 fn new(pipe: MessagePipe<M>) -> Self {
118 Self {
119 state: ShutdownGuestChannelState::NegotiateVersion,
120 pipe,
121 }
122 }
123
124 async fn process(
125 &mut self,
126 ic: &mut ShutdownGuestIc,
127 check: &task_control::StopCheck<'_>,
128 ) -> Result<(), Error> {
129 loop {
130 check.check()?;
131 match read_from_pipe(&mut self.pipe).await {
132 Ok(buf) => {
133 self.handle_host_message(&buf, ic).await;
134 }
135 Err(err) => {
136 tracelimit::error_ratelimited!(
137 err = &err as &dyn std::error::Error,
138 "reading shutdown packet from host",
139 );
140 }
141 }
142 }
143 }
144
145 async fn handle_host_message(&mut self, buf: &[u8], ic: &ShutdownGuestIc) {
146 let (header, rest) = match hyperv_ic_protocol::Header::read_from_prefix(buf).ok() {
148 Some((h, r)) => (h, r),
149 None => {
150 tracelimit::error_ratelimited!("invalid shutdown packet from host",);
151 return;
152 }
153 };
154 match header.message_type {
155 hyperv_ic_protocol::MessageType::VERSION_NEGOTIATION => {
156 self.state = ShutdownGuestChannelState::NegotiateVersion;
160 if let Err(err) = self.handle_version_negotiation(&header, rest).await {
161 tracelimit::error_ratelimited!(
162 err = &err as &dyn std::error::Error,
163 "Failed version negotiation"
164 );
165 }
166 }
167 hyperv_ic_protocol::MessageType::SHUTDOWN
168 if matches!(self.state, ShutdownGuestChannelState::Running { .. }) =>
169 {
170 if let Err(err) = self.handle_shutdown_notification(&header, rest, ic).await {
171 tracelimit::error_ratelimited!(
172 err = &err as &dyn std::error::Error,
173 "Failed processing shutdown message"
174 );
175 }
176 }
177 _ => {
178 tracelimit::error_ratelimited!(r#type = ?header.message_type, state = ?self.state, "Unrecognized packet");
179 }
180 }
181 }
182
183 fn find_latest_supported_version<'a>(
184 buf: &'a [u8],
185 count: usize,
186 supported: &[hyperv_ic_protocol::Version],
187 ) -> (Option<hyperv_ic_protocol::Version>, &'a [u8]) {
188 let mut rest = buf;
189 let mut next_version;
190 let mut latest_version = None;
191 for _ in 0..count {
192 (next_version, rest) = match hyperv_ic_protocol::Version::read_from_prefix(rest).ok() {
194 Some((n, r)) => (n, r),
195 None => {
196 tracelimit::error_ratelimited!("truncated message version list");
197 return (latest_version, rest);
198 }
199 };
200 for known in supported {
201 if known.major == next_version.major && known.minor == next_version.minor {
202 if latest_version.is_some() {
203 if next_version.major >= latest_version.unwrap().major {
204 if next_version.major > latest_version.unwrap().major
205 || next_version.minor > latest_version.unwrap().minor
206 {
207 latest_version = Some(next_version);
208 }
209 }
210 } else {
211 latest_version = Some(next_version);
212 }
213 }
214 }
215 }
216 (latest_version, rest)
217 }
218
219 async fn handle_version_negotiation(
220 &mut self,
221 header: &hyperv_ic_protocol::Header,
222 msg: &[u8],
223 ) -> Result<(), Error> {
224 const FRAMEWORK_VERSIONS: &[hyperv_ic_protocol::Version] =
225 &[FRAMEWORK_VERSION_1, FRAMEWORK_VERSION_3];
226
227 const SHUTDOWN_VERSIONS: &[hyperv_ic_protocol::Version] = &[
228 SHUTDOWN_VERSION_1,
229 SHUTDOWN_VERSION_3,
230 SHUTDOWN_VERSION_3_1,
231 SHUTDOWN_VERSION_3_2,
232 ];
233
234 let (prefix, rest) = hyperv_ic_protocol::NegotiateMessage::read_from_prefix(msg)
235 .map_err(|_| Error::TruncatedMessage)?; let (latest_framework_version, rest) = Self::find_latest_supported_version(
237 rest,
238 prefix.framework_version_count as usize,
239 FRAMEWORK_VERSIONS,
240 );
241 let framework_version = if let Some(version) = latest_framework_version {
242 version
243 } else {
244 tracelimit::error_ratelimited!("Unsupported framework version");
245 FRAMEWORK_VERSIONS[FRAMEWORK_VERSIONS.len() - 1]
246 };
247 let (latest_message_version, _) = Self::find_latest_supported_version(
248 rest,
249 prefix.message_version_count as usize,
250 SHUTDOWN_VERSIONS,
251 );
252 let message_version = if let Some(version) = latest_message_version {
253 version
254 } else {
255 tracelimit::error_ratelimited!("Unsupported message version");
256 SHUTDOWN_VERSIONS[SHUTDOWN_VERSIONS.len() - 1]
257 };
258
259 let message = hyperv_ic_protocol::NegotiateMessage {
260 framework_version_count: 1,
261 message_version_count: 1,
262 ..FromZeros::new_zeroed()
263 };
264 let response = hyperv_ic_protocol::Header {
265 message_type: hyperv_ic_protocol::MessageType::VERSION_NEGOTIATION,
266 message_size: (size_of_val(&message)
267 + size_of_val(&framework_version)
268 + size_of_val(&message_version)) as u16,
269 status: Status::SUCCESS,
270 transaction_id: header.transaction_id,
271 flags: hyperv_ic_protocol::HeaderFlags::new()
272 .with_transaction(header.flags.transaction())
273 .with_response(true),
274 ..FromZeros::new_zeroed()
275 };
276 self.pipe
277 .send_vectored(&[
278 IoSlice::new(response.as_bytes()),
279 IoSlice::new(message.as_bytes()),
280 IoSlice::new(framework_version.as_bytes()),
281 IoSlice::new(message_version.as_bytes()),
282 ])
283 .await
284 .map_err(Error::Ring)?;
285
286 tracing::info!(%framework_version, %message_version, "version negotiated");
287 self.state = ShutdownGuestChannelState::Running {
288 framework_version,
289 message_version,
290 };
291 Ok(())
292 }
293
294 async fn handle_shutdown_notification(
295 &mut self,
296 header: &hyperv_ic_protocol::Header,
297 buf: &[u8],
298 ic: &ShutdownGuestIc,
299 ) -> Result<(), Error> {
300 let ShutdownGuestChannelState::Running {
301 framework_version,
302 message_version,
303 } = &self.state
304 else {
305 panic!("Shutdown message processing while in invalid state");
306 };
307
308 let message = hyperv_ic_protocol::shutdown::ShutdownMessage::read_from_prefix(buf)
309 .map_err(|_| Error::TruncatedMessage)?
310 .0; let shutdown_type = if message.flags.restart() {
312 ShutdownType::Reboot
313 } else if message.flags.hibernate() {
314 ShutdownType::Hibernate
315 } else {
316 ShutdownType::PowerOff
317 };
318 let params = ShutdownParams {
319 shutdown_type,
320 force: message.flags.force(),
321 };
322
323 let status = match ic.send_shutdown_notification.call(|x| x, params).await {
325 Ok(ShutdownResult::Ok) => Status::SUCCESS,
326 Ok(ShutdownResult::Failed(x)) => Status(x),
327 Ok(ShutdownResult::NotReady) | Ok(ShutdownResult::AlreadyInProgress) | Err(_) => {
328 Status::FAIL
329 }
330 };
331
332 let response = hyperv_ic_protocol::Header {
334 framework_version: *framework_version,
335 message_version: *message_version,
336 message_type: hyperv_ic_protocol::MessageType::SHUTDOWN,
337 message_size: 0,
338 status,
339 transaction_id: header.transaction_id,
340 flags: hyperv_ic_protocol::HeaderFlags::new()
341 .with_transaction(header.flags.transaction())
342 .with_response(true),
343 ..FromZeros::new_zeroed()
344 };
345 self.pipe
346 .send(response.as_bytes())
347 .await
348 .map_err(Error::Ring)
349 }
350}
351
352async fn read_from_pipe<T: RingMem>(pipe: &mut MessagePipe<T>) -> Result<Vec<u8>, Error> {
353 let mut buf = vec![0; hyperv_ic_protocol::MAX_MESSAGE_SIZE];
354 let n = pipe.recv(&mut buf).await.map_err(Error::Ring)?;
355 let buf = &buf[..n];
356 Ok(buf.to_vec())
357}
358
359impl SimpleVmbusClientDevice for ShutdownGuestIc {
360 type SavedState = NoSavedState;
361 type Runner = ShutdownGuestChannel;
362
363 fn instance_id(&self) -> Guid {
364 INSTANCE_ID
365 }
366
367 fn offer(&self, _offer: &vmbus_core::protocol::OfferChannel) -> OfferResponse {
368 OfferResponse::Open
369 }
370
371 fn inspect(&mut self, req: inspect::Request<'_>, runner: Option<&mut Self::Runner>) {
372 req.respond().merge(self).merge(runner);
373 }
374
375 fn open(
376 &mut self,
377 _channel_idx: u16,
378 channel: RawAsyncChannel<MemoryBlockRingBuffer>,
379 ) -> Result<Self::Runner, ChannelOpenError> {
380 let pipe = MessagePipe::new(channel)?;
381 Ok(ShutdownGuestChannel::new(pipe))
382 }
383
384 fn close(&mut self, _channel_idx: u16) {}
385
386 fn supports_save_restore(
387 &mut self,
388 ) -> Option<
389 &mut dyn SaveRestoreSimpleVmbusClientDevice<
390 SavedState = Self::SavedState,
391 Runner = Self::Runner,
392 >,
393 > {
394 None
395 }
396}
397
398impl SimpleVmbusClientDeviceAsync for ShutdownGuestIc {
399 async fn run(
400 &mut self,
401 stop: &mut StopTask<'_>,
402 runner: &mut Self::Runner,
403 ) -> Result<(), Cancelled> {
404 let check = stop.check_handle()?;
405 stop.until_stopped(async {
406 match runner.process(self, &check).await {
407 Ok(()) => {}
408 Err(Error::Cancelled(cancelled)) => return Err(cancelled),
409 Err(err) => {
410 tracing::error!(
411 error = &err as &dyn std::error::Error,
412 "shutdown ic relay error"
413 )
414 }
415 }
416 Ok(())
417 })
418 .await?
419 }
420}