openvmm_entry/
kvp.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code to handle KVP (Key-Value Pair) operations.
5
6use hyperv_ic_resources::kvp::KvpConnectRpc;
7use mesh::CancelContext;
8use mesh::rpc::RpcSend as _;
9use std::net::Ipv4Addr;
10use std::net::Ipv6Addr;
11use std::time::Duration;
12
13#[derive(clap::Args)]
14pub(crate) struct KvpCommand {
15    /// The timeout in seconds.
16    #[clap(long, default_value = "3")]
17    timeout: u64,
18    #[clap(subcommand)]
19    command: KvpSubcommand,
20}
21
22#[derive(clap::Subcommand)]
23enum KvpSubcommand {
24    /// Set a key/value pair in the KVP store.
25    Set {
26        /// The pool to use.
27        #[clap(long)]
28        pool: KvpPool,
29
30        /// The key to set the value of.
31        key: String,
32
33        #[clap(long = "type", short = 't', default_value = "string")]
34        value_type: ValueType,
35
36        /// The value to set.
37        value: String,
38    },
39    /// Get a key/value pair from the KVP store.
40    Get {
41        /// The pool to use.
42        #[clap(long)]
43        pool: KvpPool,
44
45        /// The key to get the value of.
46        key: String,
47    },
48    /// Enumerate the key/value pairs in the KVP store.
49    Enum {
50        /// The pool to use.
51        #[clap(long)]
52        pool: KvpPool,
53    },
54    /// Get IP address information for a given adapter.
55    IpInfo {
56        /// The MAC address to get the IP info for.
57        adapter_id: String,
58    },
59}
60
61#[derive(clap::ValueEnum, Clone)]
62enum KvpPool {
63    Guest,
64    External,
65    Auto,
66    AutoExternal,
67}
68
69#[derive(clap::ValueEnum, Clone)]
70enum ValueType {
71    String,
72    Dword,
73    Qword,
74}
75
76pub(crate) async fn handle_kvp(
77    kvp: &mesh::Sender<KvpConnectRpc>,
78    command: KvpCommand,
79) -> anyhow::Result<()> {
80    let KvpCommand { timeout, command } = command;
81    CancelContext::new()
82        .with_timeout(Duration::from_secs(timeout))
83        .until_cancelled(handle_subcommand(kvp, command))
84        .await?
85}
86
87async fn handle_subcommand(
88    kvp: &mesh::Sender<KvpConnectRpc>,
89    command: KvpSubcommand,
90) -> anyhow::Result<()> {
91    let (kvp, _) = kvp.call_failable(KvpConnectRpc::WaitForGuest, ()).await?;
92    match command {
93        KvpSubcommand::Set {
94            pool,
95            key,
96            value_type,
97            value,
98        } => {
99            let pool = pool_cvt(pool);
100            let value = match value_type {
101                ValueType::String => hyperv_ic_resources::kvp::Value::String(value),
102                ValueType::Dword => hyperv_ic_resources::kvp::Value::U32(value.parse()?),
103                ValueType::Qword => hyperv_ic_resources::kvp::Value::U64(value.parse()?),
104            };
105            kvp.call_failable(
106                hyperv_ic_resources::kvp::KvpRpc::Set,
107                hyperv_ic_resources::kvp::SetParams { pool, key, value },
108            )
109            .await?;
110        }
111        KvpSubcommand::Get { pool, key } => {
112            // Can you believe it? They never implemented the get
113            // operation in the guest. Enumerate instead.
114            let pool = pool_cvt(pool);
115            for i in 0.. {
116                match kvp
117                    .call_failable(
118                        hyperv_ic_resources::kvp::KvpRpc::Enumerate,
119                        hyperv_ic_resources::kvp::EnumerateParams { pool, index: i },
120                    )
121                    .await?
122                {
123                    Some(v) if v.key == key => {
124                        println!("{}", DisplayValue(&v.value));
125                        break;
126                    }
127                    Some(_) => {
128                        // Do nothing, continue searching
129                    }
130                    None => {
131                        anyhow::bail!("not found");
132                    }
133                }
134            }
135        }
136        KvpSubcommand::Enum { pool } => {
137            let pool = pool_cvt(pool);
138            for i in 0.. {
139                match kvp
140                    .call_failable(
141                        hyperv_ic_resources::kvp::KvpRpc::Enumerate,
142                        hyperv_ic_resources::kvp::EnumerateParams { pool, index: i },
143                    )
144                    .await?
145                {
146                    Some(v) => {
147                        println!("{}: {}", v.key, DisplayValue(&v.value));
148                    }
149                    None => break,
150                }
151            }
152        }
153        KvpSubcommand::IpInfo { adapter_id } => {
154            let ip_info = kvp
155                .call_failable(
156                    hyperv_ic_resources::kvp::KvpRpc::GetIpInfo,
157                    hyperv_ic_resources::kvp::GetIpInfoParams { adapter_id },
158                )
159                .await?;
160            let hyperv_ic_resources::kvp::IpInfo {
161                ipv4,
162                ipv6,
163                dhcp_enabled,
164                ipv4_addresses,
165                ipv6_addresses,
166                ipv4_gateways,
167                ipv6_gateways,
168                ipv4_dns_servers,
169                ipv6_dns_servers,
170            } = ip_info;
171            if dhcp_enabled {
172                println!("DHCP enabled");
173            } else {
174                println!("DHCP disabled");
175            }
176            let origin_str = |origin| match origin {
177                hyperv_ic_resources::kvp::AddressOrigin::Unknown => "unknown",
178                hyperv_ic_resources::kvp::AddressOrigin::Other => "other",
179                hyperv_ic_resources::kvp::AddressOrigin::Static => "static",
180            };
181            if ipv4 {
182                let a = Ipv4Addr::from;
183                println!("IPv4:");
184                for addr in ipv4_addresses {
185                    println!(
186                        "  {}/{} (origin {})",
187                        a(addr.address),
188                        a(addr.subnet),
189                        origin_str(addr.origin)
190                    );
191                }
192                for gw in ipv4_gateways {
193                    println!("  Gateway: {}", a(gw));
194                }
195                for dns in ipv4_dns_servers {
196                    println!("  DNS: {}", a(dns));
197                }
198            }
199            if ipv6 {
200                let a = Ipv6Addr::from;
201                println!("IPv6:");
202                for addr in ipv6_addresses {
203                    println!(
204                        "  {}/{} (origin {})",
205                        a(addr.address),
206                        addr.subnet,
207                        origin_str(addr.origin)
208                    );
209                }
210                for gw in ipv6_gateways {
211                    println!("  Gateway: {}", a(gw));
212                }
213                for dns in ipv6_dns_servers {
214                    println!("  DNS: {}", a(dns));
215                }
216            }
217        }
218    }
219    Ok(())
220}
221
222fn pool_cvt(pool: KvpPool) -> hyperv_ic_resources::kvp::KvpPool {
223    match pool {
224        KvpPool::Guest => hyperv_ic_resources::kvp::KvpPool::Guest,
225        KvpPool::External => hyperv_ic_resources::kvp::KvpPool::External,
226        KvpPool::Auto => hyperv_ic_resources::kvp::KvpPool::Auto,
227        KvpPool::AutoExternal => hyperv_ic_resources::kvp::KvpPool::AutoExternal,
228    }
229}
230
231struct DisplayValue<'a>(&'a hyperv_ic_resources::kvp::Value);
232
233impl std::fmt::Display for DisplayValue<'_> {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        match self.0 {
236            hyperv_ic_resources::kvp::Value::String(s) => write!(f, "\"{s}\""),
237            hyperv_ic_resources::kvp::Value::U32(v) => write!(f, "{v}"),
238            hyperv_ic_resources::kvp::Value::U64(v) => write!(f, "{v}"),
239        }
240    }
241}