-
Notifications
You must be signed in to change notification settings - Fork 110
/
Copy pathamd64.rs
205 lines (185 loc) · 7.12 KB
/
amd64.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
//! VMware provider on x86_64.
//!
//! This uses the guest->host backdoor protocol for introspection.
use super::VmwareProvider;
use anyhow::{bail, Context, Result};
use base64::{engine::general_purpose, Engine as _};
use libflate::gzip::Decoder;
use serde_json::json;
use std::io::Read;
/// Guestinfo key for network kargs.
static INITRD_NET_KARGS: &str = "guestinfo.afterburn.initrd.network-kargs";
static METADATA: &str = "guestinfo.metadata";
static METADATA_ENCODING: &str = "guestinfo.metadata.encoding";
impl VmwareProvider {
/// Build the VMware provider, fetching and caching guestinfo entries.
pub fn try_new() -> Result<Self> {
if !vmw_backdoor::is_vmware_cpu() {
bail!("not running on VMWare CPU");
}
// NOTE(lucab): privileged mode is in theory more reliable but
// `kernel_lockdown(7)` may block it due to `iopl()` usage.
// Thus, we try that first and fall back if kernel blocks it.
let mut backdoor = vmw_backdoor::probe_backdoor_privileged().or_else(|e| {
slog_scope::warn!("failed to probe backdoor in privileged mode: {}", e);
slog_scope::warn!("falling back to unprivileged backdoor access");
vmw_backdoor::probe_backdoor()
})?;
let guestinfo_net_kargs = {
// Use a block, otherwise we would have to drop(erpc) manually
let mut erpc = vmw_backdoor::EnhancedChan::open(&mut backdoor)?;
Self::fetch_guestinfo(&mut erpc, INITRD_NET_KARGS)?
};
let guestinfo_metadata_raw = {
let mut erpc = vmw_backdoor::EnhancedChan::open(&mut backdoor)?;
Self::fetch_guestinfo(&mut erpc, METADATA)?
};
let guestinfo_metadata_encoding = {
let mut erpc = vmw_backdoor::EnhancedChan::open(&mut backdoor)?;
Self::fetch_guestinfo(&mut erpc, METADATA_ENCODING)?
};
let guestinfo_metadata =
parse_metadata(guestinfo_metadata_encoding, guestinfo_metadata_raw)?;
let provider = Self {
guestinfo_net_kargs,
guestinfo_metadata,
};
slog_scope::trace!("cached vmware provider: {:?}", provider);
Ok(provider)
}
/// Retrieve the value of a guestinfo string property, by key.
fn fetch_guestinfo(erpc: &mut vmw_backdoor::EnhancedChan, key: &str) -> Result<Option<String>> {
let guestinfo = erpc
.get_guestinfo(key.as_bytes())
.with_context(|| format!("failed to retrieve guestinfo for {}", key))?
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned());
Ok(guestinfo)
}
pub fn parse_netplan_config(&self) -> Result<Option<String>> {
if let Some(metadata) = &self.guestinfo_metadata {
// We need to parse the netplan config to remove the non-Netplan keys.
// The data can either be JSON or YAML, but since JSON is a subset of
// YAML we don't need to try serde_json::from_str here.
let netplan_config_unfiltered: serde_json::Value =
serde_yaml::from_str(metadata).context("invalid YAML/JSON metadata")?;
// Only the "network" key is allowed to be present.
// We use the json! macro but this is only about creating a serde value::Value,
// even though its name sounds like it would create JSON.
let netplan_config_filtered = json!({
"network": netplan_config_unfiltered.get("network").context("no 'network' key found")?
});
Ok(Some(serde_yaml::to_string(&netplan_config_filtered)?))
} else {
Ok(None)
}
}
#[cfg(test)]
pub fn new_from_metadata(metadata: String) -> Result<Self> {
Ok(Self {
guestinfo_net_kargs: None,
guestinfo_metadata: Some(metadata),
})
}
}
fn parse_metadata(
guestinfo_metadata_encoding: Option<String>,
guestinfo_metadata_raw: Option<String>,
) -> Result<Option<String>> {
match (
guestinfo_metadata_encoding.as_deref(),
guestinfo_metadata_raw,
) {
(Some("base64" | "b64"), Some(guestinfo_metadata_raw_val)) => {
let decoded =
general_purpose::STANDARD.decode(guestinfo_metadata_raw_val.as_bytes())?;
Ok(Some(String::from_utf8(decoded)?))
}
(Some("gzip+base64" | "gz+b64"), Some(guestinfo_metadata_raw_val)) => {
let decoded =
general_purpose::STANDARD.decode(guestinfo_metadata_raw_val.as_bytes())?;
let mut decompressor = Decoder::new(decoded.as_slice())?;
let mut uncompressed = Vec::new();
decompressor.read_to_end(&mut uncompressed)?;
Ok(Some(String::from_utf8(uncompressed)?))
}
(Some(""), guestinfo_metadata_raw) => Ok(guestinfo_metadata_raw),
(Some(encoding), _) => bail!("unknown guestinfo.metadata.encoding '{}'", encoding),
(None, guestinfo_metadata_raw) => Ok(guestinfo_metadata_raw),
}
}
#[test]
fn test_netplan_json() {
let metadata = r#"{
"network": {
"ethernets": {
"nics": {
"match": {
"name": "ens*"
}
}
}
},
"ExcludeNonNetplanField": 0
}"#;
let provider = VmwareProvider::new_from_metadata(metadata.to_owned()).unwrap();
let netplan_config = provider.parse_netplan_config().unwrap().unwrap();
let expected = r#"network:
ethernets:
nics:
match:
name: ens*
"#;
assert_eq!(netplan_config, expected);
}
#[test]
fn test_netplan_dhcp() {
let metadata = r#"network:
ethernets:
nics:
match:
name: ens*
"#;
let provider = VmwareProvider::new_from_metadata(metadata.to_owned()).unwrap();
let netplan_config = provider.parse_netplan_config().unwrap().unwrap();
assert_eq!(netplan_config, metadata);
}
#[test]
fn test_metadata_plain_1() {
let guestinfo_metadata_raw = Some("hello".to_owned());
let parsed = parse_metadata(None, guestinfo_metadata_raw)
.unwrap()
.unwrap();
assert_eq!(parsed, "hello");
}
#[test]
fn test_metadata_plain_2() {
let guestinfo_metadata_raw = Some("hello".to_owned());
let parsed = parse_metadata(Some("".into()), guestinfo_metadata_raw)
.unwrap()
.unwrap();
assert_eq!(parsed, "hello");
}
#[test]
fn test_metadata_base64() {
let guestinfo_metadata_raw = Some("aGVsbG8=".to_owned());
let parsed = parse_metadata(Some("base64".into()), guestinfo_metadata_raw.clone())
.unwrap()
.unwrap();
assert_eq!(parsed, "hello");
let parsed_b64 = parse_metadata(Some("b64".into()), guestinfo_metadata_raw)
.unwrap()
.unwrap();
assert_eq!(parsed_b64, "hello");
}
#[test]
fn test_metadata_gzip_base64() {
let guestinfo_metadata_raw = Some("H4sIAAAAAAACA8tIzcnJBwCGphA2BQAAAA==".to_owned());
let parsed = parse_metadata(Some("gzip+base64".into()), guestinfo_metadata_raw.clone())
.unwrap()
.unwrap();
assert_eq!(parsed, "hello");
let parsed_b64 = parse_metadata(Some("gz+b64".into()), guestinfo_metadata_raw)
.unwrap()
.unwrap();
assert_eq!(parsed_b64, "hello");
}