clock_bound_vmclock/shm.rs
1//! VMClock Shared Memory
2//!
3//! This crate implements the low-level implementation to share VMClock data
4//! over a shared memory segment. This crate is meant to be used by the C and Rust versions
5//! of the ClockBound client library.
6
7// TODO: prevent clippy from checking for dead code. The writer module is only re-exported publicly
8// if the write feature is selected. There may be a better way to do that and re-enable the lint.
9#![allow(dead_code)]
10
11use std::mem::size_of;
12use std::str::FromStr;
13use std::sync::atomic;
14
15use clock_bound_shm::{syserror, ShmError};
16use tracing::{debug, error};
17
18pub const VMCLOCK_SHM_DEFAULT_PATH: &str = "/dev/vmclock0";
19
20/// The magic number that identifies a VMClock shared memory segment.
21pub const VMCLOCK_SHM_MAGIC: u32 = 0x4B4C4356;
22
23/// Header structure to the Shared Memory segment where the VMClock data is kept.
24///
25/// Most members are atomic types as they are subject to be updated by the Hypervisor.
26#[repr(C)]
27#[derive(Debug)]
28pub struct VMClockShmHeader {
29 /// Magic number to uniquely identify the content of the memory segment.
30 pub magic: atomic::AtomicU32,
31
32 /// Size of the memory segment.
33 pub size: atomic::AtomicU32,
34
35 /// Version identifying the layout of data written to the shared memory segment.
36 pub version: atomic::AtomicU16,
37
38 /// Counter ID.
39 pub counter_id: atomic::AtomicU8,
40
41 /// Time type.
42 ///
43 /// Possible values are:
44 ///
45 /// VMCLOCK_TIME_UTC 0 // Since 1970-01-01 00:00:00z
46 /// VMCLOCK_TIME_TAI 1 // Since 1970-01-01 00:00:00z
47 /// VMCLOCK_TIME_MONOTONIC 2 // Since undefined epoch
48 /// VMCLOCK_TIME_INVALID_SMEARED 3 // Not supported
49 /// VMCLOCK_TIME_INVALID_MAYBE_SMEARED 4 // Not supported
50 ///
51 pub time_type: atomic::AtomicU8,
52
53 // Sequence count that is a generation number incremented by the
54 // VMClock on every update of the shared memory segment.
55 //
56 // Odd numbers mean that an update is in progress.
57 pub seq_count: atomic::AtomicU32,
58}
59
60impl VMClockShmHeader {
61 /// Initialize a VMClockShmHeader from a vector of bytes.
62 ///
63 /// It is assumed that the vecxtor has already been validated to have enough bytes to hold.
64 pub fn read(vector: &Vec<u8>) -> Result<Self, ShmError> {
65 if vector.len() < size_of::<VMClockShmHeader>() {
66 return syserror!("Insufficient bytes to create a VMClockShmHeader.");
67 }
68
69 let slice = vector.as_slice();
70
71 let magic = u32::from_le_bytes(slice[0..4].try_into().unwrap());
72 let size = u32::from_le_bytes(slice[4..8].try_into().unwrap());
73 let version = u16::from_le_bytes(slice[8..10].try_into().unwrap());
74 let counter_id = u8::from_le_bytes(slice[10..11].try_into().unwrap());
75 let time_type = u8::from_le_bytes(slice[11..12].try_into().unwrap());
76 let seq_count = u32::from_le_bytes(slice[12..16].try_into().unwrap());
77
78 let header = VMClockShmHeader {
79 magic: atomic::AtomicU32::new(magic),
80 size: atomic::AtomicU32::new(size),
81 version: atomic::AtomicU16::new(version),
82 counter_id: atomic::AtomicU8::new(counter_id),
83 time_type: atomic::AtomicU8::new(time_type),
84 seq_count: atomic::AtomicU32::new(seq_count),
85 };
86
87 header.is_valid()?;
88 Ok(header)
89 }
90
91 /// Check whether the magic number matches the expected one.
92 fn matches_magic(&self) -> bool {
93 let magic = self.magic.load(atomic::Ordering::Relaxed);
94 debug!("VMClockShmHeader has magic: {:?}", magic);
95 magic == VMCLOCK_SHM_MAGIC
96 }
97
98 /// Check whether the header is marked with a valid version
99 fn has_valid_version(&self) -> bool {
100 let version = self.version.load(atomic::Ordering::Relaxed);
101 debug!("VMClockShmHeader has version: {:?}", version);
102 version > 0
103 }
104
105 /// Check whether the header is complete
106 fn is_well_formed(&self) -> bool {
107 let size = self.size.load(atomic::Ordering::Relaxed);
108 debug!("VMClockShmHeader has size: {:?}", size);
109 size as usize >= size_of::<Self>()
110 }
111
112 /// Check whether a VMClockShmHeader is valid
113 fn is_valid(&self) -> Result<(), ShmError> {
114 if !self.matches_magic() {
115 error!("VMClockShmHeader does not have a matching magic number.");
116 return Err(ShmError::SegmentMalformed);
117 }
118
119 if !self.has_valid_version() {
120 error!("VMClockShmHeader does not have a valid version number.");
121 return Err(ShmError::SegmentNotInitialized);
122 }
123
124 if !self.is_well_formed() {
125 error!("VMClockShmHeader is not well formed.");
126 return Err(ShmError::SegmentMalformed);
127 }
128 Ok(())
129 }
130}
131
132/// Definition of mutually exclusive clock status exposed to the reader.
133#[repr(u8)]
134#[derive(Debug, Copy, Clone, PartialEq)]
135pub enum VMClockClockStatus {
136 /// The status of the clock is unknown.
137 Unknown = 0,
138
139 /// The clock is being initialized.
140 Initializing = 1,
141
142 /// The clock is kept accurate by the synchronization daemon.
143 Synchronized = 2,
144
145 /// The clock is free running and not updated by the synchronization daemon.
146 FreeRunning = 3,
147
148 /// The clock is unreliable and should not be trusted.
149 /// The reason is unspecified, but it could be due to the hardware counter being broken.
150 Unreliable = 4,
151}
152
153/// Custom struct used for indicating a parsing error when parsing a
154/// VMClockClockStatus from str.
155#[derive(Clone, PartialEq, Eq, Hash, Debug)]
156pub struct ParseError;
157
158impl FromStr for VMClockClockStatus {
159 type Err = ParseError;
160 fn from_str(input: &str) -> Result<VMClockClockStatus, Self::Err> {
161 match input {
162 "Unknown" => Ok(VMClockClockStatus::Unknown),
163 "Initializing" => Ok(VMClockClockStatus::Initializing),
164 "Synchronized" => Ok(VMClockClockStatus::Synchronized),
165 "FreeRunning" => Ok(VMClockClockStatus::FreeRunning),
166 "Unreliable" => Ok(VMClockClockStatus::Unreliable),
167 _ => Err(ParseError),
168 }
169 }
170}
171
172/// Structure that holds the VMClock data captured at a specific point in time.
173///
174/// The structure is shared across the Shared Memory segment and has a C representation to enforce
175/// this specific layout.
176#[repr(C)]
177#[derive(Debug, Copy, Clone, PartialEq)]
178pub struct VMClockShmBody {
179 /// Disruption Marker.
180 ///
181 /// This value is incremented (by an unspecified delta) each time the clock is disrupted.
182 /// This value is specific to a particular VM/EC2 instance.
183 pub disruption_marker: u64,
184
185 /// Flags.
186 ///
187 /// Bit flags representing the following:
188 ///
189 /// Bit (1 << 0): VMCLOCK_FLAG_TAI_OFFSET_VALID: Indicates that the tai_offset_sec field is valid.
190 ///
191 /// The below bits are optionally used to notify guests of pending
192 /// maintenance events. A guest which provides latency-sensitive
193 /// services may wish to remove itself from service if an event is coming up.
194 /// Two flags indicate the approximate imminence of the event.
195 ///
196 /// Bit (1 << 1): VMCLOCK_FLAG_DISRUPTION_SOON: About a day.
197 /// Bit (1 << 2): VMCLOCK_FLAG_DISRUPTION_IMMINENT: About an hour.
198 /// Bit (1 << 3): VMCLOCK_FLAG_PERIOD_ESTERROR_VALID
199 /// Bit (1 << 4): VMCLOCK_FLAG_PERIOD_MAXERROR_VALID
200 /// Bit (1 << 5): VMCLOCK_FLAG_TIME_ESTERROR_VALID
201 /// Bit (1 << 6): VMCLOCK_FLAG_TIME_MAXERROR_VALID
202 ///
203 /// The below bit is the MONOTONIC flag.
204 /// If the MONOTONIC flag is set then (other than leap seconds) it is
205 /// guaranteed that the time calculated according this structure at
206 /// any given moment shall never appear to be later than the time
207 /// calculated via the structure at any *later* moment.
208 ///
209 /// In particular, a timestamp based on a counter reading taken
210 /// immediately after setting the low bit of seq_count (and the
211 /// associated memory barrier), using the previously-valid time and
212 /// period fields, shall never be later than a timestamp based on
213 /// a counter reading taken immediately before *clearing* the low
214 /// bit again after the update, using the about-to-be-valid fields.
215 ///
216 /// Bit (1 << 7): VMCLOCK_FLAG_TIME_MONOTONIC
217 ///
218 pub flags: u64,
219
220 /// Padding.
221 pub _padding: [u8; 2],
222
223 /// Clock Status.
224 ///
225 /// The clock status indicates whether the clock is synchronized,
226 /// free-running, etc.
227 pub clock_status: VMClockClockStatus,
228
229 // Leap Second smearing hint.
230 //
231 /// The time exposed through this device is never smeared. This field
232 /// corresponds to the 'subtype' field in virtio-rtc, which indicates
233 /// the smearing method. However in this case it provides a *hint* to
234 /// the guest operating system, such that *if* the guest OS wants to
235 /// provide its users with an alternative clock which does not follow
236 /// UTC, it may do so in a fashion consistent with the other systems
237 /// in the nearby environment.
238 ///
239 /// Possible values are:
240 ///
241 /// VMCLOCK_SMEARING_STRICT: 0
242 /// VMCLOCK_SMEARING_NOON_LINEAR: 1
243 /// VMCLOCK_SMEARING_UTC_SLS: 2
244 ///
245 pub leap_second_smearing_hint: u8,
246
247 /// Offset between TAI and UTC, in seconds.
248 pub tai_offset_sec: i16,
249
250 /// Leap indicator.
251 ///
252 /// This field is based on the the VIRTIO_RTC_LEAP_xxx values as
253 /// defined in the current draft of virtio-rtc, but since smearing
254 /// cannot be used with the shared memory device, some values are
255 /// not used.
256 ///
257 /// The _POST_POS and _POST_NEG values allow the guest to perform
258 /// its own smearing during the day or so after a leap second when
259 /// such smearing may need to continue being applied for a leap
260 /// second which is now theoretically "historical".
261 ///
262 /// Possible values are:
263 /// VMCLOCK_LEAP_NONE 0x00 // No known nearby leap second
264 /// VMCLOCK_LEAP_PRE_POS 0x01 // Positive leap second at EOM
265 /// VMCLOCK_LEAP_PRE_NEG 0x02 // Negative leap second at EOM
266 /// VMCLOCK_LEAP_POS 0x03 // Set during 23:59:60 second
267 /// VMCLOCK_LEAP_POST_POS 0x04
268 /// VMCLOCK_LEAP_POST_NEG 0x05
269 ///
270 pub leap_indicator: u8,
271
272 /// Counter period shift.
273 ///
274 /// Bit shift for the counter_period_frac_sec and its error rate.
275 pub counter_period_shift: u8,
276
277 /// Counter value.
278 pub counter_value: u64,
279
280 /// Counter period.
281 ///
282 /// This is the estimated period of the counter, in binary fractional seconds.
283 /// The unit of this field is: 1 / (2 ^ (64 + counter_period_shift)) of a second.
284 pub counter_period_frac_sec: u64,
285
286 /// Counter period estimated error rate.
287 ///
288 /// This is the estimated error rate of the counter period, in binary fractional seconds per second.
289 /// The unit of this field is: 1 / (2 ^ (64 + counter_period_shift)) of a second per second.
290 pub counter_period_esterror_rate_frac_sec: u64,
291
292 /// Counter period maximum error rate.
293 ///
294 /// This is the maximum error rate of the counter period, in binary fractional seconds per second.
295 /// The unit of this field is: 1 / (2 ^ (64 + counter_period_shift)) of a second per second.
296 pub counter_period_maxerror_rate_frac_sec: u64,
297
298 /// Time according to the time_type field.
299
300 /// Time: Seconds since time_type epoch.
301 pub time_sec: u64,
302
303 /// Time: Fractional seconds, in units of 1 / (2 ^ 64) of a second.
304 pub time_frac_sec: u64,
305
306 /// Time: Estimated error.
307 pub time_esterror_nanosec: u64,
308
309 /// Time: Maximum error.
310 pub time_maxerror_nanosec: u64,
311}
312
313impl Default for VMClockShmBody {
314 /// Get a default VMClockShmBody struct
315 /// Equivalent to zero'ing this bit of memory
316 fn default() -> Self {
317 VMClockShmBody {
318 disruption_marker: 0,
319 flags: 0,
320 _padding: [0x00, 0x00],
321 clock_status: VMClockClockStatus::Unknown,
322 leap_second_smearing_hint: 0,
323 tai_offset_sec: 0,
324 leap_indicator: 0,
325 counter_period_shift: 0,
326 counter_value: 0,
327 counter_period_frac_sec: 0,
328 counter_period_esterror_rate_frac_sec: 0,
329 counter_period_maxerror_rate_frac_sec: 0,
330 time_sec: 0,
331 time_frac_sec: 0,
332 time_esterror_nanosec: 0,
333 time_maxerror_nanosec: 0,
334 }
335 }
336}
337
338#[cfg(test)]
339mod t_vmclock_shm_header {
340 use super::*;
341 use byteorder::{LittleEndian, WriteBytesExt};
342 use std::fs::{File, OpenOptions};
343 use std::io::Read;
344 use std::path::Path;
345 /// We make use of tempfile::NamedTempFile to ensure that
346 /// local files that are created during a test get removed
347 /// afterwards.
348 use tempfile::NamedTempFile;
349
350 macro_rules! write_vmclock_shm_header {
351 ($file:ident,
352 $magic:literal,
353 $size:literal,
354 $version:literal,
355 $counter_id:literal,
356 $time_type:literal,
357 $seq_count:literal) => {
358 $file
359 .write_u32::<LittleEndian>($magic)
360 .expect("Write failed magic");
361 $file
362 .write_u32::<LittleEndian>($size)
363 .expect("Write failed size");
364 $file
365 .write_u16::<LittleEndian>($version)
366 .expect("Write failed version");
367 $file
368 .write_u8($counter_id)
369 .expect("Write failed counter_id");
370 $file.write_u8($time_type).expect("Write failed time_type");
371 $file
372 .write_u32::<LittleEndian>($seq_count)
373 .expect("Write failed seq_count");
374 $file.sync_all().expect("Sync to disk failed");
375 };
376 }
377
378 fn remove_path_if_exists(path_shm: &str) {
379 let path = Path::new(path_shm);
380 if path.exists() {
381 if path.is_dir() {
382 std::fs::remove_dir_all(path_shm).expect("failed to remove file");
383 } else {
384 std::fs::remove_file(path_shm).expect("failed to remove file");
385 }
386 }
387 }
388
389 /// Assert that a file containing a valid header produces a valid VMClockShmHeader
390 #[test]
391 fn test_header_valid() {
392 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
393 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
394 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
395 let mut vmclock_shm_file = OpenOptions::new()
396 .write(true)
397 .open(vmclock_shm_path)
398 .expect("open vmclock file failed");
399 write_vmclock_shm_header!(
400 vmclock_shm_file,
401 0x4B4C4356,
402 104_u32,
403 1_u16,
404 0_u8,
405 0_u8,
406 99_u32
407 );
408
409 let mut file = File::open(vmclock_shm_path).expect("failed to open file");
410 let mut buffer = vec![];
411 file.read_to_end(&mut buffer)
412 .expect("failed to read to end of the file");
413 let header = VMClockShmHeader::read(&buffer).expect("SHM Reader read");
414
415 assert_eq!(header.magic.into_inner(), 0x4B4C4356);
416 assert_eq!(header.size.into_inner(), 104_u32);
417 assert_eq!(header.version.into_inner(), 1_u16);
418 assert_eq!(header.counter_id.into_inner(), 0_u8);
419 assert_eq!(header.time_type.into_inner(), 0_u8);
420 assert_eq!(header.seq_count.into_inner(), 99_u32);
421 }
422
423 /// Assert that a file with a bad magic returns an error
424 #[test]
425 fn test_header_bad_magic() {
426 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
427 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
428 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
429 let mut vmclock_shm_file = OpenOptions::new()
430 .write(true)
431 .open(vmclock_shm_path)
432 .expect("open vmclock file failed");
433 // magic numbers are bogus
434 write_vmclock_shm_header!(
435 vmclock_shm_file,
436 0xdeadbeef,
437 16_u32,
438 1_u16,
439 0_u8,
440 0_u8,
441 99_u32
442 );
443
444 let mut file = File::open(vmclock_shm_path).expect("failed to open file");
445 let mut buffer = vec![];
446 file.read_to_end(&mut buffer)
447 .expect("failed to read to end of the file");
448 assert!(VMClockShmHeader::read(&buffer).is_err());
449 }
450
451 /// Assert that a file with a wrongly truncated header returns an error
452 #[test]
453 fn test_header_bad_size() {
454 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
455 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
456 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
457 let mut vmclock_shm_file = OpenOptions::new()
458 .write(true)
459 .open(vmclock_shm_path)
460 .expect("open vmclock file failed");
461 write_vmclock_shm_header!(
462 vmclock_shm_file,
463 0x4B4C4356,
464 4_u32,
465 1_u16,
466 0_u8,
467 0_u8,
468 99_u32
469 );
470
471 let mut file = File::open(vmclock_shm_path).expect("failed to open file");
472 let mut buffer = vec![];
473 file.read_to_end(&mut buffer)
474 .expect("failed to read to end of the file");
475 assert!(VMClockShmHeader::read(&buffer).is_err());
476 }
477
478 /// Assert that a file with a version number of 0 returns an error
479 #[test]
480 fn test_header_bad_version() {
481 let vmclock_shm_tempfile = NamedTempFile::new().expect("create vmclock file failed");
482 let vmclock_shm_temppath = vmclock_shm_tempfile.into_temp_path();
483 let vmclock_shm_path = vmclock_shm_temppath.to_str().unwrap();
484 let mut vmclock_shm_file = OpenOptions::new()
485 .write(true)
486 .open(vmclock_shm_path)
487 .expect("open vmclock file failed");
488 // layout version is 0
489 write_vmclock_shm_header!(
490 vmclock_shm_file,
491 0x4B4C4356,
492 16_u32,
493 0_u16,
494 0_u8,
495 0_u8,
496 99_u32
497 );
498
499 let mut file = File::open(vmclock_shm_path).expect("failed to open file");
500 let mut buffer = vec![];
501 file.read_to_end(&mut buffer)
502 .expect("failed to read to end of the file");
503 assert!(VMClockShmHeader::read(&buffer).is_err());
504 }
505}