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
|
use windows::Win32::System::Wmi::{
IWbemClassObject, WBEM_E_NOT_FOUND, WBEM_FLAG_RETURN_WBEM_COMPLETE,
};
use windows_core::{BSTR, PCWSTR, VARIANT};
use wmi::result_enumerator::IWbemClassWrapper;
/// Name of the blocking Hyper-V rule.
const BLOCK_OUTBOUND_RULE_ELEMENT_NAME: &str = "Mullvad VPN outbound block-all rule";
/// Name of the blocking Hyper-V rule.
const BLOCK_INBOUND_RULE_ELEMENT_NAME: &str = "Mullvad VPN inbound block-all rule";
/// Unique instance ID identifying the outbound blocking Hyper-V rule.
const BLOCK_OUTBOUND_RULE_UUID: &str = "{319400cb-0445-4c1b-a081-1cbc57cdbcb8}";
/// Unique instance ID identifying the inbound blocking Hyper-V rule.
const BLOCK_INBOUND_RULE_UUID: &str = "{95a5e2c6-ebd5-45e5-9495-12c5d807cd91}";
const WMI_NAMESPACE: &str = "root\\standardcimv2";
/// Errors occurring while configuring Hyper-V firewall rules
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Failed to initialize the COM library")]
InitializeCom(#[source] wmi::WMIError),
#[error("Failed to connect to the WMI namespace '{WMI_NAMESPACE}'")]
ConnectWmi(#[source] wmi::WMIError),
#[error("Failed to obtain Hyper-V rule class")]
ObtainHyperVClass(#[source] wmi::WMIError),
#[error("Failed to create new instance of Hyper-V rule class")]
NewRuleInstance(#[source] windows_core::Error),
#[error("Failed to set rule setting: {0}")]
SetRuleKey(&'static str, #[source] windows_core::Error),
#[error(r#"Failed to put the rule "{0}""#)]
PutInstance(&'static str, #[source] windows_core::Error),
#[error(r#"Failed to delete rule "{0}""#)]
DeleteInstance(&'static str, #[source] windows_core::Error),
}
/// Initialize WMI connection to the ROOT\StandardCIMV2 namespace, which may be used for
/// interacting with Hyper-V rules.
pub fn init_wmi() -> Result<wmi::WMIConnection, Error> {
let con = wmi::WMIConnection::with_namespace_path(
WMI_NAMESPACE,
wmi::COMLibrary::new().map_err(Error::InitializeCom)?,
)
.map_err(Error::ConnectWmi)?;
// Test whether the class is available
let _ = con
.get_raw_by_path("MSFT_NetFirewallHyperVRule")
.map_err(Error::ObtainHyperVClass)?;
Ok(con)
}
/// Add a Hyper-V rule that blocks all traffic using WMI (Windows Management Instrumentation).
///
/// Instances of the WMI class `MSFT_NetFirewallHyperVRule` in the namespace "root\standardcimv2"
/// belong to the same firewall ruleset as that visible in PowerShell using the command
/// `Get-NetFirewallHyperVRule`.
///
/// Details about the `MSFT_NetFirewallHyperVRule`, including the meaning of properties, are
/// documented here:
/// <https://learn.microsoft.com/en-us/windows/win32/fwp/wmi/wfascimprov/msft-netfirewallhypervrule>
///
/// `con` must be a valid WMI connection for the `root\standardcimv2` WMI namespace. Such a connection
/// can be initialized using [`init_wmi`].
pub fn add_blocking_hyperv_firewall_rules(con: &wmi::WMIConnection) -> Result<(), Error> {
let class = con
.get_raw_by_path("MSFT_NetFirewallHyperVRule")
.map_err(Error::ObtainHyperVClass)?;
add_blocking_rule(
con,
&class,
BLOCK_OUTBOUND_RULE_ELEMENT_NAME,
BLOCK_OUTBOUND_RULE_UUID,
Direction::Outbound,
)?;
add_blocking_rule(
con,
&class,
BLOCK_INBOUND_RULE_ELEMENT_NAME,
BLOCK_INBOUND_RULE_UUID,
Direction::Inbound,
)
}
#[repr(i32)]
enum Direction {
Inbound = 1,
Outbound = 2,
}
fn add_blocking_rule(
con: &wmi::WMIConnection,
rule_class: &IWbemClassWrapper,
element_name: &'static str,
instance_id: &str,
direction: Direction,
) -> Result<(), Error> {
// SAFETY: We have a valid class wrapper, so spawning instances is safe
let instance = unsafe { rule_class.inner.SpawnInstance(0) }.map_err(Error::NewRuleInstance)?;
put_instance_property(
&instance,
"ElementName",
&VARIANT::from(BSTR::from(element_name)),
)?;
put_instance_property(
&instance,
"InstanceID",
&VARIANT::from(BSTR::from(instance_id)),
)?;
// Action: 4 = block
put_instance_property(&instance, "Action", &VARIANT::from(4))?;
// Enabled: 1 = enabled
put_instance_property(&instance, "Enabled", &VARIANT::from(1))?;
put_instance_property(&instance, "Direction", &VARIANT::from(direction as i32))?;
// SAFETY: We have a valid instance
unsafe {
con.svc
.PutInstance(&instance, WBEM_FLAG_RETURN_WBEM_COMPLETE, None, None)
.map_err(|error| Error::PutInstance(element_name, error))
}
}
/// Set property for a WMI class instance `inst`.
fn put_instance_property(
inst: &IWbemClassObject,
prop: &'static str,
val: &VARIANT,
) -> Result<(), Error> {
let utf16_prop: Vec<_> = prop.encode_utf16().chain(std::iter::once(0u16)).collect();
// SAFETY: All arguments are valid and properly null-terminated
unsafe {
inst.Put(PCWSTR(utf16_prop.as_ptr()), 0, val, 0)
.map_err(|error| Error::SetRuleKey(prop, error))
}
}
/// Remove Hyper-V rule previously added by [`add_blocking_hyperv_firewall_rules`]. See the
/// documentation of that function for more details.
///
/// This function succeeds if the rule is not present or has already been removed.
///
/// `con` must be a valid WMI connection for the `root\standardcimv2` WMI namespace. Such a connection
/// can be initialized using [`init_wmi`].
pub fn remove_blocking_hyperv_firewall_rules(con: &wmi::WMIConnection) -> Result<(), Error> {
remove_blocking_rule(
con,
BLOCK_INBOUND_RULE_ELEMENT_NAME,
BLOCK_INBOUND_RULE_UUID,
)?;
remove_blocking_rule(
con,
BLOCK_OUTBOUND_RULE_ELEMENT_NAME,
BLOCK_OUTBOUND_RULE_UUID,
)
}
fn remove_blocking_rule(
con: &wmi::WMIConnection,
element_name: &'static str,
instance_id: &str,
) -> Result<(), Error> {
let rule_path = BSTR::from(format!(
r#"MSFT_NetFirewallHyperVRule.InstanceID="{instance_id}""#
));
// SAFETY: All arguments are valid.
unsafe {
con.svc
.DeleteInstance(&rule_path, WBEM_FLAG_RETURN_WBEM_COMPLETE, None, None)
.or_else(|error| map_deletion_err(element_name, error))
}
}
fn map_deletion_err(element_name: &'static str, err: windows_core::Error) -> Result<(), Error> {
if err.code().0 == WBEM_E_NOT_FOUND.0 {
// If the rule doesn't exist, do nothing
Ok(())
} else {
Err(Error::DeleteInstance(element_name, err))
}
}
|