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}